多个推送消息:适配器的内容已更改,但ListView未收到通知

Chr*_*ris 13 android android-listview google-cloud-messaging

当我在1秒钟内从GCM收到大量推送消息(假设为50)时,我收到以下异常:

java.lang.IllegalStateException:适配器的内容已更改,但ListView未收到通知.确保不从后台线程修改适配器的内容,而只是从UI线程修改.[在ListView(2131427434,类android.widget.ListView)中使用Adapter(类an)]在android.widget.List.layoutChildren(ListView.java:1544)在android.widget.AbsListView.onLayout(AbsListView.java:2045)在Android.widget上的android.view.View.layout(View.java:14255)android.view.Linout.setChildFrame(LinearLayout.java:1670)的android.view.ViewGroup.layout(ViewGroup.java:4413). LinearLayout.layoutVertical(LinearLayout.java:1528)位于android.view.View. Layout(View.java:14255)的android.view.Linout.onLayout(LinearLayout.java:1441)android.view.ViewGroup.layout(ViewGroup) .java:4413)在Android.view.ViewGroup.layout(ViewGroup.java:4413)android.view.View.layout(View.java:14255)的android.support.v4.view.ViewPager.onLayout(未知来源)在android.widget.LinearLayout.setChildFrame(LinearLayout.java:1670)的android.widget.LinearLayout.layoutVertical(LinearLayout.java:1528)android.widget.LinearLayout.onLayout(LinearLayout.java:1441)android.view .View.layout(VIE w.java:14255)在android.view.View.lay(View.java:的android.sview.v4上,android.view.vout.Douter.onLayout(未知来源)的android.view.ViewGroup.layout(ViewGroup.java:4413) 14255)在android.view.Loutout(MapLayout.java:446)的android.view.View.Loutout(View.java:1425)android的Android.view.View.Loutout(FrameLayout.java:446)的android.view.ViewGroup.layout(ViewGroup.java:4413).在android.view上的android.view.View.layout(View.java:14255)的android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(未知来源)中查看.ViewGroup.layout(ViewGroup.java:4413).位于android.view.View.Loutout(ViewLayout.java:446)的android.view.Loutout(ViewLayout.java:446)处于android.view.View.layout(View.java:14255)处的ViewGroup.layout(ViewGroup.java:4413) .java:4413)在Android.widget.LinearLayout.ayLayout(LinearLayout.java:1441)上的android.widget.LinearLayout.setChildFrame(LinearLayout.java:1670)android.widget.LinearLayout.layoutVertical(LinearLayout.java:1528)在android.vi上的android.view.View.layout(View.java:14255)ew.ViewGroup.layout(ViewGroup.java:4413)位于android.view.View.Viewout.Loutout(ViewLayout.java:446),位于android.view.View.layout(View.java:14255),位于android.view.ViewGroup.layout (ViewGroup.java:4413)在Android.view.ViewRootImpl.exeTraversals(ViewRootImpl.java:1812)的android.view.ViewRootImpl.exeTransals(ViewRootImpl.java:1899)上的android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1998)(ViewRootImpl.java: 1050)在Android.view.ChoreIs上的android.view.ViewRootImpl $ TraversalRunnable.run(ViewRootImpl.java:4560),在android.view.Choreographer.doCallbacks(Choreographer.java:562)的android.view.Choreographer $ CallbackRecord.run(Choreographer.java:749) )在Android.view.Choreographer $ FrameDisplayEventReceiver.run(Choreographer.java:735)的android.view.Horeler.handleCallback(Handler.java:725)android.view.Choreographer.doFrame(Choreographer.java:532)位于android.app.Looper.loop(Looper.java:137)的.os.Handler.dispatchMessage(Handler.java:92)位于java.lang.reflect的android.app.ActivityThread.main(ActivityThread.java:5171).中号 在com.android的com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:797)的java.lang.reflect.Method.invoke(Method.java:511)中的ethod.invokeNative(Native Method).在dalvik.system.NativeStart.main(Native方法)的internal.os.ZygoteInit.main(ZygoteInit.java:564)

我已经尝试解决这个问题,通过把两个messages.add()notifyDataSetChanged()里面的runOnUIThread.我想这onUpdate()是因为每个推送消息都会调用我的监听器.但不应该解决这个问题runOnUIThread(),因为一切都是连续执行的?

    MainApplication app = (MainApplication) context.getApplicationContext();
    app.setOnRoomMessageUpdateListener(new OnRoomMessageUpdateListener() {
        @Override
        public void onUpdate() {
            // save message with highest time, so we can only query the new
            // messages
            long highestTime = getHighestMessageTime();

            messageDatabase.getConditionBuilder().add(
                    DatabaseHelper.KEY_MESSAGE_ROOM_ID + " = ? AND " + DatabaseHelper.KEY_MESSAGE_LOCAL_TIME
                            + " > ? AND " + DatabaseHelper.MESSAGE_TABLE_NAME + "."
                            + DatabaseHelper.KEY_MESSAGE_USER_ID + " <> ?",
                    new String[] { String.valueOf(roomID), String.valueOf(highestTime),
                            String.valueOf(user.getUserID()) });
            messageDatabase.getConditionBuilder().setSortOrder(DatabaseHelper.KEY_MESSAGE_LOCAL_TIME + " DESC");
            final ArrayList<Message> newMessages = messageDatabase.getList();

            ((Activity) context).runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    messages.addAll(newMessages);
                    messageAdapter.notifyDataSetChanged();
                }
            });
        }
    });
Run Code Online (Sandbox Code Playgroud)

编辑:我可能错过了代码的一个非常重要的部分,我忽略了自己:

app.setOnRoomUserUpdateListener(new OnRoomUserUpdateListener() {
    @Override
    public void onUpdate(final User user, final int roomID, final int joinStatus) {
        final String message;
        if (joinStatus == OnRoomUserUpdateListener.USER_JOINED) {
            message = context.getString(R.string.join_room_message, user.getUsername());
        } else {
            message = context.getString(R.string.leave_room_message, user.getUsername());
        }

        ((Activity) context).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                messages.add(new Message(-1, user, message, System.currentTimeMillis(), System
                        .currentTimeMillis(), false, roomID, 0, true, Message.TYPE_JOINLEAVE));
                messageAdapter.notifyDataSetChanged();
            }
        });
    }
});
Run Code Online (Sandbox Code Playgroud)

此代码块位于上面的代码下方,显然它也修改了适配器的内容.当它们同时运行时,可能会出现问题,对吧?这可以通过使用synchronized或有更好的方法来解决吗?

编辑2: 初始化:

// get all messages
    messages = new ArrayList<Message>();
    messageDatabase.getConditionBuilder().add(DatabaseHelper.KEY_MESSAGE_ROOM_ID + " = ?",
            new String[] { String.valueOf(roomID) });
    messageDatabase.getConditionBuilder().setSortOrder(DatabaseHelper.KEY_MESSAGE_LOCAL_TIME + " DESC");
    messageDatabase.getConditionBuilder().setSqlLimit(100);
    messages.addAll(messageDatabase.getList());

    // get "user joined/left" messages
    UserDatabase userDatabase = UserDatabase.getInstance(context);
    messages.addAll(userDatabase.getJoinLeaveMessages(roomID));

    Collections.sort(messages);
    messageAdapter = new MessageAdapter(getActivity(), R.layout.list_message_item, messages);
    listView.setAdapter(messageAdapter);
Run Code Online (Sandbox Code Playgroud)

编辑3:片段的完整源代码,其中包含代码:https://gist.github.com/ChristopherWalz/89a071b1606460e18ce7

Vas*_*liy 3

首先我们看一下ListView抛出这个异常的代码:

@Override
protected void layoutChildren() {
    // ... code omitted...

        // Handle the empty set by removing all views that are visible
        // and calling it a day
        if (mItemCount == 0) {
            resetList();
            invokeOnItemScrollListener();
            return;
        } else if (mItemCount != mAdapter.getCount()) {
            throw new 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(" + getId() + ", " + getClass()
                    + ") with Adapter(" + mAdapter.getClass() + ")]");
        }
    // ... code omitted...
}
Run Code Online (Sandbox Code Playgroud)

[您可能会注意到该消息有所不同,但这只是因为您使用的是旧版 Android - 该消息已于 13 年 9 月提供了更多信息]

让我们看看mItemCount成员变量是在哪里声明的……嗯,这个变量似乎是从类中继承下来的AdapterView。好的,让我们找到所有班级中的所有作业:

在此输入图像描述

基本上,除了单个赋值给0(在onIvalidated()方法中并且可以忽略)之外,该变量总是被赋值给 的AdaptergetCount()

我们可以得出结论,您收到异常是因为有一些并发代码Adapter在用于绘制ListView.

现在,从您的问题来看,您似乎怀疑 UI 线程上存在某种更新“拥塞”,因为消息太多......但是,让我们记住Runnable.run()您发布到 UI 线程的代码for 执行以原子方式执行 - 每个事件都Runnable从 UI 线程的事件队列中弹出,并在任何其他事件有机会被处理之前运行完成。

上述意味着messages将使用新数据进行更新并messageAdapter立即处理更改,并且没有其他事件可以干扰此流程(只要代码中的messages.add()和是同步调用)。messages.addAll()底线:分派Runnables到 UI 线程的代码看起来不错,而且它不太可能是问题的根源。此外,异常的堆栈跟踪不包含任何对 的引用Adapter

到目前为止,我们总结了事实。让我们开始猜测吧。

我认为问题不在您发布的代码中。我猜您执行了以下一项(或多项)操作,每项操作都可能导致出现异常:

  1. 我猜这messages与内部使用的数据结构相同messageAdapter。您可能会修改messages代码的其他部分,但该部分不在 UI 线程上运行。在这种情况下,这种修改可能会ListView在 UI 线程上刷新时发生,并导致异常 [将 'Adapter的数据结构“泄漏”到Adapter对象外部通常是不好的做法]。
  2. 同样,您可能会意外地操作messageAdapter不在 UI 线程上运行的代码的其他部分。
  3. 可能的情况是,您在ListView尚未完成初始化的情况下将事件发布到 UI 线程。我几乎不相信是这种情况,但为了安全起见,我建议您确保在中注册您的听众onResume()并在中注销它们onPause()

编辑:

根据您的代码,Fragment我可以看到异常的两个可能原因:

  1. CustomResponseHandler如果您传递给的方法将从后台线程调用,那么您从in 中ServerUtil.post()删除对象的事实可能就是这种情况。messagesonFailure()
  2. 正如我所建议的 - 注册侦听器onResume()和取消注册onPause()- 这对于按钮单击侦听器可能并不重要,但当您将这些侦听器传递给Application对象时,它会导致内存泄漏。您的代码中存在内存泄漏

MessageAdapter如果以上都没有帮助,请发布和的代码MessageDatabase