關於Adapter的The content of the adapter has changed問題分析 (轉載)

寫得很詳細, 轉載下:https://www.cnblogs.com/monodin/p/3874147.html

1、問題描述

複製代碼

 1 07-28 17:22:02.162: E/AndroidRuntime(16779): 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. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131034604, class android.widget.ListView) with Adapter(class com.nodin.sarah.HeartListAdapter)]
 2 
 3 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.ListView.layoutChildren(ListView.java:1555)
 4 
 5 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.AbsListView.onLayout(AbsListView.java:2091)
 6 
 7 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.View.layout(View.java:14785)
 8 
 9 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewGroup.layout(ViewGroup.java:4631)
10 
11 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1055)
12 
13 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.View.layout(View.java:14785)
14 
15 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewGroup.layout(ViewGroup.java:4631)
16 
17 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
18 
19 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
20 
21 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.View.layout(View.java:14785)
22 
23 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewGroup.layout(ViewGroup.java:4631)
24 
25 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1589)
26 
27 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.View.layout(View.java:14785)
28 
29 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewGroup.layout(ViewGroup.java:4631)
30 
31 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1055)
32 
33 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.View.layout(View.java:14785)
34 
35 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewGroup.layout(ViewGroup.java:4631)
36 
37 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
38 
39 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
40 
41 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.View.layout(View.java:14785)
42 
43 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewGroup.layout(ViewGroup.java:4631)
44 
45 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
46 
47 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
48 
49 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
50 
51 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.View.layout(View.java:14785)
52 
53 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewGroup.layout(ViewGroup.java:4631)
54 
55 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
56 
57 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
58 
59 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.View.layout(View.java:14785)
60 
61 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewGroup.layout(ViewGroup.java:4631)
62 
63 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1985)
64 
65 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1742)
66 
67 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:998)
68 
69 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5582)
70 
71 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
72 
73 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.Choreographer.doCallbacks(Choreographer.java:562)
74 
75 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.Choreographer.doFrame(Choreographer.java:532)
76 
77 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
78 
79 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.os.Handler.handleCallback(Handler.java:733)
80 
81 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.os.Handler.dispatchMessage(Handler.java:95)
82 
83 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.os.Looper.loop(Looper.java:137)
84 
85 07-28 17:22:02.162: E/AndroidRuntime(16779):   at android.app.ActivityThread.main(ActivityThread.java:4998)
86 
87 07-28 17:22:02.162: E/AndroidRuntime(16779):   at java.lang.reflect.Method.invokeNative(Native Method)
88 
89 07-28 17:22:02.162: E/AndroidRuntime(16779):   at java.lang.reflect.Method.invoke(Method.java:515)
90 
91 07-28 17:22:02.162: E/AndroidRuntime(16779):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
92 
93 07-28 17:22:02.162: E/AndroidRuntime(16779):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
94 
95 07-28 17:22:02.162: E/AndroidRuntime(16779):   at dalvik.system.NativeStart.main(Native Method)
96 
97 07-28 17:22:02.162: W/ActivityManager(588):   Force finishing activity  

複製代碼

 

2、復現場景

使用ListView和Adapter實現動態增刪數據列表功能,初始化數據分爲兩部分:本地和網絡。所以在Adapter的數據初始化的時候,先講本地數據添加到了容器內。同時發起網絡請求,等加載完畢後追加到容器內。

問題出現在:當網絡請求完畢後追加數據的時候,拋出上述異常。

 

3、原因分析

Exception解讀:

        Adapter的數據內容已經改變,但是ListView卻未接收到通知。要確保不在後臺線程中修改Adapter的數據內容,而要在UI Thread中修改。確保Adapter的數據內容改變時一定要調用notifyDataSetChanged()方法。

 

且不管Exception內容,先查詢Android源碼看看該Exception是從哪裏拋出來的。

在ListView的layoutChildren()方法裏有如下一段方法:

複製代碼

 1 // Handle the empty set by removing all views that are visible
 2 // and calling it a day
 3 if (mItemCount == 0) {
 4     resetList();
 5     invokeOnItemScrollListener();
 6     return;
 7 } else if (mItemCount != mAdapter.getCount()) {
 8     throw new IllegalStateException("The content of the adapter has changed but "
 9             + "ListView did not receive a notification. Make sure the content of "
10             + "your adapter is not modified from a background thread, but only "
11             + "from the UI thread. [in ListView(" + getId() + ", " + getClass()
12             + ") with Adapter(" + mAdapter.getClass() + ")]");
13 }

複製代碼

 

亦即,當ListView緩存的數據Count和ListView中Adapter.getCount()不等時,會拋出該異常。

 

結合開頭的異常解讀,可以斷定肯定是Adapter數據動態更新的問題。仔細檢查了自己的代碼:

 

當網絡請求完畢後,直接在網絡線程(非UI線程)裏調用了在Adapter中新增的自定義方法addData(List)更新數據,而addData(List)方法內更新換完數據後,通過Handler發送Message的策略調用Adapter的notifyDataSetChanged()方法通知更新。

 

這麼一來,並不能保證Adapter的數據更新時,立馬調用notifyDataSetChanged()通知ListView,這兩個線程之間的時間差引起的數據不同步,導致ListView的layoutChildren()中訪問Adapter的getCount()方法時,Adapter內已經是最新數據源,而ListView內的緩存數據Count仍是舊數據的Count,該問題最終原因終於浮出水面。

 

4、解決方案

在本例中,解決方案是:把addData(List)方法內更新數據的代碼挪出來,和notifyDataSetChanged()方法一同放在Handler裏,保證數據更新時及時通知ListView。

 

爲了儘量避免該問題,以後編程儘量從如下幾個方面檢查自己的代碼:

  • 確保Adapter的數據更新後一定要調用notifyDataSetChanged()方法通知ListView
  • 數據更新和notifyDataSetChanged()放在UI線程內,且必須同步順序執行,不可異步
  • 仔細檢查確認getCount()方法返回值是否正確
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章