在 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 執行緒)findViewById、setAdapter、setOnClickListener 等皆屬 UI 操作Handler(Adapter 內部會用到)觸發崩潰| 方法 | 執行緒 | 允許操作 | 禁止操作 |
|---|---|---|---|
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();
}
}
// 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 中 findViewById | onCreate 中初始化 View 參考 | View 參考應在 UI 執行緒取得 |
doInBackground 中 setAdapter | onPostExecute 中 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,獲得更安全的執行緒管理與生命週期感知。