上一篇使用XUtils的BitmapUtils实现了一个照片墙的功能,参见:android-----XUtils框架之BitmapUtils加载照片实现,这一篇我们从源码的角度分析下BitmapUtils到底是怎么一个执行流程的;
先来回顾下之前我们使用BitmapUtils的步骤:
很简单,就只有两步:
(1)通过BitmapUtils的构造函数创建对象;
(2)调用BitmapUtils对象的display方法;
好了,我们先从创建BitmapUtils对象开始分析,很自然想到了BitmapUtils的构造函数啦:
public BitmapUtils(Context context) {
this(context, null);
}
public BitmapUtils(Context context, String diskCachePath) {
if (context == null) {
throw new IllegalArgumentException("context may not be null");
}
this.context = context;
globalConfig = new BitmapGlobalConfig(context, diskCachePath);
defaultDisplayConfig = new BitmapDisplayConfig();
}
public BitmapUtils(Context context, String diskCachePath, int memoryCacheSize) {
this(context, diskCachePath);
globalConfig.setMemoryCacheSize(memoryCacheSize);
}
public BitmapUtils(Context context, String diskCachePath, int memoryCacheSize, int diskCacheSize) {
this(context, diskCachePath);
globalConfig.setMemoryCacheSize(memoryCacheSize);
globalConfig.setDiskCacheSize(diskCacheSize);
}
public BitmapUtils(Context context, String diskCachePath, float memoryCachePercent) {
this(context, diskCachePath);
globalConfig.setMemCacheSizePercent(memoryCachePercent);
}
public BitmapUtils(Context context, String diskCachePath, float memoryCachePercent, int diskCacheSize) {
this(context, diskCachePath);
globalConfig.setMemCacheSizePercent(memoryCachePercent);
globalConfig.setDiskCacheSize(diskCacheSize);
}
可以看到他总共有6个构造函数,但他们都会执行第2个构造函数,那我们就来看看第二个构造函数具体做些什么吧,第11行看到他使用diskCachePath以及上下文创建了一个BitmapGlobalConfig对象,既然用到BitmapGlobalConfig的构造函数,那我们就该看看里面是什么样子的了:
public BitmapGlobalConfig(Context context, String diskCachePath) {
if (context == null) throw new IllegalArgumentException("context may not be null");
this.mContext = context;
this.diskCachePath = diskCachePath;
initBitmapCache();
}
2---4行只是简单的判断context参数以及将参数赋值给BitmapGlobalConfig属性操作,最关键的部分就是initBitmapCache方法啦,来看看具体实现:
private void initBitmapCache() {
new BitmapCacheManagementTask().execute(BitmapCacheManagementTask.MESSAGE_INIT_MEMORY_CACHE);
new BitmapCacheManagementTask().execute(BitmapCacheManagementTask.MESSAGE_INIT_DISK_CACHE);
}
可以看到在initBitmapCache()中用到了BitmapCacheManagementTask,他是BitmapGlobalConfig的内部私有类,继承自PriorityAsyncTask类,第2行调用了BitmapCacheManagementTask的execute方法,并且传入的参数是BitmapCacheManagementTask.MESSAGE_INIT_MEMORY_CACHE,也就是传入的参数是0,这个execute方法在BitmapCacheManagementTask中并不存在,所以需要到他的父类PriorityAsyncTask中查找,可以看到有如下方法:
public final PriorityAsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
很显然他调用的是PriorityAsyncTask里面的executeOnExecutor方法,executeOnExecutor方法有两个重载实现:
public final PriorityAsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
return executeOnExecutor(exec, Priority.DEFAULT, params);
}
public final PriorityAsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Priority priority,
Params... params) {
if (mExecuteInvoked) {
throw new IllegalStateException("Cannot execute task:"
+ " the task is already executed.");
}
mExecuteInvoked = true;
onPreExecute();
mWorker.mParams = params;
exec.execute(new PriorityRunnable(priority, mFuture));
return this;
}
我们调用的是第一个executeOnExecutor实现,传入的第一个参数sDefaultExecutor是PriorityExecutor类型的线程池,他实现了Executor接口,默认情况下调用无参构造函数的话生成含有5个线程的线程池,也就是我们这里的sDefaultExecutor是包含有5个线程的线程池,第一个executeOnExecutor最终也会调用第二个第一个executeOnExecutor实现,只不过将每个线程的优先级设置为默认而已啦,所有我们只需要查看第二个executeOnExecutor即可,该方法中的大部分原理都是来自于AsyncTask的,如果任务正在执行则抛出异常,否则的话设置任务正在执行,接着调用onPreExecute方法,这个方法在PriorityAsyncTask中是并没有实现的,所以如果我们想要在正式执行加载图片前提示用户操作的话可以在子类实现该方法,接着将请求参数赋值给WorkerRunnable类型的对象mWorker的mParams属性,如果非要跟踪WorkerRunnable类型的话,需要到AsyncTask源码中查看,他是AsyncTask的一个抽象静态内部类,该类继承了Callable接口,理解到这就可以了,来看看mWorker的具体定义:
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
可以看到他确实实现了call方法
继续上面的executeOnExecutor方法讲解,第19行调用了线程池的execute将新创建的PriorityRunnable对象加入到了线程池,具体的执行调用的是创建PriorityRunnable的run方法啦,而PriorityRunnable的run方法其实调用的是第二个参数也就是这里mFuture的run方法,这点在源码中也体现出来了:
@Override
public void run() {
this.obj.run();
}
这里的obj就是PriorityRunnable的第二个参数,也即mFuture,他是FutureTask类型的实现了Runnable接口,mFuture具体的定义如下:
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
LogUtils.w(e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
可以看到创建mFuture的时候传入了我们刚刚创建的mWorker,那么调用mFuture的run方法具体做些什么操作呢?这需要查看FutureTask的run方法啦:
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
这个方法最核心的一句代码在第12行,他会调用c的call方法,这里的c就是上面创建mFuture对象时传入的mWorker这个对象,也就是调用的mWorker的call方法,这段源码已经在上面出现过了,直接分析可以看出在call方法的第7行调用了doInBackground方法,这个方法在PriorityAsyncTask中也是一个抽象方法,因此具体的实现还是在实现他的子类BitmapCacheManagementTask中,所以回到了BitmapCacheManagementTask中的doInBackground方法,此处我们传入的参数标志是MESSAGE_INIT_MEMORY_CACHE,查看case语句可以发现他调用了BitmapCache的initMemoryCache方法,那么BitmapCache是什么呢?其实他就是内存缓存、SD卡缓存的初始化、修改等等关于缓存操作的方法集合类,进入他的initMemoryCache方法:
public void initMemoryCache() {
if (!globalConfig.isMemoryCacheEnabled()) return;
// Set up memory cache
if (mMemoryCache != null) {
try {
clearMemoryCache();
} catch (Throwable e) {
}
}
mMemoryCache = new LruMemoryCache<MemoryCacheKey, Bitmap>(globalConfig.getMemoryCacheSize()) {
/**
* Measure item size in bytes rather than units which is more practical
* for a bitmap cache
*/
@Override
protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) {
if (bitmap == null) return 0;
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
这个方法是用来初始化内存缓存的,首先第2行判断是否允许内存缓存,不允许的话直接return,第5---10行如果缓存非空的话,那么会调用clearMemoryCache来清空缓存,第11行创建新的缓存,其大小是globalConfig.getMemoryCacheSize(),这个大小我们可以在创建BitmapUtils的时候传入,并且在创建的过程中重写了每个对象占用字节数大小的siezof方法;
这样initBitmapCache的第2行代码分析完毕,其实就是创建了一个内存缓存LruMemoryCache对象,第3行代码传入的参数是BitmapCacheManagementTask.MESSAGE_INIT_DISK_CACHE,在经历和上面一样的步骤之后会走到BitmapGlobalConfig内部类BitmapCacheManagementTask的doInBackground方法里面,执行他标志为MESSAGE_INIT_DISK_CACHE的case语句块:
case MESSAGE_INIT_DISK_CACHE:
cache.initDiskCache();
break;
可以看到他执行的是BitmapCache的initDiskCache方法,也就是创建了SD卡缓存对象,我们到这个方法里面看看:
public void initDiskCache() {
if (!globalConfig.isDiskCacheEnabled()) return;
// Set up disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
File diskCacheDir = new File(globalConfig.getDiskCachePath());
if (diskCacheDir.exists() || diskCacheDir.mkdirs()) {
long availableSpace = OtherUtils.getAvailableSpace(diskCacheDir);
long diskCacheSize = globalConfig.getDiskCacheSize();
diskCacheSize = availableSpace > diskCacheSize ? diskCacheSize : availableSpace;
try {
mDiskLruCache = LruDiskCache.open(diskCacheDir, 1, 1, diskCacheSize);
mDiskLruCache.setFileNameGenerator(globalConfig.getFileNameGenerator());
} catch (Throwable e) {
mDiskLruCache = null;
LogUtils.e(e.getMessage(), e);
}
}
}
isDiskCacheReadied = true;
mDiskCacheLock.notifyAll();
}
}
同样首先判断是否允许SD卡缓存,不允许的话直接return,否则执行后面代码,7--11行设置SD卡缓存文件的存储路径以及缓存大小设置,13行通过LruDiskCache的静态方法open创建缓存对象,如果你对LruDiskCache不太了解,可以先看看我的另一篇博客:android-----带你一步一步优化ListView(二)
,这篇博客详细介绍了LruDiskCache源码部分内容;
至此,调用完initBitmapCache方法之后就创建出来内存缓存以及SD卡缓存了,BitmapGlobalConfig构造函数也就执行结束,BitmapUtils包含两个参数的构造方法中也就创建了BitmapGlobalConfig对象了,接着在BitmapUtils包含两个参数的构造方法中我们看到穿件了BitmapDisplayConfig对象,这个对象主要用来设置显示图片的一些属性的,比如动画、旋转之类的,这个不是重点;
那么BitmapUtils最重要的两个参数构造函数源码就执行结束了,至于其他5个构造函数基本上都是自定义设置缓存大小的一些操作,都可以从参数名字上面体现出来,这里就不再赘述;
有了BitmapUtils对象,下一步我们就要调用display方法请求网络图片或者本地图片或者assets文件夹下面的图片显示到控件上面了,很显然display将是重要方法:
public <T extends View> void display(T container, String uri) {
display(container, uri, null, null);
}
public <T extends View> void display(T container, String uri, BitmapDisplayConfig displayConfig) {
display(container, uri, displayConfig, null);
}
public <T extends View> void display(T container, String uri, BitmapLoadCallBack<T> callBack) {
display(container, uri, null, callBack);
}
public <T extends View> void display(T container, String uri, BitmapDisplayConfig displayConfig, BitmapLoadCallBack<T> callBack) {
}
可以看到display共有4个重载实现,但是他们最终都会聚集到第四个实现上面,所以我们只需要分析第四个即可,先来看看他的四个参数个表示什么:
container:表示所要显示图片的控件;
uri:表示请求图片的地址,注意这个可以是本地路径或者assets文件夹下面的路径;
displayConfig:对显示图片的一些操作,比如动画呀,旋转呀之类的;
callBack:图片加载的一些回调接口实现对象;
这里面我们需要看看BitmapLoadCallBack里面提供了哪些回调方法可以让我们在加载图片的过程中进行一些提示用户操作:
public abstract class BitmapLoadCallBack<T extends View> {
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);
}
可以看出BitmapLoadCallBack是抽象类,在这里我只列出了它里面的抽象方法,具体每个方法是做什么的名字上面都可以体现出来的啦!
好啦,开始进入display方法里面了:
public <T extends View> void display(T container, String uri, BitmapDisplayConfig displayConfig, BitmapLoadCallBack<T> callBack) {
if (container == null) {
return;
}
container.clearAnimation();
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()));
callBack.onPreLoad(container, uri, displayConfig);
if (TextUtils.isEmpty(uri)) {
callBack.onLoadFailed(container, uri, displayConfig.getLoadFailedDrawable());
return;
}
// 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);
// load bitmap from uri or diskCache
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));
Priority priority = displayConfig.getPriority();
if (priority == null) {
priority = Priority.DEFAULT;
}
loadTask.executeOnExecutor(executor, priority);
}
}
第2行首先判断所要显示图片的控件是否存在,不存在直接return,这个很好理解啦,你都没想要把图片显示在哪里,那整个display函数执行就没什么意义了,接着第6行清除掉原先控件上面的所有动画属性,第8行判断我们传入的第四个参数是否为空,如果为空的话,则执行第9行,创建一个DefaultBitmapLoadCallBack对象出来,那DefaultBitmapLoadCallBack里面是什么样子的呢?
@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抽象类的里面的onLoadCompleted和onLoadFailed方法,onLoadCompleted是在加载成功之后回调的方法,第3行调用setBitmap方法将图片显示到container上面,接着第4行查看是否设置了animation属性,如果设置了的话调用animationDisplay方法开启animation属性,这个方法会通过反射来克隆一个Animation对象出来,并且调用startAnimation方法来将动画设置到控件上面;onLoadFailed方法会在加载图片失败的时候调用,至于onLoadCompleted和onLoadFailed这两个方法的调用时机会在后面介绍;
回到display方法,接下来第12行如果display方法的第三个参数displayConfig为空或者等于defaultDisplayConfig的话,则调用cloneNew方法深度拷贝defaultDisplayConfig给displayConfig,第17行获取需要设置图片的大小,我们来看看getBitmapMaxSize方法:
public BitmapSize getBitmapMaxSize() {
return bitmapMaxSize == null ? BitmapSize.ZERO : bitmapMaxSize;
}
如果bitmapMaxSize为空的话,则其大小为(0,0);
回到display方法,第18行首先通过optimizeMaxSizeByView方法来根据View的大小以及bitmapsize的大小来计算出最终设置成图片的大小,随后调用setBitmapMaxSize方法将其设置到配置对象上面,来看看optimizeMaxSizeByView方法:
public static BitmapSize optimizeMaxSizeByView(View view, int maxImageWidth, int maxImageHeight) {
int width = maxImageWidth;
int height = maxImageHeight;
if (width > 0 && height > 0) {
return new BitmapSize(width, height);
}
final ViewGroup.LayoutParams params = view.getLayoutParams();
if (params != null) {
if (params.width > 0) {
width = params.width;
} else if (params.width != ViewGroup.LayoutParams.WRAP_CONTENT) {
width = view.getWidth();
}
if (params.height > 0) {
height = params.height;
} else if (params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
height = view.getHeight();
}
}
if (width <= 0) width = getImageViewFieldValue(view, "mMaxWidth");
if (height <= 0) height = getImageViewFieldValue(view, "mMaxHeight");
BitmapSize screenSize = getScreenSize(view.getContext());
if (width <= 0) width = screenSize.getWidth();
if (height <= 0) height = screenSize.getHeight();
return new BitmapSize(width, height);
}
这个方法首先获取到bitmapsize的长宽,如果这两个值都不为0的话表示这里的长宽已经使我们所指定的,则执行第6行返回该长宽的BitmapSize对象;如果长或者宽为0的话,则执行9行后面的代码,首先获取View的LayoutParams属性值,若params的宽大于0表示View的大小是固定的,而并不是wrap或者match,那么直接设置即可,如果宽小于等于0并且宽度不等于View的宽度的话,则设置宽度为View的宽度,高度设置方法与宽度一致,如果10--22行的设置之后width或者height仍然小于等于0,并且设置的图片显示控件是ImageView的话,则通过getImageViewFieldValue方法获取到图片的宽高,getImageViewFieldValue里面是通过反射实现获取的,如果24以及25行之后width或者height仍然小于等于0,那么就会将BitmapSize设置为是当前窗体的大小了;
回到display方法里面,接着第20行调用callBack的onPreLoad方法,这个方法是我们自己实现的,具体实现在我们调用display方法的第四个参数里面,22行判断如果请求地址为空的话,直接调用callBack的onLoadFailed方法显示出错信息,并且直接return;接着第28行通过getBitmapFromMemCache方法从内存缓存中读取对应uri的Bitmap:
public Bitmap getBitmapFromMemCache(String uri, BitmapDisplayConfig config) {
if (mMemoryCache != null && globalConfig.isMemoryCacheEnabled()) {
MemoryCacheKey key = new MemoryCacheKey(uri, config);
return mMemoryCache.get(key);
}
return null;
}
这里首先根据uri生成key,随后根据key从内存缓存中读取对应于key的Bitmap对象;
回到display方法,如果bitmap不为空的话,表示内存缓存中存在对应于uri的Bitmap对象,那么我们调用callback的onLoadStarted提示开始加载图片已经结束,以及onLoadCompleted图片已经把加载完成并且把Bitmap显示在控件上面;如果bitmap为空,表示当前内存缓存中并不存在对应于uri的bitmap,那么首先会在38行通过bitmapLoadTaskExist来判断是否已经存在加载当前uri图片的任务,我们来看看bitmapLoadTaskExist方法:
private static <T extends View> boolean bitmapLoadTaskExist(T container, String uri, BitmapLoadCallBack<T> callBack) {
final BitmapLoadTask<T> oldLoadTask = getBitmapTaskFromContainer(container, callBack);
if (oldLoadTask != null) {
final String oldUrl = oldLoadTask.uri;
if (TextUtils.isEmpty(oldUrl) || !oldUrl.equals(uri)) {
oldLoadTask.cancel(true);
} else {
return true;
}
}
return false;
}
首先通过container以及callBack获取到异步任务,如果该任务的uri不为空并且等于当前uri的话,则返回true,否则返回false;
回到display方法,可以看到只有当bitmapLoadTaskExist返回false的情况下才会进入if语句块的,这样子保证了不存在有两个异步任务加载同一个uri上面图片的情况,接下来的40行到56行分别创建了一个指定container, uri, displayConfig, callBack的BitmapLoadTask对象,创建了一个PriorityExecutor类型的线程池对象,设置了正在加载图片的过程中控件上所显示的图片,以及当前异步任务的优先级,最后57行执行了BitmapLoadTask的executeOnExecutor,熟悉AsyncTask源码的应该都知道执行executeOnExecutor方法最终会进入到BitmapLoadTask的doInBackground方法里面,注意BitmapLoadTask是BitmapUtils的内部类,我们来看看这里面的代码:
@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;
}
我们挑重点来看,这里面第19行if语句块首先查看当前异步任务是否被暂停并且当前想要显示图片的控件是否存在,条件满足的话进入if语句块中,首先执行publishProgress方法,这个方法最终会执行到onProgressUpdate里面,因为publishProgress传入的参数是PROGRESS_LOAD_STARTED,所以查看onProgressUpdate的case标志值为PROGRESS_LOAD_STARTED的语句知道:
case PROGRESS_LOAD_STARTED:
callBack.onLoadStarted(container, uri, displayConfig);
break;
他会执行callback的onLoadStarted,这个方法也是我们在定义BitmapLoadCallBack对象的时候自己实现的;
接着第21行调用getBitmapFromDiskCache方法从SD卡缓存中获取Bitmap对象,我们来看看这个方法:
public Bitmap getBitmapFromDiskCache(String uri, BitmapDisplayConfig config) {
if (uri == null || !globalConfig.isDiskCacheEnabled()) return null;
synchronized (mDiskCacheLock) {
while (!isDiskCacheReadied) {
try {
mDiskCacheLock.wait();
} catch (Throwable e) {
}
}
if (mDiskLruCache != null) {
LruDiskCache.Snapshot snapshot = null;
try {
snapshot = mDiskLruCache.get(uri);
if (snapshot != null) {
Bitmap bitmap = null;
if (config == null || config.isShowOriginal()) {
bitmap = BitmapDecoder.decodeFileDescriptor(
snapshot.getInputStream(DISK_CACHE_INDEX).getFD());
} else {
bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(
snapshot.getInputStream(DISK_CACHE_INDEX).getFD(),
config.getBitmapMaxSize(),
config.getBitmapConfig());
}
bitmap = rotateBitmapIfNeeded(uri, config, bitmap);
addBitmapToMemoryCache(uri, config, bitmap, mDiskLruCache.getExpiryTimestamp(uri));
return bitmap;
}
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(snapshot);
}
}
return null;
}
}
这段代码最核心的部分是从第13行开始的,通过uri查看当前SD卡缓存中是否存在指定uri的值,如果存在的话进入14行处的语句块调用BitmapDecoder的decodeFileDescriptor或者decodeSampledBitmapFromDescriptor方法来获取到Bitmapt图片,并且在第27行将该Bitmap图片加入到内存缓存中,加入方法其实挺简单的:
private void addBitmapToMemoryCache(String uri, BitmapDisplayConfig config, Bitmap bitmap, long expiryTimestamp) throws IOException {
if (uri != null && bitmap != null && globalConfig.isMemoryCacheEnabled() && mMemoryCache != null) {
MemoryCacheKey key = new MemoryCacheKey(uri, config);
mMemoryCache.put(key, bitmap, expiryTimestamp);
}
}
就仅仅是先通过uri生成key值,随后将其通过put方法加入内存缓存即可,因为内存缓存是通过map实现的,所以在这里你见到了put和get操作;
上面对于SD卡缓存讲的不怎么详细,因为之前的博客已经分析过这方面的源码了包括图片压缩技术,有兴趣的可以看看:android-----带你一步一步优化ListView(二)以及android-----解决Bitmap内存溢出的一种方法(图片压缩技术),
回到我们的doInBackground方法,第25行判断从SD卡缓存中获取的Bitmap是否为空,以及当前异步任务是否暂停、显示图片的控件是否存在,如果满足if条件的话,表示SD卡缓存上面也不存在当前uri对应的bitmap,那么我们只能从网络中获取了,也就是这里的downloadBitmap方法了:
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()) {
synchronized (mDiskCacheLock) {
// Wait for disk cache to initialize
while (!isDiskCacheReadied) {
try {
mDiskCacheLock.wait();
} catch (Throwable e) {
}
}
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);
if (config != null && config.getImageFactory() != null) {
bitmap = config.getImageFactory().createBitmap(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;
}
这个方法同样比较长,我们也只挑重点进行分析,第12行首先判断是否允许SD卡缓存,允许的话进入if语句块,接着24行相当于查看当前SD卡缓存中是否存在指定于uri的对象,这一步个人认为是属于检查操作的,为了防止调用downloadBitmap方法前的检查失误,接下来的就是LruDiskCache的标准操作步骤了,获得Editor对象,初始化OutputStream对象,接着通过downloadToStream方法从网络中获取指定uri的输出流,downloadToStream这个方法是在Downloader中定义的,但是具体实现是在DefaultDownloader中,具体实现如下:
@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.trustAllSSLForHttpsURLConnection();
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;
while ((len = bis.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
currCount += len;
if (task.isCancelled() || task.getTargetContainer() == null) return -1;
task.updateProgress(fileLen, currCount);
}
outputStream.flush();
} catch (Throwable e) {
result = -1;
LogUtils.e(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(bis);
}
return result;
}
正是在这个方法里面你就会发现为什么之前说我们可以通过BitmapUtils加载本地图片以及网络图片以及assets文件夹下面图片的原因了,downloadToStream会根据图片来源进行不同的处理,15行判断如果图片来自SD卡的话,则直接读取就可以了,20行判断如果来自于assets文件夹的话,同样也是直接读取的,如果来自网络的话,则从26行代码开始到33行从网络中获取图片流,第40到45行将图片流写入到输出流中,同时在写入的过程中第44行还要调用updateProgress来和用户界面发生一定联系,具体怎么联系呢?就是你可以用来显示下载图片的进度条了,具体怎么做的呢?第44行调用updateProgress之后实际上执行的是BitmapLoadTask里面的updateProgress,这个方法的代码:
public void updateProgress(long total, long current) {
this.publishProgress(PROGRESS_LOADING, total, current);
}
也就是说这个方法会调用publishProgress方法并且传入的标志是PROGRESS_LOADING,转而就会执行onProgressUpdate里面的标志为PROGRESS_LOADING的case语句块:
case PROGRESS_LOADING:
if (values.length != 3) return;
callBack.onLoading(container, uri, displayConfig, (Long) values[1], (Long) values[2]);
break;
这个语句块中的callback会调用onLoading方法,这个方法是我们在定义callback的时候自己实现的,具体可以在该方法里面进行更新进度条的操作等等;
回到downloadBitmap方法中,第56行判断如果bitmap为空的话,则需要进入if语句块中,那么什么情况下会出现bitmap为空呢?SD卡不存在或者不可用的情况下,进入if语句块之后同样也会在第58行执行downloadToStream方法区网络中获取图片,接着在第67行判断上面获得的bitmap是否为空,如果非空的话会在第72行将其添加到内存缓存中,addBitmapToMemoryCache方法前面已经介绍过了,不再赘述,那么downloadBitmap方法大致分析结束了;
downloadBitmap结束之后doInBackground方法也就执行结束啦,最后返回我们获取到的bitmap对象;
到现在,我们获取到了bitmap对象对象,但是如果我们是通过异步任务获取的bitmap对象的话,我们将其设置到控件上面是什么时候呢?
在BitmapUtils里面我们找到了onPostExecute方法,这个方法就是就是我们在doInBackground执行结束之后会回调的方法啦,AsyncTask源码中已经分析过了:
@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());
}
}
}
这段代码在控件非空以及返回的图片非空的情况下会调用callback的onLoadCompleted方法,这个方法是我们在创建callback的时候自己实现的,如果bitmap为空的话,调用callback的onLoadFailed方法,这个方法同样也是在创建callback的时候自己实现的,这样的话就能够方便的在图片加载成功或者失败之后我们对用户界面进行不同的操作提示了;
至此,BitmapUtils源码答题分析结束了,希望有错的地方大家可以纠正,赠人玫瑰,手留余香!!!!!