Безопасный стандартный способ загрузки изображений в ListView в другом потоке?

Прежде чем задать этот вопрос, я искал и читал следующие: Ленивая загрузка изображений в ListView Android: проблема с ленивой загрузкой изображений в ListView

Моя проблема в том, что у меня есть ListView, где:

  • Каждая строка содержит ImageView, содержимое которого должно быть загружено из Интернета.
  • Представление каждой строки перерабатывается, как в списке ApiDemo14.

Что я хочу в конечном итоге:

  • Загружайте изображения лениво, только когда пользователь прокручивает их
  • Загружайте изображения в разные потоки, чтобы поддерживать отзывчивость

Мой текущий подход:

  • В методе getView() адаптера, помимо настройки других дочерних представлений, я запускаю новый поток, который загружает растровое изображение из Интернета. Когда этот поток загрузки завершается, он возвращает Bitmap для установки в ImageView (я делаю это, используя AsyncTask или Handler).
  • Поскольку я перерабатываю ImageViews, может случиться так, что я сначала хочу установить представление с Bitmap#1, а затем хочу установить его на Bitmap#2, когда пользователь прокручивает вниз. Загрузка Bitmap#1 может занять больше времени, чем Bitmap#2, поэтому Bitmap#2 в представлении может быть перезаписано. Я решаю эту проблему, сохраняя WeakHashMap, который запоминает последние Bitmap, которые я хочу установить для этого представления.

Ниже приведен несколько псевдокодов для моего текущего подхода. Я опустил другие детали, такие как кэширование, просто для ясности.

public class ImageLoader {

    // keeps track of the last Bitmap we want to set for this ImageView
    private static final WeakHashMap<ImageView, AsyncTask> assignments
                                    = new WeakHashMap<ImageView, AsyncTask>();

    /** Asynchronously sets an ImageView to some Bitmap loaded from the internet */
    public static void setImageAsync(final ImageView imageView, final String imageUrl) {
        // cancel whatever previous task
        AsyncTask oldTask = assignments.get(imageView);
        if (oldTask != null) {
            oldTask.cancel(true);
        }

        // prepare to launch a new task to load this new image
        AsyncTask<String, Integer, Bitmap> newTask = new AsyncTask<String, Integer, Bitmap>() {

            protected void onPreExecute() {
                // set ImageView to some "loading..." image
            }

            protected Bitmap doInBackground(String... urls) {
                return loadFromInternet(imageUrl);
            }

            protected void onPostExecute(Bitmap bitmap) {
                // set Bitmap if successfully loaded, or an "error" image
                if (bitmap != null) {
                    imageView.setImageBitmap(bitmap);
                } else {
                    imageView.setImageResource(R.drawable.error);
                }
            }
        };
        newTask.execute();

        // mark this as the latest Bitmap we want to set for this ImageView
        assignments.put(imageView, newTask);
    }

    /** returns (Bitmap on success | null on error) */
    private Bitmap loadFromInternet(String imageUrl) {}
}

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

  • Есть ли какой-либо риск, когда поток загрузки снова вызывает ImageView позже, когда действие уже уничтожено?
  • Кроме того, AsyncTask имеет некоторый глобальный пул потоков, поэтому, если длительные задачи не будут отменены, когда они больше не нужны, я могу в конечном итоге тратить время на загрузку вещей, которые пользователи
    не видят. Мой нынешний план хранения этой вещи в глобальном масштабе слишком уродлив и может в конечном итоге привести к некоторым утечкам, которые я не понимаю. Вместо того, чтобы делать ImageLoader таким синглтоном, я думаю создать отдельные объекты ImageLoader для разных Activities, а затем, когда Activity будет уничтожен, все его AsyncTask будут отменены. Это слишком неудобно?

В любом случае, мне интересно, есть ли безопасный и стандартный способ сделать это в Android. Кроме того, я не знаю iPhone, но есть ли там подобная проблема и есть ли у них стандартный способ сделать такую ​​​​задачу?

Большое спасибо.


person Phil    schedule 22.05.2010    source источник


Ответы (1)


Я решаю эту проблему, поддерживая WeakHashMap, который запоминает последнее растровое изображение, которое я хочу установить для этого представления.

Я применил подход, прикрепив URL-адрес желаемого изображения к ImageView через setTag(). Затем, когда я загружаю изображение, я дважды проверяю URL-адрес ImageView — если он отличается от того, который я только что загрузил, я не обновляю ImageView, потому что он был переработан. Я просто кеширую.

Есть ли какой-либо риск, когда поток загрузки снова вызывает ImageView позже, когда действие уже уничтожено?

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

Вместо того, чтобы делать ImageLoader таким синглтоном, я думаю о создании отдельных объектов ImageLoader для разных действий, а затем, когда действие будет уничтожено, все его AsyncTask будут отменены. Это слишком неудобно?

Отменить AsyncTask не очень просто, если он уже запущен. Я бы просто позволил ему работать до конца.

В идеале избегайте синглетонов. Либо используйте Service, либо передайте ImageLoader следующему экземпляру вашей активности через onRetainNonConfigurationInstance() (например, isFinishing() — это false в onDestroy(), так что это чередование).

person CommonsWare    schedule 22.05.2010
comment
Я делаю что-то подобное, хотя и использую Thread и Handler. Проблема возникает, когда пользователь выбрасывает список... возникает множество потоков, и в конечном итоге Android принудительно закрывает действие. Итак, я использую переменную-член mThread и прерываю ее в bindView моего адаптера. Но по какой-то причине эти потоки перестают вызываться после того, как список получает бросок. В режиме отладки все так тормозит, что проблема не возникает, поэтому не могу понять, почему перестает работать. Был ли у вас метод предотвращения появления множества потоков в одно мгновение? - person Tenfour04; 21.02.2011