实战案例:实现120fps流畅的渐变进度条
高性能视图演示项目摘要 该项目展示Android中不同实现方式的进度条视图性能对比,包含四种实现: 基础实现 - 简单但性能较差的自定义视图 优化实现 - 减少不必要的绘制操作 异步实现 - 在子线程处理计算任务 高性能实现 - 综合各种优化技术 主界面(MainActivity)提供: 手动/自动控制进度动画 实时性能监控(FPS、内存、CPU、GPU) 性能基准测试入口 缓存管理功能 项目结构
·
项目结构
HighPerformanceViewDemo/
├── app/
│ ├── src/main/java/com/example/performance/
│ │ ├── MainActivity.kt
│ │ ├── view/
│ │ │ ├── BadProgressView.kt
│ │ │ ├── OptimizedProgressView.kt
│ │ │ ├── AsyncProgressView.kt
│ │ │ └── HighPerformanceProgressView.kt
│ │ ├── utils/
│ │ │ ├── PerformanceMonitor.kt
│ │ │ ├── BitmapManager.kt
│ │ │ └── FrameRateTracker.kt
│ │ └── performance/
│ │ ├── BenchmarkActivity.kt
│ │ └── PerformancePanel.kt
│ └── build.gradle.kts
└── README.md
1. 主界面实现 (MainActivity.kt)
package com.example.performance
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import com.example.performance.databinding.ActivityMainBinding
import com.example.performance.utils.PerformanceMonitor
import com.example.performance.view.*
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val handler = Handler(Looper.getMainLooper())
private var isRunning = false
private var currentProgress = 0f
private val progressUpdater = object : Runnable {
override fun run() {
if (isRunning) {
currentProgress = (currentProgress + 0.01f) % 1f
binding.badProgressView.setProgress(currentProgress)
binding.optimizedProgressView.setProgress(currentProgress)
binding.asyncProgressView.setProgress(currentProgress)
binding.highPerformanceProgressView.setProgress(currentProgress)
handler.postDelayed(this, 16) // ~60fps更新
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupViews()
setupPerformanceMonitor()
}
private fun setupViews() {
// 手动进度控制
binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
val progressFloat = progress / 100f
binding.badProgressView.setProgress(progressFloat)
binding.optimizedProgressView.setProgress(progressFloat)
binding.asyncProgressView.setProgress(progressFloat)
binding.highPerformanceProgressView.setProgress(progressFloat)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
// 自动动画开关
binding.btnToggleAnimation.setOnClickListener {
isRunning = !isRunning
binding.btnToggleAnimation.text = if (isRunning) "停止动画" else "开始动画"
if (isRunning) {
handler.post(progressUpdater)
} else {
handler.removeCallbacks(progressUpdater)
}
}
// 性能测试按钮
binding.btnBenchmark.setOnClickListener {
BenchmarkActivity.start(this)
}
// 清空缓存按钮
binding.btnClearCache.setOnClickListener {
BitmapManager.clearCache()
binding.tvCacheStatus.text = "缓存已清空"
handler.postDelayed({
binding.tvCacheStatus.text = ""
}, 1500)
}
}
private fun setupPerformanceMonitor() {
PerformanceMonitor.startMonitoring(this)
// 每2秒更新一次性能数据
handler.post(object : Runnable {
override fun run() {
val info = PerformanceMonitor.getPerformanceInfo()
binding.tvFps.text = String.format("FPS: %.1f", info.fps)
binding.tvMemory.text = String.format("内存: %.1fMB", info.usedMemoryMB)
binding.tvCpu.text = String.format("CPU: %.1f%%", info.cpuUsage)
binding.tvGpuTime.text = String.format("GPU: %.1fms", info.gpuTimeMs)
// 设置颜色提示
val fpsColor = when {
info.fps > 55 -> 0xFF4CAF50.toInt() // 绿色
info.fps > 30 -> 0xFFFFC107.toInt() // 黄色
else -> 0xFFF44336.toInt() // 红色
}
binding.tvFps.setTextColor(fpsColor)
handler.postDelayed(this, 2000)
}
})
}
override fun onDestroy() {
super.onDestroy()
isRunning = false
handler.removeCallbacks(progressUpdater)
PerformanceMonitor.stopMonitoring()
}
}
2. 布局文件 (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 性能监控面板 -->
<com.example.performance.performance.PerformancePanel
android:id="@+id/performancePanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp" />
<!-- 控制面板 -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="控制面板"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/btnToggleAnimation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始动画"
android:layout_marginBottom="8dp" />
<Button
android:id="@+id/btnBenchmark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="性能测试"
android:layout_marginBottom="8dp" />
<Button
android:id="@+id/btnClearCache"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="清空Bitmap缓存" />
<TextView
android:id="@+id/tvCacheStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/holo_green_dark"
android:layout_marginTop="8dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 四个对比View -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="❌ 基础实现 (问题版)"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<com.example.performance.view.BadProgressView
android:id="@+id/badProgressView"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="✅ 优化版1:硬件加速"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<com.example.performance.view.OptimizedProgressView
android:id="@+id/optimizedProgressView"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🚀 优化版2:异步绘制 (API 28+)"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<com.example.performance.view.AsyncProgressView
android:id="@+id/asyncProgressView"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🏆 优化版3:综合优化"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<com.example.performance.view.HighPerformanceProgressView
android:id="@+id/highPerformanceProgressView"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="24dp" />
<!-- 实时性能数据 -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="实时性能数据"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/tvFps"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="FPS: --"
android:textSize="14sp" />
<TextView
android:id="@+id/tvMemory"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="内存: --MB"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tvCpu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="CPU: --%"
android:textSize="14sp" />
<TextView
android:id="@+id/tvGpuTime"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="GPU: --ms"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>
3. 四个View的完整实现
3.1 BadProgressView.kt (问题版)
package com.example.performance.view
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import kotlin.random.Random
class BadProgressView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// ❌ 问题1:每次onDraw都创建新的Paint对象
// ❌ 问题2:Shader在onDraw中频繁创建
// ❌ 问题3:过度绘制
// ❌ 问题4:对象分配频繁
private var progress = 0f
private var lastUpdateTime = 0L
private var frameCount = 0
init {
// 故意不启用硬件加速,展示问题
setLayerType(LAYER_TYPE_SOFTWARE, null)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// ❌ 每帧都创建新对象
val paint = Paint().apply {
color = Color.RED
style = Paint.Style.FILL
}
// ❌ 在onDraw中创建Shader
val shader = LinearGradient(
0f, 0f, width.toFloat(), 0f,
Color.RED, Color.BLUE, Shader.TileMode.CLAMP
)
paint.shader = shader
// ❌ 每帧都创建RectF
val rect = RectF(0f, 0f, width * progress, height.toFloat())
// 绘制背景
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
// ❌ 过度绘制:先画背景再画进度
paint.color = Color.GREEN
canvas.drawRect(rect, paint)
// ❌ 复杂绘制:圆角效果(软件绘制性能差)
val path = Path().apply {
addRoundRect(rect, 20f, 20f, Path.Direction.CW)
}
paint.color = Color.YELLOW
canvas.drawPath(path, paint)
// ❌ 文本绘制频繁
val textPaint = Paint().apply {
color = Color.WHITE
textSize = 24f
textAlign = Paint.Align.CENTER
}
// ❌ 每帧都计算文本位置
val text = "${(progress * 100).toInt()}%"
val textBounds = Rect()
textPaint.getTextBounds(text, 0, text.length, textBounds)
// ❌ 多次绘制文本
canvas.drawText(text, width / 2f, height / 2f, textPaint)
canvas.drawText("FPS: ${calculateFPS()}", 100f, 30f, textPaint)
// ❌ 不必要的阴影效果
paint.setShadowLayer(10f, 5f, 5f, Color.GRAY)
canvas.drawText("性能差", 200f, 30f, paint)
frameCount++
}
fun setProgress(progress: Float) {
this.progress = progress
invalidate() // ❌ 全量刷新
}
private fun calculateFPS(): Int {
val currentTime = System.currentTimeMillis()
if (currentTime - lastUpdateTime > 1000) {
lastUpdateTime = currentTime
val fps = frameCount
frameCount = 0
return fps
}
return 0
}
// ❌ 内存泄漏风险
private val bitmaps = mutableListOf<Bitmap>()
private fun createMemoryLeak() {
// 故意创建Bitmap但不回收
repeat(10) {
bitmaps.add(
Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
// ❌ 忘记清理Bitmap
// bitmaps.forEach { it.recycle() }
}
}
3.2 OptimizedProgressView.kt (硬件加速优化版)
package com.example.performance.view
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.annotation.RequiresApi
class OptimizedProgressView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// ✅ 优化1:复用Paint对象
private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {
style = Paint.Style.FILL
color = Color.BLUE
}
private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
textSize = 32f
textAlign = Paint.Align.CENTER
typeface = Typeface.DEFAULT_BOLD
}
private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.LTGRAY
style = Paint.Style.FILL
}
// ✅ 优化2:复用RectF对象
private val progressRect = RectF()
private val bgRect = RectF()
// ✅ 优化3:Shader缓存
private var gradientShader: LinearGradient? = null
private var lastWidth = 0
// ✅ 优化4:文本边界缓存
private val textBounds = Rect()
private var cachedText = ""
private var progress = 0f
init {
// ✅ 启用硬件加速
setLayerType(LAYER_TYPE_HARDWARE, null)
// ✅ 标记不支持硬件加速的操作
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
forceHasOverlappingRendering(false)
}
// ✅ 初始化Shader
updateShaderIfNeeded()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
updateShaderIfNeeded()
}
private fun updateShaderIfNeeded() {
if (width != lastWidth && width > 0) {
gradientShader = LinearGradient(
0f, 0f, width.toFloat(), 0f,
intArrayOf(
Color.parseColor("#FF6B6B"),
Color.parseColor("#4ECDC4"),
Color.parseColor("#45B7D1")
),
floatArrayOf(0f, 0.5f, 1f),
Shader.TileMode.CLAMP
)
progressPaint.shader = gradientShader
lastWidth = width
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// ✅ 检查硬件加速
if (!canvas.isHardwareAccelerated) {
drawSoftware(canvas)
return
}
// ✅ 绘制背景
bgRect.set(0f, 0f, width.toFloat(), height.toFloat())
canvas.drawRoundRect(bgRect, 12f, 12f, bgPaint)
// ✅ 绘制进度条
if (progress > 0) {
progressRect.set(0f, 0f, width * progress, height.toFloat())
canvas.drawRoundRect(progressRect, 12f, 12f, progressPaint)
// ✅ 缓存文本计算
val text = "${(progress * 100).toInt()}%"
if (text != cachedText) {
textPaint.getTextBounds(text, 0, text.length, textBounds)
cachedText = text
}
// ✅ 计算文本位置
val x = (width * progress).coerceAtLeast(textBounds.width() / 2f + 20)
val y = height / 2f - (textBounds.top + textBounds.bottom) / 2f
// ✅ 只绘制一次文本
canvas.drawText(text, x, y, textPaint)
}
}
private fun drawSoftware(canvas: Canvas) {
// 软件绘制的回退方案
bgRect.set(0f, 0f, width.toFloat(), height.toFloat())
canvas.drawRoundRect(bgRect, 12f, 12f, bgPaint)
if (progress > 0) {
progressRect.set(0f, 0f, width * progress, height.toFloat())
canvas.drawRoundRect(progressRect, 12f, 12f, progressPaint)
}
}
fun setProgress(progress: Float) {
this.progress = progress.coerceIn(0f, 1f)
// ✅ 使用局部刷新
val left = (width * (this.progress - 0.01f)).toInt()
val right = (width * (this.progress + 0.01f)).toInt()
invalidate(left, 0, right, height)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
// ✅ 清理资源
gradientShader = null
}
}
3.3 AsyncProgressView.kt (异步绘制版)
package com.example.performance.view
import android.content.Context
import android.graphics.*
import android.os.*
import android.util.AttributeSet
import android.view.View
import androidx.annotation.RequiresApi
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@RequiresApi(Build.VERSION_CODES.P)
class AsyncProgressView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// ✅ 异步绘制优化
private val renderThread = RenderThread()
private val mainHandler = Handler(Looper.getMainLooper())
private val asyncExecutor: ExecutorService = Executors.newSingleThreadExecutor()
// ✅ RenderNode API
private var renderNode: RenderNode? = null
private var currentBitmap: Bitmap? = null
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
}
private var progress = 0f
private var targetProgress = 0f
private val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 300
interpolator = android.view.animation.AccelerateDecelerateInterpolator()
addUpdateListener {
progress = it.animatedValue as Float
scheduleRender()
}
}
init {
// ✅ 必须启用硬件加速
setLayerType(LAYER_TYPE_HARDWARE, null)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// ✅ 使用RenderNode
renderNode = RenderNode("AsyncProgressView")
}
// ✅ 初始化渐变
paint.shader = LinearGradient(
0f, 0f, 1000f, 0f, // 使用最大宽度
intArrayOf(
Color.parseColor("#667EEA"),
Color.parseColor("#764BA2"),
Color.parseColor("#F093FB")
),
null,
Shader.TileMode.CLAMP
)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
// ✅ 创建离屏Bitmap用于异步绘制
if (w > 0 && h > 0) {
currentBitmap?.recycle()
currentBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
renderNode?.setPosition(0, 0, w, h)
}
}
fun setProgress(progress: Float) {
targetProgress = progress.coerceIn(0f, 1f)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// ✅ 使用RenderThread驱动的动画
animator.setFloatValues(this.progress, targetProgress)
if (!animator.isRunning) {
animator.start()
}
} else {
this.progress = targetProgress
scheduleRender()
}
}
private fun scheduleRender() {
// ✅ 提交异步绘制任务
asyncExecutor.submit {
renderOffscreen()
// ✅ 完成后通知主线程更新
mainHandler.post {
invalidate()
}
}
}
private fun renderOffscreen() {
val bitmap = currentBitmap ?: return
val canvas = Canvas(bitmap)
// ✅ 在后台线程绘制
drawContent(canvas)
// ✅ 使用RenderNode记录绘制命令
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val node = renderNode ?: return
node.beginRecording().let { recorder ->
drawContent(recorder)
node.endRecording()
}
}
}
private fun drawContent(canvas: Canvas) {
// 清空画布
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
// 绘制背景
canvas.drawRoundRect(
0f, 0f, width.toFloat(), height.toFloat(),
16f, 16f, Paint().apply {
color = Color.parseColor("#E0E0E0")
isAntiAlias = true
}
)
// 绘制进度
val progressWidth = width * progress
if (progressWidth > 0) {
canvas.drawRoundRect(
0f, 0f, progressWidth, height.toFloat(),
16f, 16f, paint
)
// 绘制文本
val text = "${(progress * 100).toInt()}%"
val textPaint = Paint().apply {
color = Color.WHITE
textSize = 28f
textAlign = Paint.Align.CENTER
isAntiAlias = true
typeface = Typeface.DEFAULT_BOLD
}
val x = (width * progress / 2).coerceAtLeast(50f)
val y = height / 2f - (textPaint.descent() + textPaint.ascent()) / 2
canvas.drawText(text, x, y, textPaint)
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (!canvas.isHardwareAccelerated) {
// 软件渲染回退
drawContent(canvas)
return
}
// ✅ 绘制预渲染的内容
currentBitmap?.let { bitmap ->
canvas.drawBitmap(bitmap, 0f, 0f, null)
}
// ✅ 使用RenderNode绘制
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
renderNode?.let { node ->
canvas.drawRenderNode(node)
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
// ✅ 清理资源
animator.cancel()
asyncExecutor.shutdown()
currentBitmap?.recycle()
renderNode = null
// ✅ 停止渲染线程
renderThread.quit()
}
@RequiresApi(Build.VERSION_CODES.P)
private inner class RenderThread : HandlerThread("RenderThread") {
private lateinit var handler: Handler
init {
start()
handler = Handler(looper)
}
fun render(frame: Long, callback: Runnable) {
handler.postAtTime(callback, frame)
}
fun quit() {
looper.quitSafely()
}
}
}
3.4 HighPerformanceProgressView.kt (综合优化版)
package com.example.performance.view
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.os.*
import android.util.AttributeSet
import android.view.Choreographer
import android.view.View
import androidx.annotation.RequiresApi
import com.example.performance.utils.BitmapManager
import kotlin.math.max
import kotlin.math.min
class HighPerformanceProgressView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr), Choreographer.FrameCallback {
// ========== 优化配置 ==========
companion object {
// ✅ 静态Paint对象,避免重复创建
private val PROGRESS_PAINT = Paint().apply {
flags = Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG or Paint.FILTER_BITMAP_FLAG
style = Paint.Style.FILL
color = Color.parseColor("#2196F3")
}
private val TEXT_PAINT = Paint().apply {
flags = Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG
color = Color.WHITE
textSize = 36f
textAlign = Paint.Align.CENTER
typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
}
private val BG_PAINT = Paint().apply {
flags = Paint.ANTI_ALIAS_FLAG
color = Color.parseColor("#F5F5F5")
style = Paint.Style.FILL
}
private val BORDER_PAINT = Paint().apply {
flags = Paint.ANTI_ALIAS_FLAG
color = Color.parseColor("#E0E0E0")
style = Paint.Style.STROKE
strokeWidth = 2f
}
}
// ========== 状态管理 ==========
private var currentProgress = 0f
private var animatedProgress = 0f
private var targetProgress = 0f
private var isAnimating = false
// ========== 对象复用池 ==========
private val rectPool = mutableListOf<RectF>()
private val pathPool = mutableListOf<Path>()
private fun obtainRectF(): RectF {
return if (rectPool.isNotEmpty()) {
rectPool.removeAt(0).also { it.setEmpty() }
} else {
RectF()
}
}
private fun recycleRectF(rect: RectF) {
if (rectPool.size < 5) { // 限制池大小
rectPool.add(rect)
}
}
// ========== 缓存管理 ==========
private var cachedWidth = 0
private var cachedHeight = 0
private var gradientShader: LinearGradient? = null
private var lastShaderUpdateTime = 0L
// ========== 路径预计算 ==========
private val cornerPath = Path()
private val clipPath = Path()
private var pathsDirty = true
// ========== 文本缓存 ==========
private val textBounds = Rect()
private var cachedText = ""
private var cachedTextWidth = 0f
// ========== 动画系统 ==========
private val animator = ValueAnimator().apply {
duration = 250L
interpolator = android.view.animation.AccelerateDecelerateInterpolator()
addUpdateListener {
animatedProgress = it.animatedValue as Float
if (useChoreographerSync) {
scheduleFrame() // 使用Choreographer同步
} else {
invalidateOptimized()
}
}
}
// ========== 高刷新率适配 ==========
private var refreshRate = 60f
private var useChoreographerSync = false
private var choreographer: Choreographer? = null
private var vsyncTime = 0L
// ========== 初始化 ==========
init {
// ✅ 启用硬件加速层
setLayerType(LAYER_TYPE_HARDWARE, null)
// ✅ 获取设备刷新率
val display = context.getSystemService(Context.DISPLAY_SERVICE) as? android.view.Display
refreshRate = display?.refreshRate ?: 60f
// ✅ 根据刷新率调整动画时长
if (refreshRate > 60) {
useChoreographerSync = true
choreographer = Choreographer.getInstance()
animator.duration = (1000 * 60f / refreshRate).toLong()
}
// ✅ 标记无重叠渲染
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
forceHasOverlappingRendering(false)
}
// ✅ 禁用默认的clipChildren
clipChildren = false
// ✅ 初始化渐变
updateGradientShader()
}
// ========== 尺寸处理 ==========
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (w != cachedWidth || h != cachedHeight) {
cachedWidth = w
cachedHeight = h
pathsDirty = true
updateGradientShader()
}
}
private fun updateGradientShader() {
if (cachedWidth <= 0) return
val now = System.currentTimeMillis()
// ✅ 限制Shader更新频率(至少间隔500ms)
if (now - lastShaderUpdateTime > 500) {
gradientShader = LinearGradient(
0f, 0f, cachedWidth.toFloat(), 0f,
intArrayOf(
Color.parseColor("#FF6B6B"),
Color.parseColor("#FFD166"),
Color.parseColor("#06D6A0"),
Color.parseColor("#118AB2")
),
floatArrayOf(0f, 0.3f, 0.6f, 1f),
Shader.TileMode.CLAMP
).apply {
setLocalMatrix(Matrix().apply {
setScale(1.5f, 1f) // 扩大渐变范围,避免动画时重复创建
})
}
PROGRESS_PAINT.shader = gradientShader
lastShaderUpdateTime = now
}
}
// ========== 绘制优化 ==========
override fun onDraw(canvas: Canvas) {
if (!canvas.isHardwareAccelerated) {
drawSoftwareFallback(canvas)
return
}
// ✅ 1. 预计算路径(惰性计算)
if (pathsDirty) {
updatePaths()
pathsDirty = false
}
// ✅ 2. 使用离屏缓存(针对复杂效果)
if (shouldUseOffscreenCache()) {
drawWithOffscreenCache(canvas)
return
}
// ✅ 3. 直接硬件加速绘制
drawHardwareAccelerated(canvas)
}
private fun updatePaths() {
cornerPath.reset()
clipPath.reset()
val cornerRadius = min(cachedHeight / 2f, 24f)
// 圆角路径
cornerPath.addRoundRect(
0f, 0f, cachedWidth.toFloat(), cachedHeight.toFloat(),
cornerRadius, cornerRadius, Path.Direction.CW
)
// 裁剪路径(用于进度条)
clipPath.addRect(0f, 0f, cachedWidth.toFloat(), cachedHeight.toFloat(), Path.Direction.CW)
}
private fun shouldUseOffscreenCache(): Boolean {
// 在需要复杂混合效果时使用离屏缓存
return PROGRESS_PAINT.xfermode != null ||
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
}
private fun drawWithOffscreenCache(canvas: Canvas) {
// 使用BitmapManager获取缓存Bitmap
val bitmap = BitmapManager.getOrCreateBitmap(
"progress_cache_${cachedWidth}x${cachedHeight}",
cachedWidth, cachedHeight
)
val offscreenCanvas = Canvas(bitmap)
// 绘制到离屏Bitmap
drawToCanvas(offscreenCanvas)
// 绘制到实际Canvas
canvas.drawBitmap(bitmap, 0f, 0f, null)
}
private fun drawHardwareAccelerated(canvas: Canvas) {
// ✅ 保存画布状态
canvas.save()
// ✅ 使用clipPath避免过度绘制
canvas.clipPath(cornerPath)
// ✅ 绘制背景
canvas.drawRoundRect(
0f, 0f, cachedWidth.toFloat(), cachedHeight.toFloat(),
12f, 12f, BG_PAINT
)
// ✅ 绘制边框
canvas.drawRoundRect(
0f, 0f, cachedWidth.toFloat(), cachedHeight.toFloat(),
12f, 12f, BORDER_PAINT
)
// ✅ 绘制进度条
if (animatedProgress > 0.001f) {
val progressWidth = cachedWidth * animatedProgress
// 使用对象池获取RectF
val progressRect = obtainRectF().apply {
set(0f, 0f, progressWidth, cachedHeight.toFloat())
}
canvas.drawRoundRect(progressRect, 12f, 12f, PROGRESS_PAINT)
// ✅ 绘制文本(带缓存)
drawOptimizedText(canvas, progressWidth)
// 归还对象到池中
recycleRectF(progressRect)
}
canvas.restore()
}
private fun drawOptimizedText(canvas: Canvas, progressWidth: Float) {
val text = "${(animatedProgress * 100).toInt()}%"
// ✅ 文本缓存优化
if (text != cachedText) {
TEXT_PAINT.getTextBounds(text, 0, text.length, textBounds)
cachedText = text
cachedTextWidth = TEXT_PAINT.measureText(text)
}
// ✅ 计算文本位置
val textX = max(progressWidth / 2, cachedTextWidth / 2 + 16)
val textY = cachedHeight / 2f - (textBounds.top + textBounds.bottom) / 2f
// ✅ 文本阴影效果(硬件加速友好)
if (progressWidth > cachedTextWidth + 32) {
// 只在有足够空间时绘制阴影
TEXT_PAINT.setShadowLayer(4f, 2f, 2f, Color.parseColor("#66000000"))
canvas.drawText(text, textX, textY, TEXT_PAINT)
TEXT_PAINT.clearShadowLayer()
} else {
canvas.drawText(text, textX, textY, TEXT_PAINT)
}
}
private fun drawSoftwareFallback(canvas: Canvas) {
// 软件渲染的简单实现
canvas.drawRoundRect(
0f, 0f, cachedWidth.toFloat(), cachedHeight.toFloat(),
12f, 12f, BG_PAINT
)
if (animatedProgress > 0) {
canvas.drawRoundRect(
0f, 0f, cachedWidth * animatedProgress, cachedHeight.toFloat(),
12f, 12f, PROGRESS_PAINT
)
}
}
private fun drawToCanvas(canvas: Canvas) {
// 通用的绘制逻辑
drawHardwareAccelerated(canvas)
}
// ========== 动画控制 ==========
fun setProgress(progress: Float, animate: Boolean = true) {
targetProgress = progress.coerceIn(0f, 1f)
if (!animate || !useChoreographerSync) {
currentProgress = targetProgress
animatedProgress = targetProgress
invalidateOptimized()
return
}
if (isAnimating) {
animator.cancel()
}
animator.setFloatValues(animatedProgress, targetProgress)
animator.start()
isAnimating = true
}
// ========== 高刷新率同步 ==========
private fun scheduleFrame() {
choreographer?.postFrameCallback(this)
}
override fun doFrame(frameTimeNanos: Long) {
val frameTimeMillis = frameTimeNanos / 1_000_000
// ✅ 基于垂直同步的时间差计算
val delta = (frameTimeMillis - vsyncTime).coerceAtLeast(16L)
vsyncTime = frameTimeMillis
// ✅ 更新动画进度(基于时间插值)
if (isAnimating) {
val fraction = animator.animatedFraction
animatedProgress = currentProgress + (targetProgress - currentProgress) * fraction
if (fraction >= 1f) {
isAnimating = false
currentProgress = targetProgress
choreographer?.removeFrameCallback(this)
} else {
choreographer?.postFrameCallback(this)
}
invalidateOptimized()
}
}
// ========== 刷新优化 ==========
private fun invalidateOptimized() {
// ✅ 计算脏矩形,只刷新需要更新的区域
val progressWidth = cachedWidth * animatedProgress
val dirtyLeft = (progressWidth - 50).toInt().coerceAtLeast(0)
val dirtyRight = (progressWidth + 50).toInt().coerceAtMost(cachedWidth)
invalidate(dirtyLeft, 0, dirtyRight, cachedHeight)
}
// ========== 内存管理 ==========
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
// ✅ 停止所有动画
animator.cancel()
choreographer?.removeFrameCallback(this)
// ✅ 清理渐变
gradientShader = null
PROGRESS_PAINT.shader = null
// ✅ 清理对象池
rectPool.clear()
pathPool.clear()
// ✅ 通知BitmapManager清理相关缓存
BitmapManager.removeCache("progress_cache_${cachedWidth}x${cachedHeight}")
}
// ========== 性能监控 ==========
private var drawStartTime = 0L
override fun dispatchDraw(canvas: Canvas?) {
drawStartTime = System.nanoTime()
super.dispatchDraw(canvas)
val drawTime = (System.nanoTime() - drawStartTime) / 1_000_000f
// 监控绘制时间(生产环境应该移除)
if (drawTime > 16) {
// 绘制时间过长警告
android.util.Log.w("Performance", "HighPerformanceProgressView draw time: ${drawTime}ms")
}
}
}
4. 性能监控工具
4.1 PerformanceMonitor.kt
package com.example.performance.utils
import android.app.Activity
import android.app.ActivityManager
import android.content.Context
import android.os.Build
import android.os.Debug
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Choreographer
import kotlin.math.roundToInt
data class PerformanceInfo(
val fps: Float,
val usedMemoryMB: Float,
val totalMemoryMB: Float,
val cpuUsage: Float,
val gpuTimeMs: Float,
val jankCount: Int,
val frameTime95th: Float
)
object PerformanceMonitor : Choreographer.FrameCallback {
private var isMonitoring = false
private var frameCount = 0
private var lastFrameTime = 0L
private var frameTimes = mutableListOf<Long>()
private val handler = Handler(Looper.getMainLooper())
private var activity: Activity? = null
private var jankThreshold = 16666667L // 16.67ms in nanoseconds
private var jankCount = 0
private val frameTimeHistory = ArrayDeque<Long>()
private const val HISTORY_SIZE = 240 // 4秒数据(按60fps)
// 性能数据回调
var onPerformanceUpdate: ((PerformanceInfo) -> Unit)? = null
fun startMonitoring(activity: Activity) {
if (isMonitoring) return
this.activity = activity
isMonitoring = true
frameCount = 0
lastFrameTime = System.nanoTime()
frameTimes.clear()
frameTimeHistory.clear()
jankCount = 0
// 根据刷新率调整阈值
val display = activity.getSystemService(Context.DISPLAY_SERVICE)
as android.view.Display
val refreshRate = display.refreshRate
jankThreshold = (1_000_000_000L / refreshRate).toLong()
// 开始监控
Choreographer.getInstance().postFrameCallback(this)
Log.i("PerformanceMonitor", "Started monitoring with refresh rate: ${refreshRate}Hz")
}
fun stopMonitoring() {
isMonitoring = false
Choreographer.getInstance().removeFrameCallback(this)
activity = null
Log.i("PerformanceMonitor", "Stopped monitoring")
}
override fun doFrame(frameTimeNanos: Long) {
if (!isMonitoring) return
// 计算帧时间
val frameTime = frameTimeNanos - lastFrameTime
lastFrameTime = frameTimeNanos
// 记录帧时间
frameTimes.add(frameTime)
frameTimeHistory.addLast(frameTime)
if (frameTimeHistory.size > HISTORY_SIZE) {
frameTimeHistory.removeFirst()
}
// 检测卡顿
if (frameTime > jankThreshold * 2) {
jankCount++
Log.w("PerformanceMonitor", "Jank detected: ${frameTime / 1_000_000}ms")
}
frameCount++
// 每秒计算一次FPS
if (frameCount >= 60) { // 大约每秒(按60fps)
calculatePerformance()
frameCount = 0
frameTimes.clear()
}
// 继续监控下一帧
Choreographer.getInstance().postFrameCallback(this)
}
private fun calculatePerformance() {
val ctx = activity ?: return
// 计算FPS
val fps = if (frameTimes.isNotEmpty()) {
val avgFrameTime = frameTimes.average()
1_000_000_000.0 / avgFrameTime
} else {
0.0
}
// 获取内存使用
val memoryInfo = ActivityManager.MemoryInfo()
val activityManager = ctx.getSystemService(Context.ACTIVITY_SERVICE)
as ActivityManager
activityManager.getMemoryInfo(memoryInfo)
val usedMemory = Runtime.getRuntime().totalMemory() -
Runtime.getRuntime().freeMemory()
val usedMemoryMB = usedMemory / (1024.0 * 1024.0)
val totalMemoryMB = Runtime.getRuntime().totalMemory() / (1024.0 * 1024.0)
// 计算CPU使用率(简化版)
val cpuUsage = calculateCpuUsage()
// 计算GPU时间(估算)
val gpuTimeMs = estimateGpuTime()
// 计算95%帧时间
val sortedTimes = frameTimeHistory.sorted()
val index95th = (sortedTimes.size * 0.95).toInt()
val frameTime95th = if (sortedTimes.isNotEmpty() && index95th < sortedTimes.size) {
sortedTimes[index95th] / 1_000_000.0f
} else {
0f
}
val info = PerformanceInfo(
fps = fps.toFloat(),
usedMemoryMB = usedMemoryMB.toFloat(),
totalMemoryMB = totalMemoryMB.toFloat(),
cpuUsage = cpuUsage,
gpuTimeMs = gpuTimeMs,
jankCount = jankCount,
frameTime95th = frameTime95th
)
// 回调更新
handler.post {
onPerformanceUpdate?.invoke(info)
}
}
private fun calculateCpuUsage(): Float {
return try {
// 读取/proc/stat获取CPU使用率
val reader = java.io.BufferedReader(
java.io.InputStreamReader(
java.lang.ProcessBuilder("top", "-n", "1").start().inputStream
)
)
var cpuUsage = 0f
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.contains("CPU") == true) {
val parts = line!!.split("\\s+".toRegex())
if (parts.size > 8) {
val user = parts[2].toFloatOrNull() ?: 0f
val system = parts[4].toFloatOrNull() ?: 0f
cpuUsage = user + system
break
}
}
}
reader.close()
cpuUsage
} catch (e: Exception) {
Log.e("PerformanceMonitor", "Error calculating CPU usage", e)
0f
}
}
private fun estimateGpuTime(): Float {
// 简化估算:基于帧时间和CPU使用率
if (frameTimeHistory.isEmpty()) return 0f
val avgFrameTime = frameTimeHistory.average()
val cpuTime = avgFrameTime * 0.4 // 假设40%是CPU时间
val gpuTime = avgFrameTime - cpuTime
return (gpuTime / 1_000_000).toFloat().coerceAtLeast(0f)
}
fun getPerformanceInfo(): PerformanceInfo {
val ctx = activity ?: return PerformanceInfo(0f, 0f, 0f, 0f, 0f, 0, 0f)
// 实时计算当前性能数据
val currentFps = if (frameTimes.isNotEmpty()) {
val avgFrameTime = frameTimes.average()
(1_000_000_000.0 / avgFrameTime).toFloat()
} else {
0f
}
val usedMemory = Runtime.getRuntime().totalMemory() -
Runtime.getRuntime().freeMemory()
val usedMemoryMB = usedMemory / (1024.0f * 1024.0f)
return PerformanceInfo(
fps = currentFps,
usedMemoryMB = usedMemoryMB,
totalMemoryMB = Runtime.getRuntime().totalMemory() / (1024.0f * 1024.0f),
cpuUsage = calculateCpuUsage(),
gpuTimeMs = estimateGpuTime(),
jankCount = jankCount,
frameTime95th = 0f // 需要完整历史数据
)
}
fun resetJankCount() {
jankCount = 0
}
}
4.2 BitmapManager.kt
package com.example.performance.utils
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import android.util.LruCache
import android.util.Size
import kotlin.math.roundToInt
object BitmapManager {
// 内存缓存(使用LRU策略)
private val memoryCache: LruCache<String, Bitmap>
// 尺寸缓存池
private val sizeCache = mutableMapOf<Size, MutableList<Bitmap>>()
init {
// 分配最大内存的1/8作为图片缓存
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
val cacheSize = maxMemory / 8
memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
// 返回Bitmap占用的KB数
return bitmap.allocationByteCount / 1024
}
override fun entryRemoved(
evicted: Boolean,
key: String,
oldValue: Bitmap,
newValue: Bitmap?
) {
// 被移除时尝试放入尺寸缓存池
if (evicted && !oldValue.isRecycled) {
cacheBitmapBySize(oldValue)
}
}
}
}
/**
* 加载或获取缓存的Bitmap
*/
fun getOrCreateBitmap(key: String, width: Int, height: Int): Bitmap {
// 1. 尝试从内存缓存获取
memoryCache.get(key)?.let { cachedBitmap ->
if (!cachedBitmap.isRecycled &&
cachedBitmap.width == width &&
cachedBitmap.height == height) {
return cachedBitmap
}
}
// 2. 尝试从尺寸缓存池获取
val size = Size(width, height)
val pool = sizeCache[size]
pool?.let {
synchronized(it) {
if (it.isNotEmpty()) {
val bitmap = it.removeAt(0)
if (!bitmap.isRecycled) {
// 清空Bitmap内容
bitmap.eraseColor(android.graphics.Color.TRANSPARENT)
return bitmap
}
}
}
}
// 3. 创建新的Bitmap
return createOptimizedBitmap(width, height).also { bitmap ->
memoryCache.put(key, bitmap)
}
}
/**
* 创建经过优化的Bitmap
*/
private fun createOptimizedBitmap(width: Int, height: Int): Bitmap {
// 根据需求选择合适的配置
val config = when {
// 需要透明度
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ->
Bitmap.Config.ARGB_8888
// 不需要透明度且节省内存
else -> Bitmap.Config.RGB_565
}
return try {
Bitmap.createBitmap(width, height, config)
} catch (e: OutOfMemoryError) {
// 内存不足时尝试清理缓存
clearCache()
// 尝试使用更小的配置
val fallbackConfig = Bitmap.Config.RGB_565
Bitmap.createBitmap(width, height, fallbackConfig)
}
}
/**
* 根据尺寸缓存Bitmap
*/
private fun cacheBitmapBySize(bitmap: Bitmap) {
if (bitmap.isRecycled) return
val size = Size(bitmap.width, bitmap.height)
val pool = sizeCache.getOrPut(size) { mutableListOf() }
synchronized(pool) {
// 限制池大小,避免占用过多内存
if (pool.size < 3) {
pool.add(bitmap)
} else {
// 池已满,回收Bitmap
bitmap.recycle()
}
}
}
/**
* 解码资源图片并优化
*/
fun decodeResource(resId: Int, reqWidth: Int, reqHeight: Int): Bitmap {
val key = "res_${resId}_${reqWidth}x${reqHeight}"
// 检查缓存
memoryCache.get(key)?.let { return it }
val options = BitmapFactory.Options().apply {
// 只读取尺寸信息
inJustDecodeBounds = true
BitmapFactory.decodeResource(ResourcesProvider.resources, resId, this)
// 计算采样率
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
// 配置解码参数
inJustDecodeBounds = false
inPreferredConfig = Bitmap.Config.RGB_565
inDither = true
inScaled = true
// Android 4.4+ 启用位图复用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
inMutable = true
// 尝试从缓存中获取可复用的Bitmap
val reusableBitmap = findReusableBitmap(reqWidth, reqHeight)
reusableBitmap?.let { inBitmap = it }
}
}
return try {
BitmapFactory.decodeResource(ResourcesProvider.resources, resId, options).also {
memoryCache.put(key, it)
}
} catch (e: IllegalArgumentException) {
// inBitmap复用失败,不使用inBitmap重试
options.inBitmap = null
BitmapFactory.decodeResource(ResourcesProvider.resources, resId, options).also {
memoryCache.put(key, it)
}
}
}
/**
* 计算合适的采样率
*/
private fun calculateInSampleSize(
options: BitmapFactory.Options,
reqWidth: Int,
reqHeight: Int
): Int {
val (width, height) = options.run { outWidth to outHeight }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while (halfHeight / inSampleSize >= reqHeight &&
halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
// 针对高分辨率屏幕进一步优化
val density = ResourcesProvider.resources.displayMetrics.densityDpi
if (density > 320 && inSampleSize == 1) {
inSampleSize = 2
}
return inSampleSize
}
/**
* 查找可复用的Bitmap
*/
private fun findReusableBitmap(reqWidth: Int, reqHeight: Int): Bitmap? {
// 1. 从尺寸缓存池查找
val size = Size(reqWidth, reqHeight)
val pool = sizeCache[size]
pool?.let {
synchronized(it) {
if (it.isNotEmpty()) {
val bitmap = it.removeAt(0)
if (!bitmap.isRecycled &&
bitmap.width >= reqWidth &&
bitmap.height >= reqHeight) {
return bitmap
}
}
}
}
// 2. 从LRU缓存中查找(需要满足inBitmap的条件)
memoryCache.snapshot().forEach { (_, bitmap) ->
if (!bitmap.isRecycled &&
bitmap.isMutable &&
bitmap.config != Bitmap.Config.HARDWARE &&
bitmap.width >= reqWidth &&
bitmap.height >= reqHeight) {
return bitmap
}
}
return null
}
/**
* 清理所有缓存
*/
fun clearCache() {
memoryCache.evictAll()
sizeCache.values.forEach { pool ->
synchronized(pool) {
pool.forEach { it.recycle() }
pool.clear()
}
}
sizeCache.clear()
// 建议系统进行垃圾回收
System.gc()
}
/**
* 移除特定缓存
*/
fun removeCache(key: String) {
memoryCache.remove(key)
}
/**
* 获取缓存统计信息
*/
fun getCacheStats(): String {
val hitCount = memoryCache.hitCount()
val missCount = memoryCache.missCount()
val hitRate = if (hitCount + missCount > 0) {
hitCount.toFloat() / (hitCount + missCount) * 100
} else {
0f
}
val totalSize = memoryCache.size()
val maxSize = memoryCache.maxSize()
val poolSizes = sizeCache.values.sumOf { it.size }
return """
内存缓存: ${totalSize}KB / ${maxSize}KB
命中率: ${"%.1f".format(hitRate)}% (${hitCount}/${hitCount + missCount})
尺寸缓存池: ${poolSizes}个
""".trimIndent()
}
/**
* 资源提供者(避免传递Context)
*/
object ResourcesProvider {
lateinit var resources: android.content.res.Resources
lateinit var context: android.content.Context
fun initialize(context: android.content.Context) {
this.context = context
this.resources = context.resources
}
}
}
4.3 FrameRateTracker.kt
package com.example.performance.utils
import android.os.Handler
import android.os.Looper
import android.view.Choreographer
class FrameRateTracker {
interface FrameRateListener {
fun onFrameRateUpdate(fps: Float, droppedFrames: Int)
fun onJankDetected(frameTimeMs: Long)
}
private var choreographer: Choreographer? = null
private var frameCallback: Choreographer.FrameCallback? = null
private var isTracking = false
private var frameCount = 0
private var lastFrameTime = 0L
private var startTime = 0L
private var droppedFrames = 0
private val frameTimes = mutableListOf<Long>()
private var listener: FrameRateListener? = null
private val handler = Handler(Looper.getMainLooper())
private val reportRunnable = Runnable { reportFrameRate() }
// 配置
var reportInterval = 1000L // 报告间隔(毫秒)
var jankThreshold = 16666667L // 卡顿阈值(纳秒),16.67ms
fun startTracking() {
if (isTracking) return
isTracking = true
frameCount = 0
droppedFrames = 0
lastFrameTime = 0
startTime = System.nanoTime()
frameTimes.clear()
choreographer = Choreographer.getInstance()
frameCallback = object : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
onFrame(frameTimeNanos)
choreographer?.postFrameCallback(this)
}
}
choreographer?.postFrameCallback(frameCallback!!)
handler.postDelayed(reportRunnable, reportInterval)
android.util.Log.d("FrameRateTracker", "Started tracking")
}
fun stopTracking() {
if (!isTracking) return
isTracking = false
frameCallback?.let { choreographer?.removeFrameCallback(it) }
handler.removeCallbacks(reportRunnable)
frameCallback = null
choreographer = null
reportFrameRate() // 最终报告
android.util.Log.d("FrameRateTracker", "Stopped tracking")
}
private fun onFrame(frameTimeNanos: Long) {
if (lastFrameTime > 0) {
val frameTime = frameTimeNanos - lastFrameTime
frameTimes.add(frameTime)
// 检测掉帧
if (frameTime > jankThreshold * 1.5) {
val dropped = ((frameTime - jankThreshold) / jankThreshold).toInt()
droppedFrames += dropped.coerceAtLeast(1)
// 报告卡顿
if (frameTime > jankThreshold * 2) {
listener?.onJankDetected(frameTime / 1_000_000)
}
}
}
lastFrameTime = frameTimeNanos
frameCount++
}
private fun reportFrameRate() {
if (!isTracking) return
val elapsed = (System.nanoTime() - startTime) / 1_000_000_000.0
val fps = if (elapsed > 0) frameCount / elapsed else 0.0
// 计算帧时间统计数据
val stats = calculateFrameTimeStats()
listener?.onFrameRateUpdate(fps.toFloat(), droppedFrames)
// 重置计数器
frameCount = 0
droppedFrames = 0
startTime = System.nanoTime()
frameTimes.clear()
// 安排下一次报告
handler.postDelayed(reportRunnable, reportInterval)
}
private fun calculateFrameTimeStats(): FrameTimeStats {
if (frameTimes.isEmpty()) return FrameTimeStats()
val sorted = frameTimes.sorted()
val average = frameTimes.average()
val p50 = sorted[sorted.size / 2]
val p90 = sorted[(sorted.size * 0.9).toInt()]
val p95 = sorted[(sorted.size * 0.95).toInt()]
val p99 = sorted[(sorted.size * 0.99).toInt()]
return FrameTimeStats(
average = average,
p50 = p50,
p90 = p90,
p95 = p95,
p99 = p99
)
}
fun setFrameRateListener(listener: FrameRateListener) {
this.listener = listener
}
data class FrameTimeStats(
val average: Double = 0.0,
val p50: Long = 0,
val p90: Long = 0,
val p95: Long = 0,
val p99: Long = 0
) {
override fun toString(): String {
return """
Average: ${(average / 1_000_000).format(2)}ms
P50: ${(p50 / 1_000_000).format(2)}ms
P90: ${(p90 / 1_000_000).format(2)}ms
P95: ${(p95 / 1_000_000).format(2)}ms
P99: ${(p99 / 1_000_000).format(2)}ms
""".trimIndent()
}
private fun Double.format(digits: Int) = "%.${digits}f".format(this)
private fun Long.format(digits: Int) = "%.${digits}f".format(this / 1_000_000.0)
}
}
5. 性能测试页面
5.1 BenchmarkActivity.kt
package com.example.performance.performance
import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.performance.R
import com.example.performance.utils.FrameRateTracker
import com.example.performance.utils.PerformanceMonitor
import kotlin.math.roundToInt
class BenchmarkActivity : AppCompatActivity(), FrameRateTracker.FrameRateListener {
private lateinit var tvResults: TextView
private lateinit var btnStartTest: Button
private lateinit var btnStopTest: Button
private val frameRateTracker = FrameRateTracker()
private val handler = Handler(Looper.getMainLooper())
private var isTesting = false
private var testStartTime = 0L
// 测试结果
private val fpsHistory = mutableListOf<Float>()
private val memoryHistory = mutableListOf<Float>()
private var totalJanks = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_benchmark)
initViews()
setupFrameRateTracker()
}
private fun initViews() {
tvResults = findViewById(R.id.tvResults)
btnStartTest = findViewById(R.id.btnStartTest)
btnStopTest = findViewById(R.id.btnStopTest)
btnStartTest.setOnClickListener { startBenchmark() }
btnStopTest.setOnClickListener { stopBenchmark() }
// 初始化PerformanceMonitor
PerformanceMonitor.ResourcesProvider.initialize(this)
PerformanceMonitor.startMonitoring(this)
}
private fun setupFrameRateTracker() {
frameRateTracker.reportInterval = 500L
frameRateTracker.jankThreshold = 16666667L // 60fps阈值
frameRateTracker.setFrameRateListener(this)
}
@SuppressLint("SetTextI18n")
private fun startBenchmark() {
if (isTesting) return
isTesting = true
testStartTime = System.currentTimeMillis()
fpsHistory.clear()
memoryHistory.clear()
totalJanks = 0
frameRateTracker.startTracking()
// 开始收集性能数据
handler.post(object : Runnable {
override fun run() {
if (isTesting) {
val info = PerformanceMonitor.getPerformanceInfo()
memoryHistory.add(info.usedMemoryMB)
// 更新UI
updateResults()
handler.postDelayed(this, 1000)
}
}
})
btnStartTest.isEnabled = false
btnStopTest.isEnabled = true
tvResults.text = "性能测试开始...\n"
}
private fun stopBenchmark() {
if (!isTesting) return
isTesting = false
frameRateTracker.stopTracking()
btnStartTest.isEnabled = true
btnStopTest.isEnabled = false
// 生成最终报告
generateFinalReport()
}
@SuppressLint("SetTextI18n")
private fun updateResults() {
val duration = (System.currentTimeMillis() - testStartTime) / 1000
val currentFps = fpsHistory.lastOrNull() ?: 0f
val currentMemory = memoryHistory.lastOrNull() ?: 0f
val results = """
测试时间: ${duration}秒
当前FPS: ${"%.1f".format(currentFps)}
当前内存: ${"%.1f".format(currentMemory)}MB
卡顿次数: ${totalJanks}
平均FPS: ${calculateAverageFps()}
最大内存: ${"%.1f".format(memoryHistory.maxOrNull() ?: 0f)}MB
""".trimIndent()
tvResults.text = results
}
private fun calculateAverageFps(): String {
return if (fpsHistory.isNotEmpty()) {
"%.1f".format(fpsHistory.average())
} else {
"0.0"
}
}
@SuppressLint("SetTextI18n")
private fun generateFinalReport() {
if (fpsHistory.isEmpty() || memoryHistory.isEmpty()) return
val avgFps = fpsHistory.average()
val minFps = fpsHistory.minOrNull() ?: 0f
val maxFps = fpsHistory.maxOrNull() ?: 0f
val avgMemory = memoryHistory.average()
val minMemory = memoryHistory.minOrNull() ?: 0f
val maxMemory = memoryHistory.maxOrNull() ?: 0f
val duration = (System.currentTimeMillis() - testStartTime) / 1000
val report = """
====== 性能测试报告 ======
测试时长: ${duration}秒
🎯 帧率表现:
平均FPS: ${"%.1f".format(avgFps)}
最低FPS: ${"%.1f".format(minFps)}
最高FPS: ${"%.1f".format(maxFps)}
卡顿次数: ${totalJanks}
💾 内存表现:
平均内存: ${"%.1f".format(avgMemory)}MB
最低内存: ${"%.1f".format(minMemory)}MB
最高内存: ${"%.1f".format(maxMemory)}MB
📊 性能评级:
${getPerformanceRating(avgFps, totalJanks)}
💡 优化建议:
${getOptimizationSuggestions(avgFps, totalJanks, maxMemory)}
""".trimIndent()
tvResults.text = report
}
private fun getPerformanceRating(avgFps: Double, janks: Int): String {
return when {
avgFps >= 55 && janks < 5 -> "✅ 优秀 (流畅)"
avgFps >= 45 && janks < 10 -> "⚠️ 良好 (基本流畅)"
avgFps >= 30 && janks < 20 -> "⚠️ 一般 (偶有卡顿)"
else -> "❌ 较差 (需要优化)"
}
}
private fun getOptimizationSuggestions(
avgFps: Double,
janks: Int,
maxMemory: Float
): String {
val suggestions = mutableListOf<String>()
if (avgFps < 45) {
suggestions.add("- 检查过度绘制,减少不必要的View层级")
suggestions.add("- 启用硬件加速,优化绘制操作")
suggestions.add("- 使用异步绘制或RenderThread")
}
if (janks > 10) {
suggestions.add("- 避免在主线程进行耗时操作")
suggestions.add("- 优化动画,使用ValueAnimator替代Thread")
suggestions.add("- 减少每帧的绘制内容")
}
if (maxMemory > 100) {
suggestions.add("- 优化Bitmap内存使用")
suggestions.add("- 及时回收不再使用的资源")
suggestions.add("- 使用内存缓存和对象池")
}
if (suggestions.isEmpty()) {
suggestions.add("✅ 当前性能表现良好,继续保持!")
}
return suggestions.joinToString("\n")
}
// FrameRateListener实现
override fun onFrameRateUpdate(fps: Float, droppedFrames: Int) {
fpsHistory.add(fps)
totalJanks += droppedFrames
}
override fun onJankDetected(frameTimeMs: Long) {
// 记录详细卡顿信息
android.util.Log.w("Benchmark", "Jank detected: ${frameTimeMs}ms")
}
override fun onDestroy() {
super.onDestroy()
stopBenchmark()
PerformanceMonitor.stopMonitoring()
}
}
5.2 PerformancePanel.kt (自定义性能面板View)
package com.example.performance.performance
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.example.performance.utils.PerformanceMonitor
import kotlin.math.max
import kotlin.math.min
class PerformancePanel @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// 绘制配置
private val fpsPaint = Paint().apply {
color = Color.WHITE
textSize = 32f
typeface = Typeface.MONOSPACE
isAntiAlias = true
}
private val memoryPaint = Paint().apply {
color = Color.parseColor("#4CAF50")
style = Paint.Style.FILL
isAntiAlias = true
}
private val cpuPaint = Paint().apply {
color = Color.parseColor("#2196F3")
style = Paint.Style.FILL
isAntiAlias = true
}
private val gpuPaint = Paint().apply {
color = Color.parseColor("#FF9800")
style = Paint.Style.FILL
isAntiAlias = true
}
private val gridPaint = Paint().apply {
color = Color.parseColor("#444444")
style = Paint.Style.STROKE
strokeWidth = 1f
isAntiAlias = true
}
private val bgPaint = Paint().apply {
color = Color.parseColor("#22000000")
style = Paint.Style.FILL
isAntiAlias = true
}
// 数据存储
private val fpsHistory = FloatArray(100)
private val memoryHistory = FloatArray(100)
private val cpuHistory = FloatArray(100)
private val gpuHistory = FloatArray(100)
private var historyIndex = 0
private var historySize = 0
// 性能阈值
private val fpsThresholds = arrayOf(30f, 45f, 55f)
private val memoryThresholds = arrayOf(50f, 100f, 200f)
private val cpuThresholds = arrayOf(30f, 60f, 80f)
private val gpuThresholds = arrayOf(8.3f, 16.6f, 33.3f) // ms
init {
// 注册性能监听
PerformanceMonitor.onPerformanceUpdate = { info ->
synchronized(this) {
fpsHistory[historyIndex] = info.fps
memoryHistory[historyIndex] = info.usedMemoryMB
cpuHistory[historyIndex] = info.cpuUsage
gpuHistory[historyIndex] = info.gpuTimeMs
historyIndex = (historyIndex + 1) % fpsHistory.size
historySize = min(historySize + 1, fpsHistory.size)
postInvalidate()
}
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 绘制背景
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), bgPaint)
// 绘制网格
drawGrid(canvas)
// 绘制性能图表
drawPerformanceCharts(canvas)
// 绘制实时数据
drawRealTimeData(canvas)
// 绘制性能评级
drawPerformanceRating(canvas)
}
private fun drawGrid(canvas: Canvas) {
val gridSize = 50f
// 垂直线
var x = 0f
while (x < width) {
canvas.drawLine(x, 0f, x, height.toFloat(), gridPaint)
x += gridSize
}
// 水平线
var y = 0f
while (y < height) {
canvas.drawLine(0f, y, width.toFloat(), y, gridPaint)
y += gridSize
}
}
private fun drawPerformanceCharts(canvas: Canvas) {
if (historySize < 2) return
val chartHeight = height * 0.6f
val chartTop = height * 0.1f
val chartBottom = chartTop + chartHeight
val chartWidth = width.toFloat()
val pointSpacing = chartWidth / (historySize - 1)
// 绘制FPS曲线
drawCurve(canvas, fpsHistory, historySize, pointSpacing, chartTop,
chartBottom, 0f, 120f, fpsPaint)
// 绘制内存曲线
drawCurve(canvas, memoryHistory, historySize, pointSpacing, chartTop,
chartBottom, 0f, 500f, memoryPaint)
// 绘制CPU曲线
drawCurve(canvas, cpuHistory, historySize, pointSpacing, chartTop,
chartBottom, 0f, 100f, cpuPaint)
// 绘制GPU曲线
drawCurve(canvas, gpuHistory, historySize, pointSpacing, chartTop,
chartBottom, 0f, 50f, gpuPaint)
}
private fun drawCurve(
canvas: Canvas,
data: FloatArray,
size: Int,
pointSpacing: Float,
top: Float,
bottom: Float,
minValue: Float,
maxValue: Float,
paint: Paint
) {
val path = Path()
val range = maxValue - minValue
val chartHeight = bottom - top
// 计算第一个点
var lastIndex = (historyIndex - size + data.size) % data.size
var lastValue = data[lastIndex]
var lastY = bottom - ((lastValue - minValue) / range) * chartHeight
path.moveTo(0f, lastY.coerceIn(top, bottom))
// 绘制曲线
for (i in 1 until size) {
val index = (lastIndex + 1) % data.size
val value = data[index]
val x = i * pointSpacing
val y = bottom - ((value - minValue) / range) * chartHeight
// 使用贝塞尔曲线平滑连接
val controlX = x - pointSpacing / 2
path.cubicTo(
controlX, lastY,
controlX, y,
x, y.coerceIn(top, bottom)
)
lastIndex = index
lastValue = value
lastY = y
}
canvas.drawPath(path, paint)
}
private fun drawRealTimeData(canvas: Canvas) {
val currentIndex = (historyIndex - 1 + fpsHistory.size) % fpsHistory.size
val fps = fpsHistory[currentIndex]
val memory = memoryHistory[currentIndex]
val cpu = cpuHistory[currentIndex]
val gpu = gpuHistory[currentIndex]
val textX = 20f
var textY = 50f
val lineHeight = 35f
// FPS
fpsPaint.color = getColorForValue(fps, fpsThresholds,
arrayOf(Color.GREEN, Color.YELLOW, Color.RED))
canvas.drawText("FPS: ${"%.1f".format(fps)}", textX, textY, fpsPaint)
// 内存
textY += lineHeight
fpsPaint.color = getColorForValue(memory, memoryThresholds,
arrayOf(Color.GREEN, Color.YELLOW, Color.RED))
canvas.drawText("内存: ${"%.1f".format(memory)}MB", textX, textY, fpsPaint)
// CPU
textY += lineHeight
fpsPaint.color = getColorForValue(cpu, cpuThresholds,
arrayOf(Color.GREEN, Color.YELLOW, Color.RED))
canvas.drawText("CPU: ${"%.1f".format(cpu)}%", textX, textY, fpsPaint)
// GPU
textY += lineHeight
fpsPaint.color = getColorForValue(gpu, gpuThresholds,
arrayOf(Color.GREEN, Color.YELLOW, Color.RED))
canvas.drawText("GPU: ${"%.1f".format(gpu)}ms", textX, textY, fpsPaint)
}
private fun drawPerformanceRating(canvas: Canvas) {
val currentIndex = (historyIndex - 1 + fpsHistory.size) % fpsHistory.size
val fps = fpsHistory[currentIndex]
val memory = memoryHistory[currentIndex]
val cpu = cpuHistory[currentIndex]
val rating = calculatePerformanceRating(fps, memory, cpu)
val ratingPaint = Paint().apply {
color = rating.color
textSize = 24f
typeface = Typeface.DEFAULT_BOLD
isAntiAlias = true
}
val text = "性能评级: ${rating.text}"
val textWidth = ratingPaint.measureText(text)
val x = width - textWidth - 20
val y = 50f
canvas.drawText(text, x, y, ratingPaint)
}
private fun getColorForValue(
value: Float,
thresholds: Array<Float>,
colors: Array<Int>
): Int {
return when {
value < thresholds[0] -> colors[0]
value < thresholds[1] -> colors[1]
value < thresholds[2] -> colors[1]
else -> colors[2]
}
}
private fun calculatePerformanceRating(
fps: Float,
memory: Float,
cpu: Float
): PerformanceRating {
var score = 0
// FPS评分
score += when {
fps >= 55 -> 3
fps >= 45 -> 2
fps >= 30 -> 1
else -> 0
}
// 内存评分
score += when {
memory < 50 -> 3
memory < 100 -> 2
memory < 200 -> 1
else -> 0
}
// CPU评分
score += when {
cpu < 30 -> 3
cpu < 60 -> 2
cpu < 80 -> 1
else -> 0
}
return when (score) {
in 8..9 -> PerformanceRating("优秀", Color.GREEN)
in 5..7 -> PerformanceRating("良好", Color.YELLOW)
in 3..4 -> PerformanceRating("一般", Color.parseColor("#FF9800"))
else -> PerformanceRating("较差", Color.RED)
}
}
data class PerformanceRating(val text: String, val color: Int)
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
PerformanceMonitor.onPerformanceUpdate = null
}
}
6. 项目配置文件
6.1 build.gradle.kts (模块级)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.performance"
compileSdk = 34
defaultConfig {
applicationId = "com.example.performance"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// 启用多Dex(如果方法数超过限制)
multiDexEnabled = true
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
// 启用R8完整模式
isDebuggable = false
isJniDebuggable = false
isRenderscriptDebuggable = false
}
debug {
// 启用调试功能
isDebuggable = true
isJniDebuggable = true
isRenderscriptDebuggable = true
// 调试版本不进行代码优化,便于性能分析
isMinifyEnabled = false
isShrinkResources = false
// 添加调试配置
applicationIdSuffix = ".debug"
versionNameSuffix = "-DEBUG"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
buildConfig = true
}
// 启用数据绑定
dataBinding {
enable = true
}
// 开启lint检查但不在构建时失败
lint {
abortOnError = false
warningsAsErrors = true
}
}
dependencies {
// AndroidX Core
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
// Material Design
implementation("com.google.android.material:material:1.11.0")
// Constraint Layout
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// CardView
implementation("androidx.cardview:cardview:1.0.0")
// RecyclerView
implementation("androidx.recyclerview:recyclerview:1.3.2")
// Lifecycle
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
// 多Dex支持
implementation("androidx.multidex:multidex:2.0.1")
// 测试依赖
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// 性能监控工具
debugImplementation("androidx.profileinstaller:profileinstaller:1.3.1")
}
6.2 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- 调试权限 -->
<uses-permission android:name="android.permission.DUMP" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<application
android:name=".PerformanceApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PerformanceDemo"
android:hardwareAccelerated="true"
android:largeHeap="true"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboardHidden"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".performance.BenchmarkActivity"
android:exported="false"
android:theme="@style/Theme.PerformanceDemo.NoActionBar" />
<meta-data
android:name="android.max_aspect"
android:value="2.1" />
<meta-data
android:name="android.notch_support"
android:value="true" />
<!-- 启用Profileable(Android 10+) -->
<profileable
android:shell="true"
tools:targetApi="q" />
</application>
</manifest>
6.3 PerformanceApplication.kt
package com.example.performance
import android.app.Application
import android.os.StrictMode
import com.example.performance.utils.BitmapManager
import com.example.performance.utils.PerformanceMonitor
class PerformanceApplication : Application() {
override fun onCreate() {
super.onCreate()
// 初始化BitmapManager
BitmapManager.ResourcesProvider.initialize(this)
// 开发模式下启用严格模式检测
if (BuildConfig.DEBUG) {
enableStrictMode()
}
// 初始化性能监控
initializePerformanceMonitoring()
}
private fun enableStrictMode() {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll() // 检测所有违规
.penaltyLog() // 记录日志
.penaltyDeathOnNetwork() // 禁止网络在主线程
.penaltyFlashScreen() // 屏幕闪烁提示
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.detectLeakedClosableObjects() // 检测未关闭的对象
.detectLeakedRegistrationObjects() // 检测未取消的注册
.detectLeakedSqlLiteObjects() // 检测SQLite泄漏
.build()
)
}
private fun initializePerformanceMonitoring() {
// 可以在Application级别启动全局性能监控
if (BuildConfig.DEBUG) {
// 开发模式下启动基础监控
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
// 记录未捕获的异常
android.util.Log.e("PerformanceApp",
"Uncaught exception in thread: ${thread.name}",
throwable)
// 调用默认处理器
Thread.getDefaultUncaughtExceptionHandler()?.uncaughtException(thread, throwable)
}
}
}
override fun onLowMemory() {
super.onLowMemory()
// 内存不足时清理缓存
BitmapManager.clearCache()
// 通知所有Activity
android.util.Log.w("PerformanceApp", "Low memory warning, clearing caches")
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
// 根据Trim级别清理资源
when (level) {
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> {
// 清理部分缓存
BitmapManager.clearCache()
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL,
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
// 清理所有可能的内存
BitmapManager.clearCache()
System.gc()
}
}
android.util.Log.i("PerformanceApp", "Trim memory level: $level")
}
}
性能优化要点
1. 绘制优化
-
✅ 启用硬件加速 (setLayerType(LAYER_TYPE_HARDWARE, null))
-
✅ 避免在onDraw中创建对象
-
✅ 使用局部刷新 (invalidate(left, top, right, bottom))
-
✅ 预计算路径和矩形
-
✅ 复用Paint和Shader对象
2. 内存优化
-
✅ 使用LRU缓存管理Bitmap
-
✅ 实现Bitmap复用池
-
✅ 及时回收不再使用的资源
-
✅ 使用对象池避免频繁GC
3. 动画优化
-
✅ 使用ValueAnimator替代Thread
-
✅ 高刷新率设备适配
-
✅ 使用Choreographer同步Vsync
-
✅ 动画插值器优化
4. 异步优化
-
✅ 使用RenderThread异步绘制
-
✅ 离屏渲染缓存
-
✅ 后台线程计算,主线程更新
常见问题排查
问题1: 硬件加速导致崩溃
// 解决方案:检查不支持硬件加速的操作
override fun onDraw(canvas: Canvas) {
if (canvas.isHardwareAccelerated) {
// 使用硬件加速绘制路径
drawHardware(canvas)
} else {
// 软件回退
drawSoftware(canvas)
}
}
问题2: 内存泄漏
// 在View销毁时清理资源
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
animator?.cancel()
bitmap?.recycle()
handler.removeCallbacksAndMessages(null)
}
问题3: 高刷新率设备掉帧
// 适配不同刷新率
val display = context.display
val refreshRate = display?.refreshRate ?: 60f
val frameTime = 1000f / refreshRate
// 根据刷新率调整动画时长
animator.duration = (frameTime * 60).toLong()
这个完整的Demo项目展示了自定义View性能优化的完整解决方案,从问题定位到优化实现,再到性能监控和测试,提供了全方位的性能优化参考。
更多推荐


所有评论(0)