ArrayAdapter не является потокобезопасным.ListView и другие подобные виджеты пользовательского интерфейса, которые работают с адаптером, не позволяют содержимому адаптера неожиданно изменяться на них.И это больше, чем просто из-за других потоков - вам нужно сообщить ListView об изменениях, которые вы делаете, прежде чем он попытается в следующий раз взаимодействовать с вашим адаптером.
Если вы разрешите другому потоку изменять адаптер, илиизмените его в главном потоке, но не сообщайте ListView об изменениях, прежде чем допустить что-либо еще, вы случайным образом (из-за гонок) получите исключения, выданные ListView, об неожиданном изменении адаптера.
В конкретном случаеВ случае ArrayAdapter, если вы используете API для изменения его содержимого, он позаботится о том, чтобы сообщить представлению списка об изменении.Однако вы должны внести такие изменения в основной поток, чтобы убедиться, что представление списка не пытается получить доступ к адаптеру между точкой, в которой было внесено ваше изменение, и представлению списка об этом изменении.
Если вы только вносите простые изменения в ArrayAdapter (добавляя и удаляя несколько элементов), то у вас все будет хорошо, но вы должны сделать это в главном потоке.
Для более значительных изменений (например, адаптер получает новый набор данных из-за выборки новых данных с сервера), не используйте ArrayAdapter и вместо этого реализуйте собственный подкласс BaseAdapter.ArrayAdapter предназначен для ситуаций, когда у вас есть небольшой простой довольно статичный набор данных для отображения - простых ситуаций.Для более сложных вещей вы, вероятно, будете более счастливы, просто внедрив BaseAdapter и выполнив управление данными самостоятельно.
Типичный способ обновления адаптера в этих сложных ситуациях заключается в том, что фоновый поток генерирует новый набор данных и один разкоторый затем доступен в главном потоке, он атомарно подключается к адаптеру с помощью вызова notifyDataSetChanged (), чтобы сообщить ListView, что данные изменились.
Итак, допустим, вы показываете некоторые данные, которыемассив объектов MyItem.Храните ваши данные в игровом массиве:
ArrayList<MyItem>
Реализуйте подкласс BaseAdapter, который показывает этот список:
class MyAdapter extends BaseAdapter<MyItem> {
ArrayList<MyItem> mItems;
public int getCount() {
return mItems != null ? mItems.size() : 0;
}
public MyItem getItem(int position) {
return mItems.get(i);
}
...
}
А теперь вот функция, которую вы можете реализовать на адаптере, которая можетвызываться из другого потока, чтобы предоставить новый набор данных для отображения:
final Handler mHandler = new Handler();
void setDataFromAnyThread(final ArrayList<MyItem> newData) {
// Enqueue work on mHandler to change the data on
// the main thread.
mHandler.post(new Runnable() {
@Override
public void run() {
mItems = newData;
notifyDataSetChanged();
}
});
}
Конечно, если вы используете AsyncTask для генерации данных, это уже имеет удобное средство для выполнения этой работы на главномнить.Точно так же вы можете использовать новое средство Loader, чтобы позаботиться о генерации в фоновом режиме.
И если вы все еще хотите использовать ArrayAdapter, в своей функции выше вы можете сделать это, очистив текущие данные и добавив новыеданные на теперь пустой адаптер.Это просто больше накладных расходов, которые на самом деле ничего вам не дают.