У нас был обычный список в котором было 2 типа ячеек: некликабельные категории и кликабельные ячейки
Random пикча с подкатегориями
Адаптер который мы использовали можно увидеть здесь:
github.com/siyusong/foodtruck-master-android/blob/master/src/com/foodtruckmaster/android/adapter/SeparatedListAdapter.java
Данные загружались с сервера, отображались в ListView, при нажатии на ячейку открывался отдельный экран с подробным описанием.
Для обработки нажатий использовали AdapterView.OnItemClickListener. Наши адаптеры в getItem возвращали объекты, которые передавались дальше на экраны детального описания.
Обработка нажатий делалась так:
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Description desc = parent.getItemAtPosition(position); DescriptionActivity.open(context, desc); }
В crashlytics начали появляться крэши ClassCastException(String -> Description). Это означало что на некликабельные подзаголовки в списках все таки кликнули и вместо объекта Description мы получили String. На некликабельные ячейки можно кликнуть используя performItemClick, но такие методы мы не использовали и крэши были на всех экранах со списками и подзаголовками, хоть их было и немного.
Дальше мы будем копаться в исходниках 4.2.2
AbsListView, метод onTouchEvent
case MotionEvent.ACTION_UP: { switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: ... final AbsListView.PerformClick performClick = mPerformClick; ... if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { ... if (mTouchModeReset != null) { removeCallbacks(mTouchModeReset); } mTouchModeReset = new Runnable() { @Override public void run() { mTouchMode = TOUCH_MODE_REST; child.setPressed(false); setPressed(false); if (!mDataChanged) { performClick.run(); } } }; if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { ... postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration()); } ... return true; } ... }
В исходники android без пива лучше не лезть, видимо разработчики ос руководствовались тем же принципом.
Здесь видим что если мы кликнули на ячейку списка и она enabled, то вызываем PefrormClick через определенный интервал. В android 4.2.2 этот интервал 64 мс.
Так выглядит Runnable PerformClick
private class PerformClick extends WindowRunnnable implements Runnable { int mClickMotionPosition; public void run() { // The data has changed since we posted this action in the event queue, // bail out before bad things happen if (mDataChanged) return; final ListAdapter adapter = mAdapter; final int motionPosition = mClickMotionPosition; if (adapter != null && mItemCount > 0 && motionPosition != INVALID_POSITION && motionPosition < adapter.getCount() && sameWindow()) { final View view = getChildAt(motionPosition - mFirstPosition); // If there is no view, something bad happened (the view scrolled off the // screen, etc.) and we should cancel the click if (view != null) { performItemClick(view, motionPosition, adapter.getItemId(motionPosition)); } } } }
Этот runnable вызывает performItemClick, где уже вызывается наш OnItemClickListener. Видим, что если данные в адаптере поменялись, то ливаем. Проверяем границы адаптера и прочее. Самое интересное что если установить новый адаптер, а не поменять данные в старом, то mDataChanged будет равным false, еще стоит заметить что нет проверки на isEnabled ячейки.
Т.е. мы кликаем на ячейку, в течение 64 мс меняем адаптер, выполняется этот runnable и в итоге клик происходит не по тем данным, которые мы видели на телефоне, а по новым. Причем если в новом адаптере у ячейки isEnabled = false, то она все равно кликнется, onItemClickListener вызовется.
Так в строке
Description desc = parent.getItemAtPosition(position);
мы чудесным образом получали ClassCastException
Решение:
очевидно это баг ос, и самое простое решение было бы установка флага mDataChanged в true, либо очистка очереди сообщений при смене адаптера
Вывод:
Если вы кликнули на ячейку, а в этот момент загрузились новые данные с сервера и установились в список, значит вы кликнули по новым данным (если вы создавали адаптер заново)
Всегда проверяйте результат метода getItemAtPosition на null и на instanceof если у вас несколько типов ячеек и объектов item
ссылка на оригинал статьи http://habrahabr.ru/post/226897/
Добавить комментарий