Only the original thread that created a view hierarchy can touch its views:Android UI 組件非線程安全,只能在創建它們的線程(主線程/UI 線程)中訪問/修改。子線程中調用 containerView.draw(canvas) 觸發 View 繪製流程,涉及測量、佈局、繪製,均屬 UI 操作,故拋出異常。
new Thread() {
public void run() {
Bitmap bitmap = Bitmap.createBitmap(containerView.getWidth(), ...);
Canvas canvas = new Canvas(bitmap);
containerView.draw(canvas); // 非法:子線程操作 View
ContentFragment.this.bitmap = bitmap;
}
}.start();
RecyclerView 引入後,containerView 內部持有更複雜的 View 階層,繪製過程中會回調 onDraw、dispatchDraw 等,極大概率觸發線程檢查。
containerView.post(() -> {
Bitmap bitmap = Bitmap.createBitmap(containerView.getWidth(), containerView.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
containerView.draw(canvas);
ContentFragment.this.bitmap = bitmap;
// 後續操作(如動畫啟動)在這裡繼續
});
View.post(Runnable) 將任務投遞到 UI 線程消息隊列,確保在主線程執行。
new Thread(() -> {
// 1. 手動測量
int specW = View.MeasureSpec.makeMeasureSpec(containerView.getWidth(), View.MeasureSpec.EXACTLY);
int specH = View.MeasureSpec.makeMeasureSpec(containerView.getHeight(), View.MeasureSpec.EXACTLY);
containerView.measure(specW, specH);
containerView.layout(0, 0, containerView.getMeasuredWidth(), containerView.getMeasuredHeight());
// 2. 離屏繪製
Bitmap bitmap = Bitmap.createBitmap(containerView.getMeasuredWidth(), containerView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
containerView.draw(canvas); // 此時已測量佈局完成,但仍在子線程,風險依存
// 3. 必須切回主線程賦值
requireActivity().runOnUiThread(() -> ContentFragment.this.bitmap = bitmap);
}).start();
風險:draw(canvas) 內部仍可能訪問未同步的 View 狀態,不推薦。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PixelCopy.request(containerView, bitmap, copyResult -> {
if (copyResult == PixelCopy.SUCCESS) {
ContentFragment.this.bitmap = bitmap;
}
}, handler);
}
系統級截圖,不觸發 View 繪製流程,完全線程安全,但需最低 API 24。
View.post() 或 runOnUiThread() 將 UI 操作切回主線程。View.draw()、setText()、setVisibility() 等調用點,確保在主線程。遵循「UI 只在主線程」鐵律,配合 post()/runOnUiThread()/Handler 切換,徹底杜絕 CalledFromWrongThreadException。