繪制流程
在 Android?的 View 系統中,draw(canvas)?和?dispatchDraw(canvas)?是繪制流程中的兩個關鍵方法:
1.?draw(canvas)?方法的作用
draw(canvas)?是?View 類中的核心繪制方法,它的主要職責包括:
- 繪制背景?- 調用?drawBackground(canvas)
- 保存畫布狀態?- 調用?canvas.save()
- 繪制內容?- 調用?onDraw(canvas)
- 繪制子視圖?- 調用?dispatchDraw(canvas)
- 繪制前景?- 調用?onDrawForeground(canvas)
- 恢復畫布狀態?- 調用?canvas.restore()
2.?dispatchDraw(canvas)?的作用
dispatchDraw(canvas)?專門負責繪制子視圖,它的調用鏈是:
===========================================
draw(canvas)?
? ↓
dispatchDraw(canvas) ?← 在這里被調用
? ↓
onDraw(canvas) ?← 子視圖的繪制
===========================================
無限遞歸的問題示例
自定義View 的部分代碼:
override fun dispatchDraw(canvas: Canvas) {// 繪制矩形// ...// 調用父類的 dispatchDraw 來繪制子視圖super.dispatchDraw(canvas)// 縮略圖回調post { generateThumbnail() }
}private fun generateThumbnail() {try {// 創建縮略圖val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)val canvas = Canvas(bitmap)// 這里調用 draw(canvas) 會導致問題draw(canvas)// ← 這里會再次觸發 dispatchDraw!val thumbnail = Bitmap.createScaledBitmap(bitmap, 120, 80, true)thumbnailCallback?.onThumbnailUpdated(thumbnail)} catch (e: Exception) {Log.e(TAG, "Error generating thumbnail", e)}
}
4. 無限遞歸的原因
當你調用?draw(canvas)?時:
- ?draw(canvas)?被調用
- draw(canvas)?內部調用?dispatchDraw(canvas)
- dispatchDraw(canvas)?被調用
- dispatchDraw(canvas)?內部調用?generateThumbnail()
- generateThumbnail()?內部調用?draw(canvas)
- 回到步驟 1,形成無限循環
5. 解決方案
這就是為什么我們修改了?generateThumbnail()?方法,直接繪制矩形而不調用?draw(canvas)
private fun generateThumbnail() {// 直接繪制矩形,避免調用 draw()for (rect in rects) {paint.color = rect.colorval left = rect.cx - rect.width / 2val top = rect.cy - rect.height / 2val right = rect.cx + rect.width / 2val bottom = rect.cy + rect.height / 2canvas.drawRect(left, top, right, bottom, paint)}
}
總結
draw(canvas)?調用?dispatchDraw(canvas)?是 Android View?系統的標準繪制流程,這是為了確保:
- 父視圖先繪制自己的內容
- 然后繪制所有子視圖
- 最后繪制前景
當你在?dispatchDraw()?中調用?draw()?時,就會打破這個流程,導致無限遞歸。正確的做法是直接操作 Canvas?進行繪制,而不是調用 View 的?draw()?方法。
bitMap問題
/*** 生成縮略圖*/private fun generateThumbnail() {try {// 創建縮略圖大小的 Bitmapval thumbnailWidth = 120val thumbnailHeight = 80val bitmap = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888)val canvas = Canvas(bitmap)// // 設置白色背景
// canvas.drawColor(Color.WHITE)// 計算縮放比例val scaleX = thumbnailWidth.toFloat() / widthval scaleY = thumbnailHeight.toFloat() / heightval scale = minOf(scaleX, scaleY)// 計算居中偏移val offsetX = (thumbnailWidth - width * scale) / 2val offsetY = (thumbnailHeight - height * scale) / 2// 應用變換canvas.save()canvas.translate(offsetX, offsetY)canvas.scale(scale, scale)// 應用當前視圖的變換(平移和縮放)val cx = width / 2fval cy = height / 2fcanvas.translate(this.offsetX, this.offsetY)canvas.scale(scaleFactor, scaleFactor, cx, cy)// 直接繪制矩形,避免調用 draw()for (rect in rects) {paint.color = rect.colorval left = rect.cx - rect.width / 2val top = rect.cy - rect.height / 2val right = rect.cx + rect.width / 2val bottom = rect.cy + rect.height / 2canvas.drawRect(left, top, right, bottom, paint)}canvas.restore()thumbnailCallback?.onThumbnailUpdated(bitmap)} catch (e: Exception) {Log.e(TAG, "Error generating thumbnail", e)}}
?內存消耗的巨大差異
之前的實現(大?Bitmap):
- 假設屏幕尺寸:1080×1920 像素
- Bitmap 大小:1080 × 1920 ×?4 bytes?=?8.3MB
- 每次生成縮略圖都需要?8.3MB 內存
- 頻繁創建和銷毀大 Bitmap
現在的實現(小 Bitmap):
- 縮略圖尺寸:120×80 像素
- Bitmap 大小:120 ×?80?× 4 bytes?=?38.4KB
- 內存使用量減少了?99.5%
問題總結
1.?無限遞歸 + 大 Bitmap 的雙重打擊
之前的實現存在兩個致命問題:
- 無限遞歸:draw(canvas)?→?dispatchDraw(canvas)?→?generateThumbnail()?→?draw(canvas)
- 大 Bitmap?創建:每次遞歸都創建?8.3MB 的 Bitmap
這導致:
- CPU?使用率 100%
- 內存快速耗盡(每秒可能創建幾十個 8.3MB 的 Bitmap)
- 應用崩潰或重啟
2.?Bitmap 縮放的開銷
之前的實現還需要額外的縮放操作:
kotlin
Apply to DeepLearnVie...
//?額外的縮放操作
val?scaledBitmap?=?Bitmap.createScaledBitmap(bitmap,?120,?80,?true)
這個操作本身就很耗?CPU 和內存。
3.?頻繁的垃圾回收
大 Bitmap 的頻繁創建和銷毀會觸發:
- 頻繁的垃圾回收
- 內存碎片化
- 系統卡頓
再進一步優化:?
Android View 繪制流程 優化 (Bitmap 復用+內容變化檢測+防抖調度策略)-CSDN博客