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

Android ListView 非 UI 線程修改數據源導致崩潰:adapter.notifyDataSetChanged() 線程安全實踐

     編輯:程式設計綜合問答

Android `ListView` 點擊時拋出 IllegalStateException: The content of the adapter has changed but ListView did not receive a notification。根因:**非主線程修改了 `ArrayList` 數據源,卻未在主線程調用 `notifyDataSetChanged()`**,導致 `ListView` 佈局與數據不一致。

錯誤場景還原

// 子線程/網絡回調中
new Thread(() -> {
    List<Item> newData = fetchFromNetwork();
    adapterData.clear();           // 修改數據源
    adapterData.addAll(newData);   // 修改數據源
    // 缺少 notifyDataSetChanged() 或不在主線程調用
}).start();

// 主線程點擊 ListView 時,檢測到數據變更但未收到通知 → 崩潰

核心規則

  • 所有 UI 相關操作必須在主線程:包括修改 `Adapter` 持有的集合、調用 `notifyDataSetChanged()`、`setAdapter()`。
  • `ListView` 在佈局/點擊時會檢查數據源大小與記錄的大小是否一致,不一致即拋出異常。

修正方案

方案一:runOnUiThread / Handler 切換到主線程(傳統)

new Thread(() -> {
    List<Item> newData = fetchFromNetwork();
    runOnUiThread(() -> {  // Activity 方法
        adapterData.clear();
        adapterData.addAll(newData);
        adapter.notifyDataSetChanged();  // 必須調用
    });
}).start();

方案二:ExecutorService + Handler(更規範)

ExecutorService executor = Executors.newSingleThreadExecutor();
Handler mainHandler = new Handler(Looper.getMainLooper());

executor.execute(() -> {
    List<Item> newData = fetchFromNetwork();
    mainHandler.post(() -> {
        adapterData.clear();
        adapterData.addAll(newData);
        adapter.notifyDataSetChanged();
    });
});

方案三:LiveData + ViewModel + RecyclerView(現代架構,推薦)

  • `ViewModel` 持有 `MutableLiveData>`。
  • Repository 異步獲取數據後 `postValue()`。
  • `Activity/Fragment` `observe()` 數據變化,提交給 `RecyclerView.Adapter`(內部已處理線程與動畫)。
  • 徹底避免手動線程切換與 `notifyDataSetChanged()` 易錯點。

常見誤區

  • setClickable(false) **無法解決**:崩潰發生在佈局階段,而非點擊事件分發。
  • 在子線程 `new ArrayList()` 重新賦值 `adapterData = newList`:`Adapter` 仍持有舊引用,`ListView` 檢測不到,同樣崩潰。
  • 使用 `Collections.synchronizedList`:僅保證集合操作原子性,**不解決 UI 線程通知問題**。

調試技巧

  • 開啟 StrictMode 檢測非 UI 線程訪問:StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build())。
  • 日誌過濾 ListView/Adapter 關鍵字定位調用堆疊。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved