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

Android AsyncTask 背景執行緒操作 UI 導致崩潰解決方案

     編輯:程式設計綜合問答

問題現象

在 AsyncTask.doInBackground() 中直接操作 UI 元件(findViewById、ListView.setAdapter 等),拋出 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()。

錯誤代碼分析


private class HomeDownloadPage extends AsyncTask {
    @Override
    protected String doInBackground(String... params) {
        // ... 網路請求、解析資料 ...
        
        // 錯誤:在背景執行緒操作 UI
        ListView lv = (ListView) findViewById(R.id.user_list); // 需 UI 執行緒
        final UserFollowingListAdapter csl = new UserFollowingListAdapter(...);
        lv.setAdapter(csl); // 必須在 UI 執行緒
        
        lv.setOnItemClickListener(...); // 同樣需 UI 執行緒
        return null;
    }
}

根本原因

  • doInBackground() 執行於**背景執行緒**(非 UI 執行緒)
  • Android **嚴禁**非 UI 執行緒存取/修改 View 層級物件
  • findViewById、setAdapter、setOnClickListener 等皆屬 UI 操作
  • 嘗試在背景執行緒建立 Handler(Adapter 內部會用到)觸發崩潰

正確架構:背景運算、主執行緒更新 UI

核心原則

方法執行緒允許操作禁止操作
onPreExecute()UI顯示 ProgressDialog、初始化 UI 狀態耗時運算
doInBackground()背景網路請求、資料庫查詢、複雜運算、檔案 I/O任何 View 操作、Toast、Dialog
onProgressUpdate()UI更新進度條、中間結果顯示耗時運算
onPostExecute()UI更新 ListView、RecyclerView、隱藏 Dialog、Toast耗時運算

修正後代碼


public class HomeActivity extends AppCompatActivity {
    private ListView lv;
    private UserFollowingListAdapter adapter;
    private List userList = new ArrayList<>();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.user_main_tab_home);
        
        lv = findViewById(R.id.user_crumbs_list);
        adapter = new UserFollowingListAdapter(this, R.layout.user_crumbs_list_item, userList);
        lv.setAdapter(adapter); // 先設定空 Adapter
        
        new HomeDownloadPage().execute();
    }
    
    private class HomeDownloadPage extends AsyncTask> {
        private ProgressDialog dialog;
        
        @Override
        protected void onPreExecute() {
            dialog = new ProgressDialog(HomeActivity.this);
            dialog.setMessage("載入中...");
            dialog.show();
        }
        
        @Override
        protected List doInBackground(Void... voids) {
            // 僅做資料獲取與解析,不碰 UI
            List result = new ArrayList<>();
            try {
                User user = new User("4eeb34c6d80e8f1214000000");
                user.getFollowingCrumbsUpList(); // 網路請求
                for (User u : user.following) {
                    result.add(u);
                }
            } catch (Exception e) {
                Log.e(TAG, "資料獲取失敗", e);
                // 可選:返回 null 或空列表,onPostExecute 判斷處理錯誤
            }
            return result;
        }
        
        @Override
        protected void onPostExecute(List result) {
            dialog.dismiss();
            
            if (result != null && !result.isEmpty()) {
                // UI 執行緒安全更新
                userList.clear();
                userList.addAll(result);
                adapter.notifyDataSetChanged(); // 刷新 ListView
            } else {
                showError();
            }
        }
    }
    
    private void showError() {
        new AlertDialog.Builder(this)
            .setTitle("伺服器連線失敗")
            .setPositiveButton("確定", null)
            .show();
    }
}

現代替代方案:Kotlin Coroutines + Flow / LiveData


// ViewModel
class HomeViewModel : ViewModel() {
    private val _users = MutableLiveData>()
    val users: LiveData> = _users
    
    fun loadUsers() {
        viewModelScope.launch {
            try {
                val result = userRepository.getFollowingUsers() // suspend 函數
                _users.postValue(result)
            } catch (e: Exception) {
                _users.postValue(emptyList())
            }
        }
    }
}

// Activity
class HomeActivity : AppCompatActivity() {
    private val viewModel: HomeViewModel by viewModels()
    private val adapter = UserFollowingListAdapter()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        viewModel.users.observe(this) { users ->
            adapter.submitList(users)
            if (users.isEmpty()) showError()
        }
        viewModel.loadUsers()
    }
}

常見錯誤對照

錯誤寫法正確寫法說明
doInBackground 中 findViewByIdonCreate 中初始化 View 參考View 參考應在 UI 執行緒取得
doInBackground 中 setAdapteronPostExecute 中 adapter.notifyDataSetChanged()資料綁定在 UI 執行緒
doInBackground 中 Toast.show()onPostExecute 中 Toast.show()Toast 需 UI 執行緒
doInBackground 中 Dialog.show()onPreExecute 顯示,onPostExecute 隱藏Dialog 生命週期綁定 UI 執行緒

總結

背景執行緒只負責「拿資料」,UI 執行緒負責「顯示資料」。AsyncTask 正確模式:onPreExecute 準備 UI → doInBackground 純資料處理 → onPostExecute 更新 UI。現代專案建議遷移至 Kotlin Coroutines + ViewModel + LiveData/Flow,獲得更安全的執行緒管理與生命週期感知。

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