XUtils BitmapUtils 源码分析

来源:互联网 发布:csgo网络不稳定 编辑:程序博客网 时间:2024/05/16 17:24

在我们使用BitmapUtils来进行图片的加载时,是直接调用display方法来进行图片的加载。有好几个display方法,参数个数不一样,但是其实最后调用的都是如下方法

public <T extends View> void display(T container, String uri, BitmapDisplayConfig displayConfig, BitmapLoadCallBack<T> callBack) {        if (container == null) {            return;        }        if (callBack == null) {            callBack = new DefaultBitmapLoadCallBack<T>();        }        if (displayConfig == null || displayConfig == defaultDisplayConfig) {            displayConfig = defaultDisplayConfig.cloneNew();        }        // Optimize Max Size        BitmapSize size = displayConfig.getBitmapMaxSize();        displayConfig.setBitmapMaxSize(BitmapCommonUtils.optimizeMaxSizeByView(container, size.getWidth(), size.getHeight()));        container.clearAnimation();        if (TextUtils.isEmpty(uri)) {            callBack.onLoadFailed(container, uri, displayConfig.getLoadFailedDrawable());            return;        }        // start loading        callBack.onPreLoad(container, uri, displayConfig);        // find bitmap from mem cache.        Bitmap bitmap = globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);        if (bitmap != null) {            callBack.onLoadStarted(container, uri, displayConfig);            callBack.onLoadCompleted(                    container,                    uri,                    bitmap,                    displayConfig,                    BitmapLoadFrom.MEMORY_CACHE);        } else if (!bitmapLoadTaskExist(container, uri, callBack)) {            final BitmapLoadTask<T> loadTask = new BitmapLoadTask<T>(container, uri, displayConfig, callBack);            // get executor            PriorityExecutor executor = globalConfig.getBitmapLoadExecutor();            File diskCacheFile = this.getBitmapFileFromDiskCache(uri);            boolean diskCacheExist = diskCacheFile != null && diskCacheFile.exists();            if (diskCacheExist && executor.isBusy()) {                executor = globalConfig.getDiskCacheExecutor();            }            // set loading image            Drawable loadingDrawable = displayConfig.getLoadingDrawable();            callBack.setDrawable(container, new AsyncDrawable<T>(loadingDrawable, loadTask));            loadTask.setPriority(displayConfig.getPriority());            loadTask.executeOnExecutor(executor);        }    }

分析看下面:一步一步来分析:

//如果加载图片的容器为空,则直接返回,这里加载图片的容器container为View的子类,比如说:ImageView等。        if (container == null) {            return;        }
//DefaultBitmapLoadCallBack为抽象类BitmapLoadCallBack的一个实现子类,实现了BitmapLoadCallBack的加载完成、加载失败的回调函数。        if (callBack == null) {            callBack = new DefaultBitmapLoadCallBack<T>();        }

从下面代码可以看出DefaultBitmapLoadCallBack实现了父抽象类BitmapLoadCallBack的加载完成、加载失败的回调函数,同时自己实现了一个动画显示的函数,这里用到了反射机制:
通过getDeclaredMethod获取到了Animation类的“clone”方法,克隆出了一个副本对象,这里采用了原型模式。为什么要克隆出新对象:对于一个有可变属性的对象实例,如果直接对该引用进行操作,则有可能在对该引用操作时,将该对象的可变属性改动了,从而影响到了其他对于该对象实例的使用者。

public class DefaultBitmapLoadCallBack<T extends View> extends BitmapLoadCallBack<T> {    @Override    public void onLoadCompleted(T container, String uri, Bitmap bitmap, BitmapDisplayConfig config, BitmapLoadFrom from) {        this.setBitmap(container, bitmap);        Animation animation = config.getAnimation();        if (animation != null) {            animationDisplay(container, animation);        }    }    @Override    public void onLoadFailed(T container, String uri, Drawable drawable) {        this.setDrawable(container, drawable);    }    private void animationDisplay(T container, Animation animation) {        try {            Method cloneMethod = Animation.class.getDeclaredMethod("clone");            cloneMethod.setAccessible(true);            container.startAnimation((Animation) cloneMethod.invoke(animation));        } catch (Throwable e) {            container.startAnimation(animation);        }    }}

当然在BitmapLoadCallBack的回调函数中并不只有这两个,一共五个。可以看到加载完成跟失败都是抽象方法,没有默认实现,需要子实现类去实现。

public void onPreLoad(T container, String uri, BitmapDisplayConfig config) public void onLoadStarted(T container, String uri, BitmapDisplayConfig config)public void onLoading(T container, String uri, BitmapDisplayConfig config, long total, long current) public abstract void onLoadCompleted(T container, String uri, Bitmap bitmap, BitmapDisplayConfig config, BitmapLoadFrom from);public abstract void onLoadFailed(T container, String uri, Drawable drawable);

defaultDisplayConfig 为BitmapDisplayConfig的一个默认实现实例,BitmapUtils的构造方法中被初始化,defaultDisplayConfig 可以调用BitmapUtils类中的给出的方法来对该默认实例进行设置。这里没有使用该默认对象,而是通过cloneNew()方法克隆出了一个新对象。这是要将原型模式进行到底啊,这也是因为defaultDisplayConfig 是被许多的调用者给调用的,我们如果自己设置BitmapDisplayConfig时,每一张图片的display都会使用该默认配置,因此需要使用到原型模式。防止某些对象在使用该实例时,对他进行了修改。

        if (displayConfig == null || displayConfig == defaultDisplayConfig) {            displayConfig = defaultDisplayConfig.cloneNew();        }

下面再来看一下BitmapDisplayConfig这个类,即图片显示时候的配置类,看看里面有什么内容。有图片最大大小、动画、加载时图片、加载失败图片、优先级等等,该类中还实现了一个克隆函数cloneNew();

 private BitmapSize bitmapMaxSize;    private Animation animation;    private Drawable loadingDrawable;    private Drawable loadFailedDrawable;    private boolean autoRotation = false;    private boolean showOriginal = false;    private Bitmap.Config bitmapConfig = Bitmap.Config.RGB_565;    private BitmapFactory bitmapFactory;    private Priority priority;

在显示图片时,还对图片进行了长跟宽的比例进行了设置。这里optimizeMaxSizeByView的代码就不贴了。大致思想是:获取displayConfig的图片的BitmapSize 最大值,这里面包含了宽跟高,如果在displayConfig设置了该两个值,则直接返回该两个值,如果没有设置,则获取optimizeMaxSizeByView的container的布局属性,将该BitmapSize 设置为view的布局属性,比如说是ImageView的布局属性。

BitmapSize size = displayConfig.getBitmapMaxSize();displayConfig.setBitmapMaxSize(BitmapCommonUtils.optimizeMaxSizeByView(container,size.getWidth(),size.getHeight()));

同时设置了图片显示大小之后,将容器View的可能正在进行的动画取消:

container.clearAnimation();

当然接下来如果发现显示图片的uri链接为空的话,直接回调加载失败接口。

if (TextUtils.isEmpty(uri)) {      callBack.onLoadFailed(container, uri, displayConfig.getLoadFailedDrawable());      return;}

接下来就开始加载我们的图片了,我们是按照运行的步骤一步一步来分析的。
接下来首先第一步是进行图片加载前的回调:

// start loadingcallBack.onPreLoad(container, uri, displayConfig);

接下来是重头戏了,图片的加载部分。分析完下面这一部分,我们整个使用display方法进行图片的加载的流程也已经分析完了。这一部分我们单独拎出来一步步分析。

// find bitmap from mem cache.        Bitmap bitmap = globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);        if (bitmap != null) {            callBack.onLoadStarted(container, uri, displayConfig);            callBack.onLoadCompleted(                    container,                    uri,                    bitmap,                    displayConfig,                    BitmapLoadFrom.MEMORY_CACHE);        } else if (!bitmapLoadTaskExist(container, uri, callBack)) {            final BitmapLoadTask<T> loadTask = new BitmapLoadTask<T>(container, uri, displayConfig, callBack);            // get executor            PriorityExecutor executor = globalConfig.getBitmapLoadExecutor();            File diskCacheFile = this.getBitmapFileFromDiskCache(uri);            boolean diskCacheExist = diskCacheFile != null && diskCacheFile.exists();            if (diskCacheExist && executor.isBusy()) {                executor = globalConfig.getDiskCacheExecutor();            }            // set loading image            Drawable loadingDrawable = displayConfig.getLoadingDrawable();            callBack.setDrawable(container, new AsyncDrawable<T>(loadingDrawable, loadTask));            loadTask.setPriority(displayConfig.getPriority());            loadTask.executeOnExecutor(executor);        }

首先第一步,从内存缓存中获取图片。这里用到了一个新的对象实例globalConfig,从上面可以看出,globalConfig并没有在display方法中进行初始化,也就是说是属于BitmapUtils类的。我们来看看它到底是何方神圣。

Bitmap bitmap = globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);

上面说了defaultDisplayConfig 是在BitmapUtils的构造方法中被初始化的。当我们使用时,使用了原型模式,通过克隆它,为每一张图片的加载都提供了它的一个克隆体,在BitmapUtils构造方法中,初始化的不仅仅有它,还有一个非常重要的东西globalConfig 。它是何方神圣,竟然是使用getInstance来获得实例而不是通过new来构造实例,原因只有一个,globalConfig 要被所有的图片加载所使用到。

public BitmapUtils(Context context, String diskCachePath) {        if (context == null) {            throw new IllegalArgumentException("context may not be null");        }        this.context = context.getApplicationContext();        globalConfig = BitmapGlobalConfig.getInstance(this.context, diskCachePath);        defaultDisplayConfig = new BitmapDisplayConfig();    }

那BitmapGlobalConfig里面包含什么呢。这个类代码太长了,只给出一部分。我们在下面对BitmapGlobalConfig 里面的一些重要的东西进行详解。

public class BitmapGlobalConfig {    private String diskCachePath;    public final static int MIN_MEMORY_CACHE_SIZE = 1024 * 1024 * 2; // 2M    private int memoryCacheSize = 1024 * 1024 * 4; // 4MB    public final static int MIN_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10M    private int diskCacheSize = 1024 * 1024 * 50;  // 50M    private boolean memoryCacheEnabled = true;    private boolean diskCacheEnabled = true;    private Downloader downloader;    private BitmapCache bitmapCache;    private final static int DEFAULT_POOL_SIZE = 5;    private final static PriorityExecutor BITMAP_LOAD_EXECUTOR = new PriorityExecutor(DEFAULT_POOL_SIZE);    private final static PriorityExecutor DISK_CACHE_EXECUTOR = new PriorityExecutor(2);    private long defaultCacheExpiry = 1000L * 60 * 60 * 24 * 30; // 30 days    private int defaultConnectTimeout = 1000 * 15; // 15 sec    private int defaultReadTimeout = 1000 * 15; // 15 sec    private FileNameGenerator fileNameGenerator;    private BitmapCacheListener bitmapCacheListener;    private Context mContext;    private final static HashMap<String, BitmapGlobalConfig> configMap = new HashMap<String, BitmapGlobalConfig>(1);    /**     * @param context     * @param diskCachePath If null, use default appCacheDir+"/xBitmapCache"     */    private BitmapGlobalConfig(Context context, String diskCachePath) {        if (context == null) throw new IllegalArgumentException("context may not be null");        this.mContext = context;        this.diskCachePath = diskCachePath;        initBitmapCache();    }    public synchronized static BitmapGlobalConfig getInstance(Context context, String diskCachePath) {        if (TextUtils.isEmpty(diskCachePath)) {            diskCachePath = OtherUtils.getDiskCacheDir(context, "xBitmapCache");        }        if (configMap.containsKey(diskCachePath)) {            return configMap.get(diskCachePath);        } else {            BitmapGlobalConfig config = new BitmapGlobalConfig(context, diskCachePath);            configMap.put(diskCachePath, config);            return config;        }    }

可以看到这个全局配置中包含了非常多的属性,比如内存缓存大小、磁盘缓存大小等。我们可以看到, BitmapGlobalConfig中维护了两个PriorityExecutor :BITMAP_LOAD_EXECUTOR 、DISK_CACHE_EXECUTOR 。我们先来分析这个,其他的待会再说,因为第一眼看到它就觉得这是我们加载加载图片时真正的执行者。其实事实也是的,因为我们可以往回去看一下display方法的最后一步就是:

loadTask.executeOnExecutor(executor);

而executor是:

PriorityExecutor executor = globalConfig.getBitmapLoadExecutor();

所以这是重中之重。
先来看看PriorityExecutor 吧,在下面的代码中可以看到,PriorityExecutor 是继承自Executor 的,Executor 为何许人物?Executor 是java 5引入的并发编程的新货色,线程池!

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);

可以看到我们的PriorityExecutor 类中,默认的核心池大小为5。最大线程数为256,保活时间为1,时间单位为秒,保活时间是什么意思,就是说如果我们的当前线程池线程数量大于了核心池大小5,如果有线程空闲了1s,该线程就会被停止,被销毁。PriorityExecutor 实现了阻塞队列,这个后面在分析。也就是说,我们的全局配置BitmapGlobalConfig中维护了两个线程池,一个时图片加载时的执行线程池BITMAP_LOAD_EXECUTOR ,大小为5,一个是磁盘缓存线程池,大小为2。

public class PriorityExecutor implements Executor {    private static final int CORE_POOL_SIZE = 5;    private static final int MAXIMUM_POOL_SIZE = 256;    private static final int KEEP_ALIVE = 1;    private static final ThreadFactory sThreadFactory = new ThreadFactory() {        private final AtomicInteger mCount = new AtomicInteger(1);        @Override        public Thread newThread(Runnable r) {            return new Thread(r, "PriorityExecutor #" + mCount.getAndIncrement());        }    };    private final BlockingQueue<Runnable> mPoolWorkQueue = new PriorityObjectBlockingQueue<Runnable>();    private final ThreadPoolExecutor mThreadPoolExecutor;    public PriorityExecutor() {        this(CORE_POOL_SIZE);    }    public PriorityExecutor(int poolSize) {        mThreadPoolExecutor = new ThreadPoolExecutor(                poolSize,                MAXIMUM_POOL_SIZE,                KEEP_ALIVE,                TimeUnit.SECONDS,                mPoolWorkQueue,                sThreadFactory);    }    public int getPoolSize() {        return mThreadPoolExecutor.getCorePoolSize();    }    public void setPoolSize(int poolSize) {        if (poolSize > 0) {            mThreadPoolExecutor.setCorePoolSize(poolSize);        }    }    public boolean isBusy() {        return mThreadPoolExecutor.getActiveCount() >= mThreadPoolExecutor.getCorePoolSize();    }    @Override    public void execute(final Runnable r) {        mThreadPoolExecutor.execute(r);    }}

在BitmapGlobalConfig还维护了一个非常重要的东西:

private final static HashMap<String, BitmapGlobalConfig> configMap = new HashMap<String,BitmapGlobalConfig>(1);

这是一个HashMap,键值对是:String类型的磁盘缓存路径以及该路径对应的BitmapGlobalConfig对象。从上面给出的BitmapGlobalConfig的构造方法中我们可以看到,对于不同的磁盘路径,都初始化了一个BitmapGlobalConfig存在这个HashMap中。
对于图片下载,BitmapGlobalConfig中维护了一个图片下载器Downloader,这个后面再讲,因为后面要讲图片加载的执行,会讲到它。
BitmapGlobalConfig中还有着一个东西:一个异步任务。对于缓存区的一些初始化、清空等操作是使用的异步任务。还提供了一些回调,这里BitmapGlobalConfig中还维护了一个BitmapCacheListener,用于图片缓存操作之后的回调。
主要进行的图片缓存的管理任务操作如下:

 ////////////////////////////////// bitmap cache management task ///////////////////////////////////////    private class BitmapCacheManagementTask extends PriorityAsyncTask<Object, Void, Object[]> {        public static final int MESSAGE_INIT_MEMORY_CACHE = 0;        public static final int MESSAGE_INIT_DISK_CACHE = 1;        public static final int MESSAGE_FLUSH = 2;        public static final int MESSAGE_CLOSE = 3;        public static final int MESSAGE_CLEAR = 4;        public static final int MESSAGE_CLEAR_MEMORY = 5;        public static final int MESSAGE_CLEAR_DISK = 6;        public static final int MESSAGE_CLEAR_BY_KEY = 7;        public static final int MESSAGE_CLEAR_MEMORY_BY_KEY = 8;        public static final int MESSAGE_CLEAR_DISK_BY_KEY = 9;        private BitmapCacheManagementTask() {            this.setPriority(Priority.UI_TOP);        }        @Override        protected Object[] doInBackground(Object... params) {            if (params == null || params.length == 0) return params;            BitmapCache cache = getBitmapCache();            if (cache == null) return params;            try {                switch ((Integer) params[0]) {                    case MESSAGE_INIT_MEMORY_CACHE:                        cache.initMemoryCache();                        break;                    case MESSAGE_INIT_DISK_CACHE:                        cache.initDiskCache();                        break;                    case MESSAGE_FLUSH:                        cache.flush();                        break;                    case MESSAGE_CLOSE:                        cache.clearMemoryCache();                        cache.close();                        break;                    case MESSAGE_CLEAR:                        cache.clearCache();                        break;                    case MESSAGE_CLEAR_MEMORY:                        cache.clearMemoryCache();                        break;                    case MESSAGE_CLEAR_DISK:                        cache.clearDiskCache();                        break;                    case MESSAGE_CLEAR_BY_KEY:                        if (params.length != 2) return params;                        cache.clearCache(String.valueOf(params[1]));                        break;                    case MESSAGE_CLEAR_MEMORY_BY_KEY:                        if (params.length != 2) return params;                        cache.clearMemoryCache(String.valueOf(params[1]));                        break;                    case MESSAGE_CLEAR_DISK_BY_KEY:                        if (params.length != 2) return params;                        cache.clearDiskCache(String.valueOf(params[1]));                        break;                    default:                        break;                }            } catch (Throwable e) {                LogUtils.e(e.getMessage(), e);            }            return params;        }        @Override        protected void onPostExecute(Object[] params) {            if (bitmapCacheListener == null || params == null || params.length == 0) return;            try {                switch ((Integer) params[0]) {                    case MESSAGE_INIT_MEMORY_CACHE:                        bitmapCacheListener.onInitMemoryCacheFinished();                        break;                    case MESSAGE_INIT_DISK_CACHE:                        bitmapCacheListener.onInitDiskFinished();                        break;                    case MESSAGE_FLUSH:                        bitmapCacheListener.onFlushCacheFinished();                        break;                    case MESSAGE_CLOSE:                        bitmapCacheListener.onCloseCacheFinished();                        break;                    case MESSAGE_CLEAR:                        bitmapCacheListener.onClearCacheFinished();                        break;                    case MESSAGE_CLEAR_MEMORY:                        bitmapCacheListener.onClearMemoryCacheFinished();                        break;                    case MESSAGE_CLEAR_DISK:                        bitmapCacheListener.onClearDiskCacheFinished();                        break;                    case MESSAGE_CLEAR_BY_KEY:                        if (params.length != 2) return;                        bitmapCacheListener.onClearCacheFinished(String.valueOf(params[1]));                        break;                    case MESSAGE_CLEAR_MEMORY_BY_KEY:                        if (params.length != 2) return;                        bitmapCacheListener.onClearMemoryCacheFinished(String.valueOf(params[1]));                        break;                    case MESSAGE_CLEAR_DISK_BY_KEY:                        if (params.length != 2) return;                        bitmapCacheListener.onClearDiskCacheFinished(String.valueOf(params[1]));                        break;                    default:                        break;                }            } catch (Throwable e) {                LogUtils.e(e.getMessage(), e);            }        }    }

既然讲到了这里我们在讲讲BitmapCache吧,因为上面的异步任务是对它进行管理的。只贴部分代码:

public class BitmapCache {    private final int DISK_CACHE_INDEX = 0;    private LruDiskCache mDiskLruCache;    private LruMemoryCache<MemoryCacheKey, Bitmap> mMemoryCache;    private final Object mDiskCacheLock = new Object();    private BitmapGlobalConfig globalConfig;

可以看到,我们的图片缓存是有着两部分组成,一部分是磁盘缓存,一部分是内存缓存,从命名可以看出都是使用的Lru算法,Least recently used,最近最少使用。那么我们看看这两部分缓存都分别是怎么实现的。
先看磁盘缓存LruDiskCache ,这里我们可以看到LruDiskCache 是实现了Closeable接口的。实现了一个同步方法close,其中用来关闭资源,这里没贴出来,关闭资源之后,将引用置为了null。让实例化出来的Writer可以被GC收集。

public final class LruDiskCache implements Closeable

磁盘存储是通过维护一个LinkedHashMap来实现的:

private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<String, Entry>(0, 0.75f, true);

磁盘存储通过在磁盘中创建了一个日志文件,然后在文件中存储着图片的信息、图片的文件名、路径等,获取图片时,会从磁盘缓存文件目录查找是否有该文件,在该缓存目录中直接获取。如果不存在,则从日志文件中删除该条记录,这是因为缓存区大小有限,所以当缓存区大小溢出时,就会删掉最早使用的文件,这时便从日志文件中将该文件的信息也删去。

接下来看看内存缓存LruMemoryCache,Lru内存缓存也是使用的Lru算法,维护了一个LinkedHashMap:

private LruMemoryCache<MemoryCacheKey, Bitmap> mMemoryCache

而:LruMemoryCache内部维护着

    private final LinkedHashMap<K, V> map;

这里值得注意的是,对于LruMemoryCache中图片的get、put、remove操作都是使用了synchronized关键字保持同步,避免多线程中并发put、remove、get操作,比如说我要put一个Bitmap到我的LinkedHashMap中,如果没有加同步,两个线程并发执行,都判断map中没有存该图片,那么接下来一起put,map中便会存进两个相同的Bitmap;
图片缓存BitmapCache就讲到这里吧,到这里BitmapGlobalConfig这个全局参数里面所存的东西重要的也大概讲了一下了。
回到之前的那一点,在我们初始化一个BitmapGlobalConfig时,获得他的一个实例时,实际上是从BitmapGlobalConfig类中的HashMap进行获取的,里面存的键值对是缓存路径与BitmapGlobalConfig。

private final static HashMap<String, BitmapGlobalConfig> configMap = new HashMap<String,,BitmapGlobalConfig>(1);

也就是说我们对于不同的缓存路径,BitmapGlobalConfig是不同的。而缓存路径便是我们构造BitmapUtils的时候选传的一个参数。所以,对于一个BitmapUtils工具类对象来说,它是与一个缓存路径绑定了的,而BitmapGlobalConfig对象又是跟缓存路径一对一关系的,所以,一个BitmapUtils对应着一个BitmapGlobalConfig。

讲了这么多BitmapGlobalConfig,我们接下来继续最开始的display方法中图片加载的过程。
再贴一遍之前的代码:

// find bitmap from mem cache.        Bitmap bitmap = globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);        if (bitmap != null) {            callBack.onLoadStarted(container, uri, displayConfig);            callBack.onLoadCompleted(                    container,                    uri,                    bitmap,                    displayConfig,                    BitmapLoadFrom.MEMORY_CACHE);        } else if (!bitmapLoadTaskExist(container, uri, callBack)) {            final BitmapLoadTask<T> loadTask = new BitmapLoadTask<T>(container, uri, displayConfig, callBack);            // get executor            PriorityExecutor executor = globalConfig.getBitmapLoadExecutor();            File diskCacheFile = this.getBitmapFileFromDiskCache(uri);            boolean diskCacheExist = diskCacheFile != null && diskCacheFile.exists();            if (diskCacheExist && executor.isBusy()) {                executor = globalConfig.getDiskCacheExecutor();            }            // set loading image            Drawable loadingDrawable = displayConfig.getLoadingDrawable();            callBack.setDrawable(container, new AsyncDrawable<T>(loadingDrawable, loadTask));            loadTask.setPriority(displayConfig.getPriority());            loadTask.executeOnExecutor(executor);        }

现在我们应该比较好理解了。
先进行图片加载回调方法中的加载前回调方法onPreLoad。
(1)首先从我们当前的BitmapUtils的globalConfig获取到图片缓存类对象BitmapCache,然后再调用BitmapCache中的getBitmapFromMemCache来从内存缓存LruMemoryCache中获取缓存图片。
(2) 如果图片不为空的话,回调BitmapLoadCallBack(图片加载回调类)中的onLoadStarted、onLoadCompleted。
(3)如果我们没有自己实现BitmapLoadCallBack的实现子类的话,使用DefaultBitmapLoadCallBack,而默认图片加载回调中实现了onLoadCompleted,在这里面将View容器的background或者是src(这里判断了View容器是ImageView或者是其他的)设置为该Bitmap,以及进行BitmapDisplayConfig中的动画的运行,这里动画的展示使用了原型模式。
(4)如果图片在内存缓存LruMemoryCache中不存在,获得的Bitmap为空
(5)先创建一个图片加载的异步任务,在这里,异步任务中对于容器View 的引用是弱引用。

final BitmapLoadTask<T> loadTask = new BitmapLoadTask<T>(container, uri, displayConfig, callBack);

(6)获取图片加载的加载器executor,即线程池,这里使用的是BITMAP_LOAD_EXECUTOR。

// get executorPriorityExecutor executor = globalConfig.getBitmapLoadExecutor();

(7)根据uri来从磁盘缓存中获取图片,如果文件存在,而且BITMAP_LOAD_EXECUTOR线程池处于繁忙状态(alive的线程数大于核心线程数),那么就将executor赋值为DISK_CACHE_EXECUTOR,使用DISK_CACHE_EXECUTOR作为异步任务的执行线程池。

File diskCacheFile = this.getBitmapFileFromDiskCache(uri);boolean diskCacheExist = diskCacheFile != null && diskCacheFile.exists();if (diskCacheExist && executor.isBusy()) {     executor = globalConfig.getDiskCacheExecutor();}

(8)然后在设置加载中显示的图片

Drawable loadingDrawable = displayConfig.getLoadingDrawable();callBack.setDrawable(container, new AsyncDrawable<T>(loadingDrawable, loadTask));

(9)执行任务,将异步任务的执行器设置为executor,并执行,这里,由于异步任务默认是以串行方式进行执行的,如果想并行,得使用executeOnExecutor方法来执行。

loadTask.setPriority(displayConfig.getPriority());loadTask.executeOnExecutor(executor);

下面我们在分析一下执行任务当中的一些代码,我们来看看异步任务的doInBackground:

@Override        protected Bitmap doInBackground(Object... params) {            synchronized (pauseTaskLock) {                while (pauseTask && !this.isCancelled()) {                    try {                        pauseTaskLock.wait();                        if (cancelAllTask) {                            return null;                        }                    } catch (Throwable e) {                    }                }            }            Bitmap bitmap = null;            // get cache from disk cache            if (!this.isCancelled() && this.getTargetContainer() != null) {                this.publishProgress(PROGRESS_LOAD_STARTED);                bitmap = globalConfig.getBitmapCache().getBitmapFromDiskCache(uri, displayConfig);            }            // download image            if (bitmap == null && !this.isCancelled() && this.getTargetContainer() != null) {                bitmap = globalConfig.getBitmapCache().downloadBitmap(uri, displayConfig, this);                from = BitmapLoadFrom.URI;            }            return bitmap;        }

这里有一个暂停锁。我们来看看暂停锁的定义,其实就是一个对象。一个BitmapUtils中使用异步任务来执行请求,而异步任务又是采用多线程并发,因此通过给一个final对象进行同步,便可以在判断是否取消任务、暂停任务时,是同步执行的,当线程想要取消任务、暂停任务时,必须先获得pauseTaskLock 对象锁,让任务取消之后,又会释放这个对象锁以供其他线程来获取它。

private final Object pauseTaskLock = new Object();

任务取消后:

@Override        protected void onCancelled(Bitmap bitmap) {            synchronized (pauseTaskLock) {                pauseTaskLock.notifyAll();            }        }

接下来继续,如果任务没有取消,而且容器View不为null,则从磁盘缓存中获取图片。
否则就开始进行图片的网络下载。也就是说uri不是本地的存储路径,或者说uri对应的路径文件不存在,,则尝试根据当前uri进行网络图片的获取。
我们来看一下图片网络下载的方法,这个方法是写在BitmapCache中的。

public Bitmap downloadBitmap(String uri, BitmapDisplayConfig config, final BitmapUtils.BitmapLoadTask<?> task) {        BitmapMeta bitmapMeta = new BitmapMeta();        OutputStream outputStream = null;        LruDiskCache.Snapshot snapshot = null;        try {            Bitmap bitmap = null;            // try download to disk            if (globalConfig.isDiskCacheEnabled()) {                if (mDiskLruCache == null) {                    initDiskCache();                }                if (mDiskLruCache != null) {                    try {                        snapshot = mDiskLruCache.get(uri);                        if (snapshot == null) {                            LruDiskCache.Editor editor = mDiskLruCache.edit(uri);                            if (editor != null) {                                outputStream = editor.newOutputStream(DISK_CACHE_INDEX);                                bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri, outputStream, task);                                if (bitmapMeta.expiryTimestamp < 0) {                                    editor.abort();                                    return null;                                } else {                                    editor.setEntryExpiryTimestamp(bitmapMeta.expiryTimestamp);                                    editor.commit();                                }                                snapshot = mDiskLruCache.get(uri);                            }                        }                        if (snapshot != null) {                            bitmapMeta.inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);                            bitmap = decodeBitmapMeta(bitmapMeta, config);                            if (bitmap == null) {                                bitmapMeta.inputStream = null;                                mDiskLruCache.remove(uri);                            }                        }                    } catch (Throwable e) {                        LogUtils.e(e.getMessage(), e);                    }                }            }            // try download to memory stream            if (bitmap == null) {                outputStream = new ByteArrayOutputStream();                bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri, outputStream, task);                if (bitmapMeta.expiryTimestamp < 0) {                    return null;                } else {                    bitmapMeta.data = ((ByteArrayOutputStream) outputStream).toByteArray();                    bitmap = decodeBitmapMeta(bitmapMeta, config);                }            }            if (bitmap != null) {                bitmap = rotateBitmapIfNeeded(uri, config, bitmap);                bitmap = addBitmapToMemoryCache(uri, config, bitmap, bitmapMeta.expiryTimestamp);            }            return bitmap;        } catch (Throwable e) {            LogUtils.e(e.getMessage(), e);        } finally {            IOUtils.closeQuietly(outputStream);            IOUtils.closeQuietly(snapshot);        }        return null;    }

我们在初始化BitmapUtils时,便初始化了一个默认的DefaultBitmapGlobalConfig,而在这个默认的全局配置中,维护着一个DefaultDownloader,这个DefaultDownloader是下载器Downloader的一个实现类。Downloader是一个抽象类。
下载器有以下方法,主要是用来从uri下载图片,uri包括网络图片,还有存储在assert等文件夹中的图片。

public abstract class Downloader {    /**     * Download bitmap to outputStream by uri.     *     * @param uri     * @param outputStream     * @return The expiry time stamp or -1 if failed to download.     */    public abstract long downloadToStream(String uri, OutputStream outputStream, final BitmapUtils.BitmapLoadTask<?> task);    private Context context;    private long defaultExpiry;    private int defaultConnectTimeout;    private int defaultReadTimeout;    public Context getContext() {        return context;    }    public void setContext(Context context) {        this.context = context;    }    public void setDefaultExpiry(long expiry) {        this.defaultExpiry = expiry;    }    public long getDefaultExpiry() {        return this.defaultExpiry;    }    public int getDefaultConnectTimeout() {        return defaultConnectTimeout;    }    public void setDefaultConnectTimeout(int defaultConnectTimeout) {        this.defaultConnectTimeout = defaultConnectTimeout;    }    public int getDefaultReadTimeout() {        return defaultReadTimeout;    }    public void setDefaultReadTimeout(int defaultReadTimeout) {        this.defaultReadTimeout = defaultReadTimeout;    }}

在默认的图片下载器中实现了下载方法。可以看到,这里建立了URLConnection 来进行图片的下载。会先对uri进行判定,判断是从网络上下载图片还是从本地assert文件夹等地方进行图片的获取、这里是将uri对应图片下载成文件流

public class DefaultDownloader extends Downloader {    /**     * Download bitmap to outputStream by uri.     *     * @param uri          file path, assets path(assets/xxx) or http url.     * @param outputStream     * @param task     * @return The expiry time stamp or -1 if failed to download.     */    @Override    public long downloadToStream(String uri, OutputStream outputStream, final BitmapUtils.BitmapLoadTask<?> task) {        if (task == null || task.isCancelled() || task.getTargetContainer() == null) return -1;        URLConnection urlConnection = null;        BufferedInputStream bis = null;        OtherUtils.trustAllHttpsURLConnection();        long result = -1;        long fileLen = 0;        long currCount = 0;        try {            if (uri.startsWith("/")) {                FileInputStream fileInputStream = new FileInputStream(uri);                fileLen = fileInputStream.available();                bis = new BufferedInputStream(fileInputStream);                result = System.currentTimeMillis() + this.getDefaultExpiry();            } else if (uri.startsWith("assets/")) {                InputStream inputStream = this.getContext().getAssets().open(uri.substring(7, uri.length()));                fileLen = inputStream.available();                bis = new BufferedInputStream(inputStream);                result = Long.MAX_VALUE;            } else {                final URL url = new URL(uri);                urlConnection = url.openConnection();                urlConnection.setConnectTimeout(this.getDefaultConnectTimeout());                urlConnection.setReadTimeout(this.getDefaultReadTimeout());                bis = new BufferedInputStream(urlConnection.getInputStream());                result = urlConnection.getExpiration();                result = result < System.currentTimeMillis() ? System.currentTimeMillis() + this.getDefaultExpiry() : result;                fileLen = urlConnection.getContentLength();            }            if (task.isCancelled() || task.getTargetContainer() == null) return -1;            byte[] buffer = new byte[4096];            int len = 0;            BufferedOutputStream out = new BufferedOutputStream(outputStream);            while ((len = bis.read(buffer)) != -1) {                out.write(buffer, 0, len);                currCount += len;                if (task.isCancelled() || task.getTargetContainer() == null) return -1;                task.updateProgress(fileLen, currCount);            }            out.flush();        } catch (Throwable e) {            result = -1;            LogUtils.e(e.getMessage(), e);        } finally {            IOUtils.closeQuietly(bis);        }        return result;    }}

然后在将文件流转换成Bitmap对象,我们在display方法中可以看到是调用了

downloadBitmap(String uri, BitmapDisplayConfig config, final BitmapUtils.BitmapLoadTask<?> task)

这个方法来进行图片的下载,而这个方法内部又调用了

globalConfig.getDownloader().downloadToStream(uri, outputStream, task);

这个方法来将uri对应的图片转换成图片流,最终转换成Bitmap对象。

我们最后再来看看display方法的最后一句中的这个异步任务是怎么提交下载图片的结果并显示 在容器View中的。

loadTask.executeOnExecutor(executor);

这个BitmapLoadTask图片加载任务时HttpUtils的一个内部类,它继承自PriorityAsyncTask,是一个异步任务。对容器View是持有的弱引用,代码如下。

  1. 在doInBackground中进行图片的获取,也就是说从内存缓存中获取图片是在主线程中进行的,而从磁盘缓存或者网络下载图片时,是在子线程中进行的。
  2. 下载完成之后doInBackground方法会返回一个Bitmap对象,最终在运行完成回调函数onPostExecute 中回调DefaultBitmapLoadCallBack实例callback的
public abstract void onLoadCompleted(T container, String uri, Bitmap bitmap, BitmapDisplayConfig config, BitmapLoadFrom from)

或者是

 public abstract void onLoadFailed(T container, String uri, Drawable drawable);

如果Bitmap不为空,则表示下载成功,那么就会在onLoadCompleted中将Bitmap在container中显示出来。

public class BitmapLoadTask<T extends View> extends PriorityAsyncTask<Object, Object, Bitmap> {        private final String uri;        private final WeakReference<T> containerReference;        private final BitmapLoadCallBack<T> callBack;        private final BitmapDisplayConfig displayConfig;        private BitmapLoadFrom from = BitmapLoadFrom.DISK_CACHE;        public BitmapLoadTask(T container, String uri, BitmapDisplayConfig config, BitmapLoadCallBack<T> callBack) {            if (container == null || uri == null || config == null || callBack == null) {                throw new IllegalArgumentException("args may not be null");            }            this.containerReference = new WeakReference<T>(container);            this.callBack = callBack;            this.uri = uri;            this.displayConfig = config;        }        @Override        protected Bitmap doInBackground(Object... params) {            synchronized (pauseTaskLock) {                while (pauseTask && !this.isCancelled()) {                    try {                        pauseTaskLock.wait();                        if (cancelAllTask) {                            return null;                        }                    } catch (Throwable e) {                    }                }            }            Bitmap bitmap = null;            // get cache from disk cache            if (!this.isCancelled() && this.getTargetContainer() != null) {                this.publishProgress(PROGRESS_LOAD_STARTED);                bitmap = globalConfig.getBitmapCache().getBitmapFromDiskCache(uri, displayConfig);            }            // download image            if (bitmap == null && !this.isCancelled() && this.getTargetContainer() != null) {                bitmap = globalConfig.getBitmapCache().downloadBitmap(uri, displayConfig, this);                from = BitmapLoadFrom.URI;            }            return bitmap;        }        public void updateProgress(long total, long current) {            this.publishProgress(PROGRESS_LOADING, total, current);        }        private static final int PROGRESS_LOAD_STARTED = 0;        private static final int PROGRESS_LOADING = 1;        @Override        protected void onProgressUpdate(Object... values) {            if (values == null || values.length == 0) return;            final T container = this.getTargetContainer();            if (container == null) return;            switch ((Integer) values[0]) {                case PROGRESS_LOAD_STARTED:                    callBack.onLoadStarted(container, uri, displayConfig);                    break;                case PROGRESS_LOADING:                    if (values.length != 3) return;                    callBack.onLoading(container, uri, displayConfig, (Long) values[1], (Long) values[2]);                    break;                default:                    break;            }        }        @Override        protected void onPostExecute(Bitmap bitmap) {            final T container = this.getTargetContainer();            if (container != null) {                if (bitmap != null) {                    callBack.onLoadCompleted(                            container,                            this.uri,                            bitmap,                            displayConfig,                            from);                } else {                    callBack.onLoadFailed(                            container,                            this.uri,                            displayConfig.getLoadFailedDrawable());                }            }        }        @Override        protected void onCancelled(Bitmap bitmap) {            synchronized (pauseTaskLock) {                pauseTaskLock.notifyAll();            }        }        public T getTargetContainer() {            final T container = containerReference.get();            final BitmapLoadTask<T> bitmapWorkerTask = getBitmapTaskFromContainer(container, callBack);            if (this == bitmapWorkerTask) {                return container;            }            return null;        }    }

写在最后

一级标题写我的感想。
第一次写源码分析,写的很乱。
不过写完之后自己确实是清晰了,其实很多东西在自己看代码的过程中没注意的,写源码分析时注意到了。不过还是有很多东西我自己本来就没看懂,就没写上来。

原创粉丝点击