Adapter内容变更未通知ListView:UI线程操作仍报错求助
我来帮你拆解下这个问题的几个关键排查方向,你遇到的这个错误看似矛盾,但其实藏着几个容易忽略的细节:
先检查
get_data()方法的实现
虽然你在onPostExecute里是在UI线程更新adapter,但doInBackground是后台线程,get_data()如果在这里直接修改了全局的listProducts(比如做add/remove、或者直接操作和adapter绑定的数据源),那本质就是在后台线程修改了adapter的内容,这完全符合错误提示里说的“从后台线程修改adapter内容”的情况。
正确的做法应该是让get_data()生成并返回一个全新的List对象,而不是直接修改已有的数据源。去掉
onPostExecute里多余的runOnUiThreadAsyncTask的onPostExecute方法本身就是在UI线程执行的,你这里额外套了一层getActivity().runOnUiThread()完全没必要,反而会混淆线程逻辑,简化后的代码应该是:@Override protected void onPostExecute(Void result) { super.onPostExecute(result); if (isAdded()) { mAdapter.setData(listProducts); mAdapter.notifyDataSetChanged(); loading_indicator.hide(); } }解决搜索时的重复任务冲突
你用了300ms延迟触发搜索任务,但如果用户快速输入,会同时存在多个待执行的LoadingProducts任务。这些任务会先后执行onPostExecute,可能出现:后完成的任务覆盖了先完成的数据源,但ListView的通知逻辑没跟上,导致数据和UI不同步。
建议每次触发新任务前取消旧任务:- 把
LoadingProducts实例存为成员变量:private LoadingProducts mCurrentLoadingTask; - 在
onQueryTextChange里先取消旧任务:if (mCurrentLoadingTask != null) { mCurrentLoadingTask.cancel(true); } mHandler.postDelayed(new Runnable() { @Override public void run() { if (mQueryStringSearch.length() > 2) { mCurrentLoadingTask = new LoadingProducts(); mCurrentLoadingTask.execute(); } } }, 300); - 在
doInBackground里判断任务是否已取消,避免无效操作:@Override protected Void doInBackground(Void... params) { if (isCancelled()) return null; get_data(); return null; }
- 把
确保数据源的线程安全
最好在doInBackground里创建独立的数据源对象,比如让get_data()返回新的列表,再赋值给全局变量,避免后台线程和UI线程同时操作同一个List:@Override protected Void doInBackground(Void... params) { if (isCancelled()) return null; List<Product> newProductList = get_data(); // 修改get_data()返回新列表 listProducts = newProductList; return null; }
内容的提问来源于stack exchange,提问作者Emmanuel

