浅析Android 开源框架ImageLoader的用法

Posted 小威少威

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析Android 开源框架ImageLoader的用法相关的知识,希望对你有一定的参考价值。

一、前言
android开发中,会经常涉及到显示图片的相关操作,在网上查阅资料,ImageLoader得到大家广泛的使用,本篇文章针对初使用者的一个向导,同时也是自己使用该框架的一个总结,主要包含:

   ## 源码浅析 ##
   ## 使用教程 ##
   ## 用法总结及demo下载 ##

二、源码浅析
从用法来看,我们在使用该框架的时候,会先做一个初始化操作(一般在Application中),

ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.init(ImageLoaderConfiguration imageLoaderConfiguration );

我们在源码进入该方法查看:

private volatile static ImageLoader instance;

    /** Returns singleton class instance */
    public static ImageLoader getInstance() {
        if (instance == null) {
            synchronized (ImageLoader.class) {
                if (instance == null) {
                    instance = new ImageLoader();
                }
            }
        }
        return instance;
    }

    protected ImageLoader() {
    }

    /**
     * Initializes ImageLoader instance with configuration.<br />
     * If configurations was set before ( {@link #isInited()} == true) then this method does nothing.<br />
     * To force initialization with new configuration you should {@linkplain #destroy() destroy ImageLoader} at first.
     *
     * @param configuration {@linkplain ImageLoaderConfiguration ImageLoader configuration}
     * @throws IllegalArgumentException if <b>configuration</b> parameter is null
     */
    public synchronized void init(ImageLoaderConfiguration configuration) {
        if (configuration == null) {
            throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
        }
        if (this.configuration == null) {
            L.d(LOG_INIT_CONFIG);
            engine = new ImageLoaderEngine(configuration);
            this.configuration = configuration;
        } else {
            L.w(WARNING_RE_INIT_CONFIG);
        }
    }

获取实例对象的方式用了单例模式,这里我们主要看一下init()这个方法:
根据注释的文档可知初始化时的一些注意事项,该方法主要对engine做初始化:

engine = new ImageLoaderEngine(configuration);

我们继续进入ImageLoaderEngine中看下这个类的构造方法:

ImageLoaderEngine(ImageLoaderConfiguration configuration) {
        this.configuration = configuration;

        taskExecutor = configuration.taskExecutor;
        taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;

        taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
    }

可知,该库是通过Executor对象将线程放入线程池中运行的,此构造方法里面初始化了taskExecutorForCachedImages、taskExecutor、taskDistributor这三个对象,它们都是Executor接口的实例。关于Executor接口,大家可以上网搜一下相关的知识,这里给个参考链接:

http://blog.csdn.net/minword/article/details/20565867

关于初始化的操作我们先看到这里,主要是明白了它是通过Executor来处理任务的。

接下来看下显示图片的操作,同样,我们看下源码:

/**
     * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.<br />
     * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call
     *
     * @param uri              Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
     * @param imageAware       {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view}
     *                         which should display image
     * @param options          {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
     *                         decoding and displaying. If <b>null</b> - default display image options
     *                         {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
     *                         from configuration} will be used.
     * @param listener         {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
     *                         events on UI thread if this method is called on UI thread.
     * @param progressListener {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
     *                         Listener} for image loading progress. Listener fires events on UI thread if this method
     *                         is called on UI thread. Caching on disk should be enabled in
     *                         {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions options} to make
     *                         this listener work.
     * @throws IllegalStateException    if {@link #init(ImageLoaderConfiguration)} method wasn't called before
     * @throws IllegalArgumentException if passed <b>imageAware</b> is null
     */
    public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        checkConfiguration();
        if (imageAware == null) {
            throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
        }
        if (listener == null) {
            listener = emptyListener;
        }
        if (options == null) {
            options = configuration.defaultDisplayImageOptions;
        }

        if (TextUtils.isEmpty(uri)) {
            engine.cancelDisplayTaskFor(imageAware);
            listener.onLoadingStarted(uri, imageAware.getWrappedView());
            if (options.shouldShowImageForEmptyUri()) {
                imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
            } else {
                imageAware.setImageDrawable(null);
            }
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
            return;
        }

        ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

        listener.onLoadingStarted(uri, imageAware.getWrappedView());

        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
        if (bmp != null && !bmp.isRecycled()) {
            L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

            if (options.shouldPostProcess()) {
                ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                        options, listener, progressListener, engine.getLockForUri(uri));
                ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                        defineHandler(options));
                if (options.isSyncLoading()) {
                    displayTask.run();
                } else {
                    engine.submit(displayTask);
                }
            } else {
                options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
            }
        } else {
            if (options.shouldShowImageOnLoading()) {
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {
                imageAware.setImageDrawable(null);
            }

            ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                    options, listener, progressListener, engine.getLockForUri(uri));
            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                    defineHandler(options));
            if (options.isSyncLoading()) {
                displayTask.run();
            } else {
                engine.submit(displayTask);
            }
        }
    }

可知,我们将显示图片的任务加入到线程池中,之后ImageAware进行工作,
这里看下这段代码:

if (options == null) {
            options = configuration.defaultDisplayImageOptions;
}

其实就是我们设置的图片显示失败时显示的图片,这边是我们设置null时,使用了默认的图片。
回到刚才,我们发现该类读取了很多的配置参数信息,其实是我们初始化时,配置的参数,主要是配置缓存相关信息,见第二部分,使用教程。最后我们通过ImageLoaderEngine来执行显示图片的任务。

engine.submit(displayTask);

submit内部方法(ImageLoaderEngine类中):

/** Submits task to execution pool */
    void submit(ProcessAndDisplayImageTask task) {
        initExecutorsIfNeed();
        taskExecutorForCachedImages.execute(task);
    }

    private void initExecutorsIfNeed() {
        if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) {
            taskExecutor = createTaskExecutor();
        }
        if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages)
                .isShutdown()) {
            taskExecutorForCachedImages = createTaskExecutor();
        }
    }

以上是主要的一些源码浅析,想了解更多的可以自行翻阅源码查看。接下来我们介绍一下简单的使用教程。

三、使用教程

首先获取该库:我用的是gradle配置:

在gradle中加入:

compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3'

Maven 配置:

<dependency>
    <groupid>com.nostra13.universalimageloader</groupid>
    <artifactId>universal-image-loader</artifactid>
    <version>1.9.3</version>
</dependency>

在AndroidManifest.xml中加入(涉及到图片缓存读写路径,访问网络操作):

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

之后我们就可以代码编写。
首先在activity_main中设置一个显示图片的控件:

  <ImageView
        android:id="@+id/user_image"
        android:layout_width="64dp"
        android:layout_height="64dp"/>

在MainActivity中使用ImageLoader加载一张网络图片:

imageView = (ImageView) findViewById(R.id.user_image);
ImageLoaderUtil.init(this);
String url_image = "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWsK1HF6hhy/it/u=3266622398,4228444443&fm=116&gp=0.jpg";
ImageLoaderUtil.displayImage(url_image,imageView,ImageLoaderUtil.getAvatarDisplayOptions());

显示的效果图:
正常状态下显示的图片

当网络路径不存在图片时(显示预设的图片):
路径出错时显示的预设图片

ImageLoaderUtil是我自己封装的一个类:

主要我们得编写init()方法:

public static void init(Context context) {
        File cacheDir = 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(true)) // l
                .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
                .writeDebugLogs() // 打印debug log
                .build(); //开始构建
        ImageLoader.getInstance().init(config);
    }

可参照注释对应了解参数信息。

完整的ImageLoaderUtil类:

package constraintlayout.test.test.viviant.imageloadertest.util;

import android.content.Context;
import android.util.Log;
import android.widget.ImageView;

import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.HashCodeFileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.decode.BaseImageDecoder;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;

import java.io.File;

import constraintlayout.test.test.viviant.imageloadertest.R;

/**
 * 作者:viviant on 2016/6/30 09:22
 * 描述:
 */
public class ImageLoaderUtil {

    private static final String PICTURE_CACHE_DIR = "picture";
    private static String TAG = "ImageLoaderUtil";

    public static void init(Context context) {
        File cacheDir = 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(true)) // l
                .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
                .writeDebugLogs() // 打印debug log
                .build(); //开始构建
        ImageLoader.getInstance().init(config);
    }

    /**
     * 获取缓存文件
     *
     * @param context
     * @return
     */
    public final static File getCacheDirectory(Context context) {
        String cacheDir = SystemUtility.getAppCachePath();
        return createDir(cacheDir + PICTURE_CACHE_DIR);
    }

    private final static File createDir(String dir) {
        File appCacheDir = new File(dir);
        if (!appCacheDir.exists()) {
            if (!appCacheDir.mkdirs()) {
                Log.i(TAG, "createDir# Unable to create external cache directory");
                return null;
            }
        }
        return appCacheDir;
    }

    /**
     *显示出错,替换的图片
     * @return
     */
    public static DisplayImageOptions getAvatarDisplayOptions() {
        DisplayImageOptions avatarOptions = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.error)
                .showImageForEmptyUri(R.drawable.error)
                .showImageOnFail(R.drawable.error)
                .cacheInMemory(true).cacheOnDisk(true).build();
        return avatarOptions;
    }

    /**
     * 显示图片
     *
     * @param url
     * @param imageView
     * @param options
     */
    public static void displayImage(String url, ImageView imageView,
                                    DisplayImageOptions options) {
        ImageLoader.getInstance().displayImage(url, imageView, options);
    }

}

四、用法总结及源码下载:

ImageLoader的用法总的来说还是很便捷的,我们可以设置相应的参数来初始化配置,并且它的优点是应用进行大量的访问网络图片。以上是我对该框架的一些使用方法进行总结,不足之处,欢迎批评。

源码下载(AndroidStudio直接导入):
https://github.com/viviant1224/ImageLoaderTest

以上是关于浅析Android 开源框架ImageLoader的用法的主要内容,如果未能解决你的问题,请参考以下文章

Android UI-开源框架ImageLoader的完美例子

Android 开源框架 ( 九 ) 图片加载框架---ImageLoader

Android图片加载与缓存开源框架总结七部曲:Glide Picasso ImageLoader Fresco ASimpleCache等

Android 网络图片加载缓存处理库ImageLoader和Picasso

Android RoboGuice开源框架Butter Knife开源框架浅析

Android常用的图片加载库