首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Android yalantis SideMenu 子線程操作 RecyclerView 導致 ViewRootImpl$CalledFromWrongThreadException 解決

     編輯:程式設計綜合問答

錯誤根因

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 線程消息隊列,確保在主線程執行。

方案二:手動測量佈局後離屏繪製(不依賴 View 繪製流程)

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 狀態,不推薦。

方案三:PixelCopy API (API 24+)

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。

最佳實踐總結

  1. 優先使用 View.post() 或 runOnUiThread() 將 UI 操作切回主線程。
  2. 截圖邏輯本質是 UI 操作,不要放入子線程。
  3. 若需異步處理截圖後的業務(上傳、保存),在主線程截圖完成後,再開啟子線程處理 Bitmap 數據。
  4. 檢查所有 View.draw()、setText()、setVisibility() 等調用點,確保在主線程。

遵循「UI 只在主線程」鐵律,配合 post()/runOnUiThread()/Handler 切換,徹底杜絕 CalledFromWrongThreadException。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved