当我在 1 秒内从 GCM 收到大量推送消息(比如说 50 条)时,我收到以下异常:
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. [in ListView(2131427434, class android.widget.ListView) with Adapter(class a.n)] at android.widget.ListView.layoutChildren(ListView.java:1544) at android.widget.AbsListView.onLayout(AbsListView.java:2045) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1670) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1528) at android.widget.LinearLayout.onLayout(LinearLayout.java:1441) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.support.v4.view.ViewPager.onLayout(Unknown Source) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1670) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1528) at android.widget.LinearLayout.onLayout(LinearLayout.java:1441) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.support.v4.widget.DrawerLayout.onLayout(Unknown Source) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.FrameLayout.onLayout(FrameLayout.java:446) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(Unknown Source) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.FrameLayout.onLayout(FrameLayout.java:446) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1670) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1528) at android.widget.LinearLayout.onLayout(LinearLayout.java:1441) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.widget.FrameLayout.onLayout(FrameLayout.java:446) at android.view.View.layout(View.java:14255) at android.view.ViewGroup.layout(ViewGroup.java:4413) at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1998) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1812) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1050) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4560) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749) at android.view.Choreographer.doCallbacks(Choreographer.java:562) at android.view.Choreographer.doFrame(Choreographer.java:532) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735) at android.os.Handler.handleCallback(Handler.java:725) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:5171) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:564) at dalvik.system.NativeStart.main(Native Method)
我已经尝试通过将
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();
}
});
}
});
编辑: 我可能错过了代码的一个非常重要的部分,我自己忽略了它:
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();
}
});
}
});
该代码块位于上面的代码下方,显然它也在修改适配器的内容。当他们两个同时运行时,可能会出现问题,对吧?这可以通过使用
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);
编辑 3: fragment 的完整源代码,其中包含代码:https://gist.github.com/ChristopherWalz/89a071b1606460e18ce7
最佳答案
首先我们看一下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...
}
[您可能会注意到消息有所不同,但这只是因为您使用的是旧版 Android - 该消息已在 9 月 `13 中提供了更多信息]
让我们看看
mItemCount
声明了成员变量……嗯,这个变量好像是从AdapterView
一路继承而来的类(class)。好的,让我们找到所有类(class)中的所有作业:基本上,除了对
0
的单个分配(在 onIvalidated()
方法中,可以忽略),这个变量总是赋值给 Adapter
的getCount()
值(value)。我们可以得出结论,您得到了异常,因为有一些并发代码会改变您的
Adapter
。的数据,而它用于绘制 ListView
的内容.现在,从您的问题来看,您似乎怀疑 UI 线程上存在某种更新“拥塞”,因为消息太多......但是,请记住
Runnable.run()
的代码您发布到 UI 线程以执行的自动执行 - 每个 Runnable
从 UI 线程的事件队列中弹出,并在任何其他事件有机会处理之前运行完成。以上表示
messages
将使用新数据和 messageAdapter
进行更新将立即处理更改,并且没有其他事件可以干扰此流程(只要您的代码中的 messages.add()
和 messages.addAll()
是同步调用)。底线:调度 Runnables
的代码到 UI 线程看起来很好,它不太可能是问题的根源。此外,异常的堆栈跟踪不包含对 Adapter
的任何引用。 .到目前为止,我们总结了事实。让我们开始猜测。
我认为问题不在您发布的代码中。我猜你做了以下一个(或多个)操作,每一个都可能导致你得到的异常:
messages
与 messageAdapter
使用的数据结构相同内部。可能是您修改了messages
在代码的其他部分中,它不在 UI 线程上运行。在这种情况下,此修改可能发生在 ListView
在 UI 线程上刷新并导致异常 [“泄漏”Adapter
通常是不好的做法的 Adapter
之外的数据结构目的]。 messageAdapter
在代码的其他部分未在 UI 线程上运行。 ListView
时将事件发布到 UI 线程。尚未完成初始化。我几乎不相信是这种情况,但为了安全起见,我建议您确保在 onResume()
中注册您的听众。并在 onPause()
中取消注册编辑:
基于您的
Fragment
的代码我可以看到异常的两个可能原因:CustomResponseHandler
的方法你传给 ServerUtil.post()
将从后台线程中调用,然后从 messages
中删除对象这一事实在 onFailure()
可能是这样。 onResume()
中注册听众并在 onPause()
中注销- 这对于按钮单击监听器可能并不重要,但是当您将这些监听器传递给 Application
时会导致内存泄漏目的。 您的代码中有内存泄漏 . 如果上述方法都没有帮助,请发布
MessageAdapter
的代码和 MessageDatabase
关于android - 多条推送消息 : The content of the adapter has changed but ListView did not receive a notification,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31191259/