Intereting Posts
Предоставляет ли веб-браузер Android возможность загружать фотографии, только что снятые с камеры? Почему служба Android выходит из строя с помощью NullPointerException? Как центрировать выравнивание текста в панели вкладок в Android Немедленно применить ui изменения Атрибут Alpha работает только на Android Lollipop, почему? Нет хорошего примера о RecyclerView и StaggeredGridLayoutManager в Android Docs API Android Backup Установка тега на повторный набор для отмены по тегу позже Android Глубокая ссылка на PlayStore вместо моего приложения на некоторых устройствах Куда поместить ключ API? Ресурсы, метаданные в манифесте или статическая переменная? Определение доступной памяти на Android LinearLayout не заполняет scrollview По действию результат камеры намерение вернуть null в samsung s4 Phonegap – локальное хранилище не работает – Android Это 2012 год, люди по-прежнему используют Eclipse для Android? Как насчет эмуляторов?

Несколько push-сообщений: содержимое адаптера изменилось, но ListView не получил уведомление

Когда я получаю много push-сообщений (скажем 50) из GCM в течение 1 секунды, я получаю следующее исключение:

Java.lang.IllegalStateException: содержимое адаптера изменилось, но ListView не получил уведомление. Убедитесь, что содержимое вашего адаптера не изменено из фонового потока, но только из потока пользовательского интерфейса. [В ListView (2131427434, класс android.widget.ListView) с адаптером (класс an)] в файле 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 (Vie W.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 (Неизвестный источник) в 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.vi Ew.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 (Хореограф.java:749) на android.view.Choreographer.doCallbacks (Хореограф.java:562 ) На android.view.Choreographer.doFrame (Хореограф.java:532) на android.view.Choreographer $ FrameDisplayEventReceiver.run (Хореограф.java:735) на android.os.Handler.handleCallback (Handler.java:725) на android .os.Handler.dispatchMessage (Handler.java:92) на android.os.Looper.loop (Looper.java:137) в android.app.ActivityThread.main (ActivityThread.java:5171) в java.lang.reflect. M Thisod.invokeNative (Native Method) в 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) в dalvik.system.NativeStart.main (собственный метод)

Я уже пытался исправить это, поставив ОБОИХ 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(); } }); } }); 

EDIT: Я, вероятно, пропустил очень важную часть кода, который я забыл сам:

 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 или есть лучший способ?

EDIT 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

Solutions Collecting From Web of "Несколько push-сообщений: содержимое адаптера изменилось, но ListView не получил уведомление"

Прежде всего, давайте посмотрим на код в 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 – сообщение было более информативным в сентябре `13]

Давайте посмотрим, где mItemCount переменная-член mItemCount … Хм, эта переменная кажется наследуемой полностью от класса AdapterView . Итак, давайте найдем все назначения во всех классах:

Введите описание изображения здесь

В принципе, за исключением одного присваивания 0 (который находится в onIvalidated() и может быть проигнорирован), эта переменная всегда назначается значению getCount() Adapter .

Мы можем заключить, что вы получаете исключение, потому что есть некоторый параллельный код, который изменяет данные вашего Adapter , когда он используется для рисования содержимого ListView .

Теперь, исходя из вашего вопроса, похоже, что вы подозреваете, что существует какая-то «перегрузка» обновлений в потоке пользовательского интерфейса, потому что сообщений слишком много … Однако давайте иметь в виду, что код Runnable.run() который Вы отправляете в поток пользовательского интерфейса для выполнения, выполняет атомарно – каждый Runnable выгружается из очереди событий потока пользовательского интерфейса и запускается до завершения, прежде чем какое-либо другое событие получит возможность обработки.

Вышеизложенное означает, что messages будут обновляться новыми данными, а messageAdapter будет обрабатывать изменения немедленно, и никакое другое событие не может помешать этому потоку (до тех пор, пока messages.add() и messages.addAll() в вашем коде являются синхронными вызовами ). Итог: код, который отправляет Runnables в поток пользовательского интерфейса, выглядит нормально, и маловероятно, что он является источником проблемы. Кроме того, трассировка стека исключений не содержит ссылок на Adapter .

До сих пор мы обобщали факты. Давайте начнем догадки.

Я думаю, что проблема не в коде, который вы опубликовали. Я думаю, вы делаете одно (или более) из следующих действий, каждое из которых может привести к тому, что вы получили:

  1. Я предполагаю, что messages – это те же структуры данных, которые используются messageAdapter внутренне. Возможно, вы модифицируете messages в другой части кода, которая не работает в потоке пользовательского интерфейса. В этом случае эта модификация может произойти, когда ListView обновляется в потоке пользовательского интерфейса и приводит к исключению [обычно это плохая практика «утечки» структуры данных Adapter вне объекта Adapter ].
  2. Аналогичным образом, вы можете случайно манипулировать messageAdapter в других частях кода, который не работает в потоке пользовательского интерфейса.
  3. Возможно, вы отправляете события в поток пользовательского интерфейса, пока ListView еще не завершил свою инициализацию. Я почти не верю, что это так, но для того, чтобы быть в безопасности, я предлагаю вам убедиться, что вы зарегистрируете своих слушателей в onResume() и onPause() регистрацию в onPause()

РЕДАКТИРОВАТЬ:

Основываясь на коде вашего Fragment я вижу две возможные причины исключения:

  1. Если методы CustomResponseHandler вы передаете ServerUtil.post() будут вызваны из фоновых потоков, тогда может произойти тот факт, что вы удаляете объект из messages в onFailure() .
  2. Как я уже сказал, – регистрировать слушателей в onResume() и onPause() регистрацию в onPause() – это может быть не важно для прослушивателей кнопок, но это вызывает утечку памяти при передаче этих слушателей объекту Application . У вас есть утечки памяти в коде .

Если ни одно из вышеперечисленных действий не помогает, MessageAdapter код MessageAdapter и MessageDatabase

  1. Создайте экземпляр адаптера один раз при запуске действия или фрагментируйте что-то вроде этого:

MessageAdapter = новый MessageAdapter (getActivity (), R.layout.list_message_item, сообщения); listView.setAdapter (messageAdapter);

Затем напишите свой класс адаптера сообщений

Класс адаптера сообщения:

 private ArrayList<Message> mMessageList; // constructor MessageAdapter(Activity activity,int layoutId, ArrayList<Message> messageList){ .. updateMessageList(messageList); } public void updateMessageList(ArrayList<Message> newMessageList){ // if First time this method is called then if(mMessageList==null){ mMessageList=new ArrayList<Message>(); } //Add new messages to message list if(null!=newMessageList && newMessageList.size()>0){ //add new messages mMessageList.addAll(newMessageList); //Sort you message list Collections.sort(mMessageList); } //update list notifyDataSetChanged(); } ..... end adapter class 
  1. Теперь из класса Activity Class или класса Fragment вы можете использовать обработчик, поскольку он действует как мост между двумя потоками (например, пользовательский интерфейс и фоновый поток).
  //local message list declaration messages=new ArrayList<Message>(); //Declare handler as a Class member variable Handler globalhandler=new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == "Some identifier string") { // after 50 messages are collected it sends one update //request after one second to list adapter messageAdapter.updateMessageList(messages); // you can also fetch messages from db and update list by calling messageAdapter.updateMessageList(messages); method } } } 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()); } messages.add(new Message(-1, user, message, System.currentTimeMillis(), System .currentTimeMillis(), false, roomID, 0, true, Message.TYPE_JOINLEAVE)); //post via handler you can also send a delayed update(like 1 second) since you are receiving 50 messages in a second. globalhandler.removeMessages("Some identifier string"); globalhandler.sendEmptyMessageDelayed("Some identifier string", 1000); }); 

Надеюсь, это ответит на ваш вопрос.