Android图片请求框架Universal-Image-Loader

姬昊焱
2023-12-01

github地址

Android上让人头疼的莫过于从网络上获取图片,然后显示图片,最后还要考虑到图片的回收问题,这之间只要有任何一个环节有问题都可能直接OOM。尤其在需要展示图片的列表页面,会加载大量网络上的图片,每当快速滑动列表的时候,都会很卡,甚至会因为内存溢出而崩溃。

这时就是ImageLoader的 show time
了。ImageLoader的目的是为了实现异步的网络图片加载、缓存及显示,支持多线程异步加载。

ImageLoader的工作原理是这样的:在显示图片的时候,它会先在内存中查找;如果没有,就去本地查找;如果还没有,就开一个新的线程去下载这张图片,下载成功会把图片同时缓存到内存和本地。

下载图片——将图片缓存在磁盘中——解码图片成为Bitmap——Bitmap的预处理——缓存在Bitmap内存中——Bitmap的后期处理——显示Bitmap

基于这个原理,我们可以在每一次退出一个页面的时候,把ImageLoader内存中的缓存全都清除,这样就节省了大量内存,反正下次再用到的时候从本地再取出来就是了。

此外,由于ImageLoader对图片是软引用的形式,所以内存中的图片会在内存不足的时候被系统回收(内存足够的时候不会对其进行垃圾回收)。


ImageLoader的三大组件:

ImageLoaderConfiguration——对图片缓存进行总体配置,包挎内存缓存的大小、本地缓存的大小和位置、日志、下载策略(FIFO还是LIFO)等等。

ImageLoader——我们一般使用displayImage来把URL对应的图片显示在ImageView上。

ImageLoaderOptions——在每个页面需要显示图片的地方,控制如何显示的细节,比如指定下载时的默认图(包括下载中、下载失败、URL为空等),是否将缓存放到内存或者本地磁盘。

特征:

1.多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
2.支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
3.支持图片的内存缓存,文件系统缓存或者SD卡缓存
4.支持图片下载过程的监听
5.根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
6.较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
7.提供在较慢的网络下对图片进行加载

具体使用

1.添加依赖:

implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'

2.添加权限:

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.INTERNET" />

3.初始化:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        initImageLoader(); //初始化ImageLoader
    }

    private void initImageLoader() {
        //创建默认的ImageLoader配置参数
        ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
        ImageLoader.getInstance().init(configuration);
    }
}

4.使用:

ImageLoader.getInstance().displayImage(url, mImageView);

ImageLoaderConfiguration

图片加载器ImageLoader的配置参数,使用了建造者模式,这里是直接使用了createDefault()方法创建一个默认的ImageLoaderConfiguration,当然我们还可以自己设置ImageLoaderConfiguration,设置如下:

  private void initImageLoader() {
        //创建默认的ImageLoader配置参数
        //ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);

        //自己设置ImageLoaderConfiguration

        String filePath = "";
        // 如果外部储存卡处于挂载状态则使用外部储存卡,否则使用内部储存卡
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/gamehelper";
        } else {
            filePath = Environment.getDataDirectory().getAbsolutePath() + "/gamehelper";
        }

        ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
                // 设置内存缓存的大小(当超过这个值会去回收bitmap)
                // Runtime.getRuntime().maxMemory()获取当前应用运行时内存的大小
                .memoryCache(new LruMemoryCache((int) (Runtime.getRuntime().maxMemory() / 4)))
                // 设置磁盘缓存的位置
                .diskCache(new UnlimitedDiskCache(new File(filePath)))
                .memoryCacheExtraOptions(200, 200)
                // 设置加载图片的线程池数量
                .threadPoolSize(8)
                .build();

        ImageLoader.getInstance().init(configuration);
    }

DisplayImageOptions

他可以配置一些图片显示的选项,比如图片在加载中ImageView显示的图片,是否需要使用内存缓存,是否需要使用文件缓存等。

 DisplayImageOptions options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.mipmap.ic_launcher)
                .showImageOnFail(R.mipmap.ic_launcher)
                .bitmapConfig(Bitmap.Config.RGB_565)
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .build();

 ImageLoader.getInstance().displayImage(url, mImageView, options);

配置一些常用的图片显示的选项:

package com.xiaoyehai.threadpool;

import android.graphics.Bitmap;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.display.CircleBitmapDisplayer;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer;

/**
 * Created by se7en on 16/5/30.
 */
public class ImageLoaderUtil {

    /**
     * 获取一个默认图片的配置
     *
     * @return
     */
    public static DisplayImageOptions getDefaultOption() {
        DisplayImageOptions options = new DisplayImageOptions.Builder()
                // 网络图片下载完成之前的预加载的默认图片
                .showImageOnLoading(R.mipmap.ic_launcher)
                // 网络图片下载失败后显示该默认图片
                .showImageOnFail(R.mipmap.ic_launcher)
                // 图片的质量
                .bitmapConfig(Bitmap.Config.RGB_565)
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .build();
        return options;
    }

    /**
     * 获取一个圆形图片的配置
     *
     * @return
     */
    public static DisplayImageOptions getCircleOption(Integer strokeColor, float strokeWidth) {
        DisplayImageOptions options = new DisplayImageOptions.Builder()
                // 网络图片下载完成之前的预加载的默认图片
                .showImageOnLoading(R.mipmap.ic_launcher)
                // 网络图片下载失败后显示该默认图片
                .showImageOnFail(R.mipmap.ic_launcher)
                // 图片的质量
                .bitmapConfig(Bitmap.Config.RGB_565)
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .displayer(new CircleBitmapDisplayer(strokeColor, strokeWidth))
                .build();
        return options;
    }

    /**
     * 获取一个圆角图片的配置
     *
     * @return
     */
    public static DisplayImageOptions getRoundBitmapOption(int cornerRadiusPixels, int marginPixels) {
        DisplayImageOptions options = new DisplayImageOptions.Builder()
                // 网络图片下载完成之前的预加载的默认图片
                .showImageOnLoading(R.mipmap.ic_launcher)
                // 网络图片下载失败后显示该默认图片
                .showImageOnFail(R.mipmap.ic_launcher)
                // 图片的质量
                .bitmapConfig(Bitmap.Config.RGB_565)
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .displayer(new RoundedBitmapDisplayer(cornerRadiusPixels, marginPixels))
                .build();
        return options;
    }

    /**
     * 获取一个渐显图片的配置
     *
     * @return
     */
    public static DisplayImageOptions getFadeInOption(int durationMillis, boolean animateFromNetwork,
                                                      boolean animateFromDisk, boolean animateFromMemory) {
        DisplayImageOptions options = new DisplayImageOptions.Builder()
                // 网络图片下载完成之前的预加载的默认图片
                .showImageOnLoading(R.mipmap.ic_launcher)
                // 网络图片下载失败后显示该默认图片
                .showImageOnFail(R.mipmap.ic_launcher)
                // 图片的质量
                .bitmapConfig(Bitmap.Config.RGB_565)
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .displayer(new FadeInBitmapDisplayer(durationMillis, animateFromNetwork, animateFromDisk, animateFromMemory))
                .build();
        return options;
    }
}

使用:

//获取一个圆形图片
ImageLoader.getInstance().displayImage(url,mImageView,ImageLoaderUtil.getCircleOption(Color.RED,2));

//获取一个圆角图片
ImageLoader.getInstance().displayImage(url,mImageView,ImageLoaderUtil.getRoundBitmapOption(50,20));

ImageLoader的优化

尽管ImageLoader很强大,但一直把图片缓存在内存中,会导致内存占用过高。虽然对图片的引用是软引用,软引用在内存不够的时候会被GC,但我们还是希望减少GC的次数,所以要经常手动清理ImageLoader中的缓存。

我们在上面MainActivity的 onDestroy()生命周期方法中,执行了ImageLoader的clearMemoryCache方法,以确保页面销毁时,把为了显示这个页面而增加的内存缓存清除。这样,即使到了下个页面要复用之前加载过的图片,虽然内存中没有了,根据ImageLoader的缓存策略,还是可以在本地磁盘上找到:

@Override
    protected void onDestroy() {
        // 回收该页面缓存在内存中的图片
        imageLoader.clearMemoryCache();
        super.onDestroy();
    }

ImageLoaderConfiguration的配置参数:

File cacheDir = StorageUtils.getCacheDirectory(context);  //缓存文件夹路径  
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)  
        .memoryCacheExtraOptions(480, 800) // default = device screen dimensions //设置缓存图片的默认尺寸,一般取设备的屏幕尺寸
        .diskCacheExtraOptions(480, 800, null)  // 本地缓存的详细信息(缓存的最大长宽),最好不要设置这个   
        .taskExecutor(...)  
        .taskExecutorForCachedImages(...)  
        .threadPoolSize(3) // default  线程池内加载的数量  
        .threadPriority(Thread.NORM_PRIORITY - 2) // default 设置当前线程的优先级  
        .tasksProcessingOrder(QueueProcessingType.FIFO) // default  
        .denyCacheImageMultipleSizesInMemory()  
        .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) //可以通过自己的内存缓存实现  
        .memoryCacheSize(2 * 1024 * 1024)  // 内存缓存的最大值  
        .memoryCacheSizePercentage(13) // default  
        .diskCache(new UnlimitedDiscCache(cacheDir)) // default 可以自定义缓存路径    
        .diskCacheSize(50 * 1024 * 1024) // 50 Mb sd卡(本地)缓存的最大值  
        .diskCacheFileCount(100)  // 可以缓存的文件数量   
        // default为使用HASHCODE对UIL进行加密命名, 还可以用MD5(new Md5FileNameGenerator())加密  
        .diskCacheFileNameGenerator(new HashCodeFileNameGenerator())   
        .imageDownloader(new BaseImageDownloader(context)) // default  
        .imageDecoder(new BaseImageDecoder()) // default  
        .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default  
        .writeDebugLogs() // 打印debug log  
        .build(); //开始构建   

上面的这些就是所有的选项配置,我们在项目中不需要每一个都自己设置,一般使用createDefault()创建的ImageLoaderConfiguration就能使用,然后调用ImageLoader的init()方法将ImageLoaderConfiguration参数传递进去,ImageLoader使用单例模式。

DisplayImageOptions配置参数:

DisplayImageOptions options = new DisplayImageOptions.Builder()  
        .showImageOnLoading(R.drawable.ic_stub) // 设置图片下载期间显示的图片  
        .showImageForEmptyUri(R.drawable.ic_empty) // 设置图片Uri为空或是错误的时候显示的图片  
        .showImageOnFail(R.drawable.ic_error) // 设置图片加载或解码过程中发生错误显示的图片  
        .resetViewBeforeLoading(false)  // default 设置图片在加载前是否重置、复位  
        .delayBeforeLoading(1000)  // 下载前的延迟时间  
        .cacheInMemory(false) // default  设置下载的图片是否缓存在内存中  
        .cacheOnDisk(false) // default  设置下载的图片是否缓存在SD卡中  
        .preProcessor(...)  
        .postProcessor(...)  
        .extraForDownloader(...)  
        .considerExifParams(false) // default  
        .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default 设置图片以如何的编码方式显示  
        .bitmapConfig(Bitmap.Config.ARGB_8888) // default 设置图片的解码类型  
        .decodingOptions(...)  // 图片的解码设置  
        .displayer(new SimpleBitmapDisplayer()) // default  还可以设置圆角图片new RoundedBitmapDisplayer(20)  
        .handler(new Handler()) // default  
        .build();  

获取图片下载进度

 //获取图片下载进度
        ImageLoader.getInstance().displayImage(url, mImageView, options, new SimpleImageLoadingListener(), new ImageLoadingProgressListener() {
            @Override
            public void onProgressUpdate(String imageUri, View view, int current, int total) {

            }
        });

如何同时加载大量图片:采用线程池优化高并发
如何提高加载速度:使用内存、磁盘缓存
如何避免OOM:加载的图片进行压缩,内存缓存具有相关的淘汰算法
如何避免ListView的图片错位:将要加载的图片和要显示的ImageView绑定, 在请求过程中判断该ImageView当前绑定的图片是否是当前请求的图片,不是则停止当前请求。

缺点:

此外,由于ImageLoader对图片是软引用的形式,所以内存中的图片会在内存不足的时候被系统回收(内存足够的时候不会对其进行垃圾回收)。

虽然这个框架有很好的缓存机制,有效的避免了OOM的产生,一般的情况下产生OOM的概率比较小,但是并不能保证OutOfMemoryError永远不发生,这个框架对于OutOfMemoryError做了简单的catch,保证我们的程序遇到OOM而不被crash掉,但是如果我们使用该框架经常发生OOM,我们应该怎么去改善呢?

1.减少线程池中线程的个数,在ImageLoaderConfiguration中的(.threadPoolSize)中配置,推荐配置1-5

2.在DisplayImageOptions选项中配置bitmapConfig为Bitmap.Config.RGB_565,因为默认是ARGB_8888, 使用RGB_565会比使用ARGB_8888少消耗2倍的内存

3.在ImageLoaderConfiguration中配置图片的内存缓存为memoryCache(new WeakMemoryCache()) 或者不使用内存缓存

4.在DisplayImageOptions选项中设置.imageScaleType(ImageScaleType.IN_SAMPLE_INT)或者imageScaleType(ImageScaleType.EXACTLY)

 类似资料: