【案例分析】
最近开发项目中遇到了java.util.IllegalStateException这种类型的crash,由于实现的场景比较复杂,这里讲出现问题的场景进行了简化模拟:自定义一个ListAdapter,每次点击ListView中的一个Item时,整个ListView中的数据膨胀一倍(即原来ListView有10个元素,item0~item9,点击其中任何一个item后,整个ListView会展示20个元素)。
错误Log展示:
【类型定位】
java.util.IllegalStateException这种类型的crash在app应用中也是会经常遇到,它代表了我们在使用listView或者gridView等控件时绑定的数据发生更新但是UI没有做出反馈的情形。这种crash更多的出现在我们自己定义了自己的ListViewAdapter,然后在写代码逻辑时更改了Adapter中对应的数据,但是没有及时通知到UI让其作出正确的更新操作。
【解决方案】
查找ListView绑定的那个adapter,找到关联这个adapter的数据源,找到所有对这个数据源进行改动的地点后(找到所有的数据源改动可以通过快捷键alt+F7, 参考提升Android Studio开发效率的小技巧),手动调用adapter的notifyDataSetChanged方法,让UI能够及时作出更新。
【代码展示】
首先是会引起java.util.IllegalStateException的crash:
public class MainActivity extends AppCompatActivity {
private ListView mListView = null;
private ListAdapter adapter = null;
private List<String> data = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < 10; ++i) {
data.add(i, "temp "+i);
}
mListView = (ListView) findViewById(R.id.text_list_view);
adapter = new ListAdapter(MainActivity.this, R.layout.list_item, data);
mListView.setAdapter(adapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
updateListData();
}
});
}
private void updateListData() {
for (int i = 0; i < 10; ++i) {
data.add("temp "+i);
}
}
private class ListAdapter extends ArrayAdapter<String> {
private List<String> mDatas;
private int resourceId;
public ListAdapter(Context context, int resourceId, List<String> data) {
super(context, resourceId, data);
mDatas = data;
this.resourceId = resourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String strItem = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, null);
} else {
view = convertView;
}
TextView tv = (TextView) view.findViewById(R.id.item_text);
tv.setText(strItem);
return view;
}
@Override
public String getItem(int position) {
return super.getItem(position);
}
@Override
public int getCount() {
return mDatas != null ? mDatas.size() : 0;
}
}
下面在updateListData这个函数中添加了adapter的更新操作,可以解决IllegalStateException:
private void updateListData() {
for (int i = 0; i < 10; ++i) {
data.add("temp "+i);
}
/**
* 作者:lexli
* 说明:
* 动态更新完ListView关联的数据后,一定要通知到adapter notifyDataSetChanged
* 好让UI能够做出正确的反应,否则就会出现crash:java.lang.IllegalStateException:
* The content of the adapter has changed but ListView did not receive a notification.
* Make sure the content of your adapter is not modified from a background thread,
* but only from the UI thread.
*
* 注意:1. 任何修改原始ListView关联的数据后都要及时通知adapter
* 2. adapter的notifyDataSetChanged 需要保证在UI线程中处理
* 博客:http://blog.youkuaiyun.com/csdn_lexli/article
*/
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
【个人总结】
- java.util.IllegalStateException对应着Adapter数据和UI展示数据不匹配问题
- 解决方案是在每次Adapter数据更改后,调用adapter的notifyDataSetChanged方法
- 定位项目所有的Adapter数据的修改:选中相关数据-> 快捷键alt+F7