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

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

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

Ответ 1

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

Ответ 2

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