ArrayAdapter поток безопасен в Android?Если нет, что я могу сделать, чтобы сделать это потокобезопасным? - PullRequest
12 голосов
/ 05 июня 2011

Допустим, я расширяю ArrayAdapter и в коде, где я переопределяю getView(int i, View v, ViewGroup g), я получаю текущий элемент, используя getItem(i).Могу ли я быть уверен, что getItem(i) вернет элемент, даже если другие потоки манипулируют тем же ArrayAdapter?

Я не уверен, но я думаю, чтоответ нет.Если да, то что вы предлагаете мне сделать, чтобы сделать его потокобезопасным?

Ответы [ 2 ]

29 голосов
/ 05 июня 2011

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, в своей функции выше вы можете сделать это, очистив текущие данные и добавив новыеданные на теперь пустой адаптер.Это просто больше накладных расходов, которые на самом деле ничего вам не дают.

5 голосов
/ 05 июня 2011

Адаптер массива не безопасен для потоков. Я видел его сбой из-за проблем параллелизма. Адаптер массива переведет ваш массив только в представление основного потока (GUI). Поэтому, если вы будете осторожны, когда-либо изменяете массив (добавляете или удаляете) только в главном потоке, вы можете быть уверены, что он будет запускаться только в 1 потоке.

...