Вопрос: Ленивая загрузка изображений в ListView


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

Общее количество изображений не фиксировано.


1710


источник


Ответы:


Вот что я создал для хранения изображений, которые в настоящее время отображается моим приложением. Обратите внимание, что используемый здесь объект «Журнал» - это моя настраиваемая оболочка вокруг конечного класса журнала внутри Android.

package com.wilson.android.library;

/*
 Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/
import java.io.IOException;

public class DrawableManager {
    private final Map<String, Drawable> drawableMap;

    public DrawableManager() {
        drawableMap = new HashMap<String, Drawable>();
    }

    public Drawable fetchDrawable(String urlString) {
        if (drawableMap.containsKey(urlString)) {
            return drawableMap.get(urlString);
        }

        Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
        try {
            InputStream is = fetch(urlString);
            Drawable drawable = Drawable.createFromStream(is, "src");


            if (drawable != null) {
                drawableMap.put(urlString, drawable);
                Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
                        + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
                        + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
            } else {
              Log.w(this.getClass().getSimpleName(), "could not get thumbnail");
            }

            return drawable;
        } catch (MalformedURLException e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            return null;
        } catch (IOException e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            return null;
        }
    }

    public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
        if (drawableMap.containsKey(urlString)) {
            imageView.setImageDrawable(drawableMap.get(urlString));
        }

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                imageView.setImageDrawable((Drawable) message.obj);
            }
        };

        Thread thread = new Thread() {
            @Override
            public void run() {
                //TODO : set imageView to a "pending" image
                Drawable drawable = fetchDrawable(urlString);
                Message message = handler.obtainMessage(1, drawable);
                handler.sendMessage(message);
            }
        };
        thread.start();
    }

    private InputStream fetch(String urlString) throws MalformedURLException, IOException {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpGet request = new HttpGet(urlString);
        HttpResponse response = httpClient.execute(request);
        return response.getEntity().getContent();
    }
}

1002



я сделал простая демонстрация ленивого списка (расположенный в GitHub) с изображениями. Это может быть полезно кому-то. Он загружает изображения в фоновый поток. Изображения кэшируются на SD-карте и в памяти. Реализация кэша очень проста и достаточно для демонстрации. Я декодирую изображения с помощью inSampleSize для уменьшения потребления памяти. Я также стараюсь правильно обрабатывать переработанные виды.

Alt text


980



Я рекомендую инструмент с открытым исходным кодом Универсальный загрузчик изображений , Первоначально он основан на проекте Федора Власова LazyList и с тех пор значительно улучшилось.

  • Загрузка многопоточного изображения
  • Возможность широкой настройки конфигурации ImageLoader (исполнители потоков, downlaoder, декодер, кеш памяти и диска, параметры отображения изображения и другие)
  • Возможность кэширования изображений в памяти и / или на файловой системе устройства (или на SD-карте)
  • Возможность «прослушать» процесс загрузки
  • Возможность настройки каждого вызова изображения с отдельными параметрами
  • Поддержка виджета
  • Поддержка Android 2.0+


527



Многопоточность для производительности , учебник Жиля Дебунна.

Это из блога разработчиков Android. В предлагаемом коде используется:

  • AsyncTasks,
  • Трудный, ограниченный размер, FIFO cache,
  • Мягкий, легкий garbage collectкэш.
  • заполнитель Drawableпока вы загружаете.

enter image description here


146



Обновление: обратите внимание, что этот ответ сейчас довольно неэффективен. Коллекционер мусора агрессивно действует на SoftReference и WeakReference, поэтому этот код НЕ подходит для новых приложений. (Вместо этого попробуйте библиотеки, например Универсальный загрузчик изображений в других ответах).

Спасибо Джеймсу за код и Бао-Лонг за предложение использовать SoftReference. Я внедрил изменения SoftReference в код Джеймса. К сожалению, SoftReferences заставил мои изображения слишком быстро собирать мусор. В моем случае это было прекрасно без материала SoftReference, потому что мой размер списка ограничен, а мои изображения малы.

Обсуждается год назад относительно SoftReferences для групп google: ссылка на нить , В качестве решения слишком ранней коллекции мусора они предполагают возможность ручной настройки размера кучи VM с помощью dalvik.system.VMRuntime.setMinimumHeapSize (), что для меня не очень привлекательно.

public DrawableManager() {
    drawableMap = new HashMap<String, SoftReference<Drawable>>();
}

public Drawable fetchDrawable(String urlString) {
    SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
    if (drawableRef != null) {
        Drawable drawable = drawableRef.get();
        if (drawable != null)
            return drawable;
        // Reference has expired so remove the key from drawableMap
        drawableMap.remove(urlString);
    }

    if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
    try {
        InputStream is = fetch(urlString);
        Drawable drawable = Drawable.createFromStream(is, "src");
        drawableRef = new SoftReference<Drawable>(drawable);
        drawableMap.put(urlString, drawableRef);
        if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
                + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
                + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
        return drawableRef.get();
    } catch (MalformedURLException e) {
        if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        return null;
    } catch (IOException e) {
        if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        return null;
    }
}

public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
    SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
    if (drawableRef != null) {
        Drawable drawable = drawableRef.get();
        if (drawable != null) {
            imageView.setImageDrawable(drawableRef.get());
            return;
        }
        // Reference has expired so remove the key from drawableMap
        drawableMap.remove(urlString);
    }

    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            imageView.setImageDrawable((Drawable) message.obj);
        }
    };

    Thread thread = new Thread() {
        @Override
        public void run() {
            //TODO : set imageView to a "pending" image
            Drawable drawable = fetchDrawable(urlString);
            Message message = handler.obtainMessage(1, drawable);
            handler.sendMessage(message);
        }
    };
    thread.start();
}

101



Пикассо

Используйте библиотеку Пикассо Джейка Уортона. (A Perfect Image Loading Library от разработчика ActionBarSherlock)

Мощная библиотека загрузки и кэширования изображений для Android.

Изображения добавляют столь необходимый контекст и визуальное чутье для приложений Android. Picasso позволяет без проблем загружать изображения в приложение - часто в одной строке кода!

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

Многие распространенные ошибки загрузки изображений на Android автоматически обрабатываются Picasso:

Обработка утилиты ImageView и отмена загрузки в адаптере. Комплексные преобразования изображений с минимальным использованием памяти. Автоматическое кэширование памяти и дисков.

Библиотека Пикассо Джейка Уортона

скольжение

Glide - это быстрая и эффективная среда для управления носителями с открытым исходным кодом для Android, которая включает декодирование мультимедиа, память и кеширование дисков, а также объединение ресурсов в простой и удобный интерфейс.

Glide поддерживает выборку, декодирование и отображение видеопотоков, изображений и анимированных GIF-файлов. Glide включает гибкий api, который позволяет разработчикам подключаться практически к любому сетевому стеклу. По умолчанию Glide использует собственный стек, основанный на HttpUrlConnection, но также включает в себя библиотеки утилиты, подключаемые к проекту Volley Google или библиотеке OkHttp Square.

Glide.with(this).load("http://goo.gl/h8qOq7").into(imageView);

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

Библиотека загрузки изображений Glide

Фреска от Facebook

Fresco - мощная система для отображения изображений в приложениях Android.

Fresco заботится о загрузке и отображении изображений, поэтому вам не нужно. Он загрузит изображения из сети, локального хранилища или локальных ресурсов и покажет местозаполнитель до появления изображения. Он имеет два уровня кеша; один в памяти и другой во внутреннем хранилище.

Fresco Github

В Android 4.x и ниже Fresco помещает изображения в специальную область памяти Android. Это позволяет вашему приложению работать быстрее - и страдать от ужасного OutOfMemoryError гораздо реже.

Фресковая документация


83



Высокопроизводительный загрузчик - после изучения предложенных здесь методов, я использовал Решение Бена с некоторыми изменениями -

  1. Я понял, что работа с чертежами быстрее, чем с растровыми изображениями, поэтому вместо этого я использую drawables

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

  3. Чтобы открыть InputStream, я использовал java.net.URLConnection, который позволяет мне использовать веб-кеш (вам нужно сначала установить кеш ответа, но это еще одна история)

Мой код:

import java.util.Map; 
import java.util.HashMap; 
import java.util.LinkedList; 
import java.util.Collections; 
import java.util.WeakHashMap; 
import java.lang.ref.SoftReference; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; 
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import android.os.Handler;
import android.os.Message;
import java.io.InputStream;
import java.net.MalformedURLException; 
import java.io.IOException; 
import java.net.URL;
import java.net.URLConnection;

public class DrawableBackgroundDownloader {    

private final Map<String, SoftReference<Drawable>> mCache = new HashMap<String, SoftReference<Drawable>>();   
private final LinkedList <Drawable> mChacheController = new LinkedList <Drawable> ();
private ExecutorService mThreadPool;  
private final Map<ImageView, String> mImageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());  

public static int MAX_CACHE_SIZE = 80; 
public int THREAD_POOL_SIZE = 3;

/**
 * Constructor
 */
public DrawableBackgroundDownloader() {  
    mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);  
}  


/**
 * Clears all instance data and stops running threads
 */
public void Reset() {
    ExecutorService oldThreadPool = mThreadPool;
    mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
    oldThreadPool.shutdownNow();

    mChacheController.clear();
    mCache.clear();
    mImageViews.clear();
}  

public void loadDrawable(final String url, final ImageView imageView,Drawable placeholder) {  
    mImageViews.put(imageView, url);  
    Drawable drawable = getDrawableFromCache(url);  

    // check in UI thread, so no concurrency issues  
    if (drawable != null) {  
        //Log.d(null, "Item loaded from mCache: " + url);  
        imageView.setImageDrawable(drawable);  
    } else {  
        imageView.setImageDrawable(placeholder);  
        queueJob(url, imageView, placeholder);  
    }  
} 


private Drawable getDrawableFromCache(String url) {  
    if (mCache.containsKey(url)) {  
        return mCache.get(url).get();  
    }  

    return null;  
}

private synchronized void putDrawableInCache(String url,Drawable drawable) {  
    int chacheControllerSize = mChacheController.size();
    if (chacheControllerSize > MAX_CACHE_SIZE) 
        mChacheController.subList(0, MAX_CACHE_SIZE/2).clear();

    mChacheController.addLast(drawable);
    mCache.put(url, new SoftReference<Drawable>(drawable));

}  

private void queueJob(final String url, final ImageView imageView,final Drawable placeholder) {  
    /* Create handler in UI thread. */  
    final Handler handler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            String tag = mImageViews.get(imageView);  
            if (tag != null && tag.equals(url)) {
                if (imageView.isShown())
                    if (msg.obj != null) {
                        imageView.setImageDrawable((Drawable) msg.obj);  
                    } else {  
                        imageView.setImageDrawable(placeholder);  
                        //Log.d(null, "fail " + url);  
                    } 
            }  
        }  
    };  

    mThreadPool.submit(new Runnable() {  
        @Override  
        public void run() {  
            final Drawable bmp = downloadDrawable(url);
            // if the view is not visible anymore, the image will be ready for next time in cache
            if (imageView.isShown())
            {
                Message message = Message.obtain();  
                message.obj = bmp;
                //Log.d(null, "Item downloaded: " + url);  

                handler.sendMessage(message);
            }
        }  
    });  
}  



private Drawable downloadDrawable(String url) {  
    try {  
        InputStream is = getInputStream(url);

        Drawable drawable = Drawable.createFromStream(is, url);
        putDrawableInCache(url,drawable);  
        return drawable;  

    } catch (MalformedURLException e) {  
        e.printStackTrace();  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  

    return null;  
}  


private InputStream getInputStream(String urlString) throws MalformedURLException, IOException {
    URL url = new URL(urlString);
    URLConnection connection;
    connection = url.openConnection();
    connection.setUseCaches(true); 
    connection.connect();
    InputStream response = connection.getInputStream();

    return response;
}
}

77



I have followed this Android Training and I think it does an excellent job at downloading images without blocking the main UI. It also handles caching and dealing with scrolling through many images: Loading Large Bitmaps Efficiently


74



1. Picasso allows for hassle-free image loading in your application—often in one line of code!

Use Gradle:

implementation 'com.squareup.picasso:picasso:2.71828'

Just one line of code!

Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);

Many common pitfalls of image loading on Android are handled automatically by Picasso

  • Handling ImageView recycling and download cancelation in an adapter.
  • Complex image transformations with minimal memory use.
  • Automatic memory and disk caching.

enter image description here

2. Glide An image loading and caching library for Android focused on smooth scrolling

Use Gradle:

repositories {
  mavenCentral() // jcenter() works as well because it pulls from Maven Central
}

dependencies {
  compile 'com.github.bumptech.glide:glide:3.7.0'
  compile 'com.android.support:support-v4:19.1.0'
}

// For a simple view:

@Override public void onCreate(Bundle savedInstanceState) {
  ...
  ImageView imageView = (ImageView) findViewById(R.id.my_image_view);

  Glide.with(this).load("http://i.imgur.com/DvpvklR.png").into(imageView);
}

// For a simple image list:

 @Override public View getView(int position, View recycled, ViewGroup container) {
      final ImageView myImageView;
      if (recycled == null) {
        myImageView = (ImageView) inflater.inflate(R.layout.my_image_view, container, false);
      } else {
        myImageView = (ImageView) recycled;
      }

      String url = myUrls.get(position);

      Glide
        .with(myFragment)
        .load(url)
        .centerCrop()
        .placeholder(R.drawable.loading_spinner)
        .crossFade()
        .into(myImageView);

      return myImageView;
}

54



I've written a tutorial that explains how to do lazy-loading of images in a listview. I go into some detail about the issues of recycling and concurrency. I also use a fixed thread pool to prevent spawning a lot of threads.

Lazy loading of images in Listview Tutorial


49