Когда я получаю много push-сообщений (пусть говорят 50) из GCM в течение 1 секунды, я получаю следующее исключение:
java.lang.IllegalStateException: содержимое адаптера имеет изменено, но ListView не получил уведомление. Убедитесь, что содержимое вашего адаптера не изменяется из фонового потока, но только из потока пользовательского интерфейса. [в ListView (2131427434, класс android.widget.ListView) с адаптером (класс a.n)] на android.widget.ListView.layoutChildren(ListView.java:1544) в android.widget.AbsListView.onLayout(AbsListView.java:2045) в android.view.View.layout(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в 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(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в android.support.v4.view.ViewPager.onLayout(Неизвестный источник) в android.view.View.layout(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в 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(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в android.support.v4.widget.DrawerLayout.onLayout(Неизвестный источник) в android.view.View.layout(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в android.widget.FrameLayout.onLayout(FrameLayout.java:446) в android.view.View.layout(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(Неизвестно Source) в android.view.View.layout(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в android.widget.FrameLayout.onLayout(FrameLayout.java:446) в android.view.View.layout(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в 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(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в android.widget.FrameLayout.onLayout(FrameLayout.java:446) в android.view.View.layout(View.java:14255) в android.view.ViewGroup.layout(ViewGroup.java:4413) в android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1998) в android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1812) в android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1050) на android.view.ViewRootImpl $TraversalRunnable.run(ViewRootImpl.java:4560) в android.view.Choreographer $CallbackRecord.run(Choreographer.java:749) на android.view.Choreographer.doCallbacks(Хореограф .java:562) на android.view.Choreographer.doFrame(Хореограф .java:532) at android.view.Choreographer $FrameDisplayEventReceiver.run(Choreographer.java:735) на android.os.Handler.handleCallback(Handler.java:725) на android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:137) в android.app.ActivityThread.main(ActivityThread.java:5171) на java.lang.reflect.Method.invokeNative(собственный метод) в java.lang.reflect.Method.invoke(Method.java:511) в com.android.internal.os.ZygoteInit $MethodAndArgsCaller.run(ZygoteInit.java:797) в com.android.internal.os.ZygoteInit.main(ZygoteInit.java:564) at dalvik.system.NativeStart.main(собственный метод)
Я уже пытался это исправить, положив BOTH messages.add()
и notifyDataSetChanged()
внутри runOnUIThread
. Я предполагаю, что это происходит, потому что onUpdate()
моего слушателя вызывается для каждого push-сообщения. Но не следует ли решить эту проблему с помощью 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: Полный источник фрагмента, который содержит код: https://gist.github.com/ChristopherWalz/89a071b1606460e18ce7