• 【原创】关于Adapter的The content of the adapter has changed问题分析


    关于Adapter的The content of the adapter has changed问题分析

     

    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()方法返回值是否正确

    作者:Nodin
    出处:http://www.cnblogs.com/monodin
    个人博客网站:http://www.coderself.com
    关于作者:爱摄影,爱旅行,爱自由,爱生活,更要爱自己。活在当下,也在为未来打拼!
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

  • 相关阅读:
    监视和调整硬件性能
    ASP.NET MVC三个重要的描述对象:ActionDescriptor
    REST in Practice
    软硬件错误的排查之道
    OMCS 多媒体连接系统
    逻辑层 vs 物理层
    深入浅出裸测之道单元测试的单元化
    简单的网络爬虫实现
    WCF返回JSON与传入JSON(普通参数或对象)
    .NET程序员的一个礼物——TypeMonster
  • 原文地址:https://www.cnblogs.com/monodin/p/3874147.html
Copyright © 2020-2023  润新知