Android图片处理神器BitmapFun源码分析
作为一名Android开发人员,相信大家对图片OOM的问题已经耳熟能详了,关于图片缓存和解决OOM的开源项目也是相当的多,被大家熟知的就是Universal_image_loader和Volley了,Volley在前面的文章中已经有介绍。Universal_image_loader在图片缓存功能方面应该算功能最强的,但是感觉很多功能用不上,所以在项目中我一般不太喜欢使用Universal_image_loader(因为本身自己的App源码非常多,加入这些开源库就就更大了,容易出现无法编译的问题,因为Android貌似对一个应用中的方法个数好像有限制,貌似是655**个吧,具体多少我也记不清)。
关于处理图片缓存上,我接触的两个播放器项目中,使用的都是BitmapFun,BitmapFun 是Google为Android开发提供了一个培训教程,既然是Google提供的,那么我觉得作为一名合格的Android开发人员很有必要学习学习,而且BitmapFun非常简单,基本可以满足我们项目中对于图片缓存处理需求了。
对于开源项目的学习,我通常很少在应用层面来学习的,因为如何使用一个开源项目的相关博客已经相当多了,而且写得都非常详细,对于大多数开源项目它都是自带sample的,所以如果想学习如何使用某个开源项目,好好研究sample就行了,但是我始终认为,熟悉经典开源项目源码才是王道。好了废话不多说,我们开始学习BitmapFun源码吧。
1、BitmapFun结构
BitmapFun和其他开源库的结构稍有不同,因为它仅仅是Google的培训教程,所以BitmapFun和它的sample放在了一个工程里面,结构图如下:上面部分是BitmapFun的应用,下面部分是BitmapFun的源码。

2、相关类介绍
在BitmapFun中最重要的一个类就是ImageFetcher,请求图片主要就是调用loadImage方法,但是这个类是继承ImageResizer,而ImageResizser是继承ImageWorker,所以我们就从ImageWorker开始学习吧
ImageWorker.java
/**
这个类用来封装一次图片的加载过程,包括使用从缓存中加载
*/
public abstract class ImageWorker {
private static final String TAG = ImageWorker;
//这个变量用于动画效果,没有实际意义
private static final int FADE_IN_TIME = 200;
//缓存,包括磁盘缓存和内存缓存
private ImageCache mImageCache;
//创建缓存需要的参数
private ImageCache.ImageCacheParams mImageCacheParams;
//加载过程中,ImageView显示的图片
private Bitmap mLoadingBitmap;
//是否使用渐变效果
private boolean mFadeInBitmap = true;
//是否提前退出任务,如果true,那么图片请求回来后是不会显示出来的
private boolean mExitTasksEarly = false;
//是否暂停任务
protected boolean mPauseWork = false;
private final Object mPauseWorkLock = new Object(); protected Resources mResources; private static final int MESSAGE_CLEAR = 0;
private static final int MESSAGE_INIT_DISK_CACHE = 1;
private static final int MESSAGE_FLUSH = 2;
private static final int MESSAGE_CLOSE = 3; protected ImageWorker(Context context) {
mResources = context.getResources();
} /**
* 请求一张图片的接口
* @param 图片url
* @param 要显示这种图片的ImageView
*/
public void loadImage(Object data, ImageView imageView) {
if (data == null) {
return;
} BitmapDrawable value = null;
//如果缓存对象不为空,那么从内存缓存中读取对象
if (mImageCache != null) {
value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
} if (value != null) {
// 内存缓存命中,那么直接显示
imageView.setImageDrawable(value);
} else if (cancelPotentialWork(data, imageView)) {
//内存缓存没有命中,那么创建一个图片请求Task,将imageView作为参数
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
//AsyncDrawable 是BitmapDrawable子类,主要用来存放当前任务的弱应用
final AsyncDrawable asyncDrawable =
new AsyncDrawable(mResources, mLoadingBitmap, task);
//将asyncDrawable设置到imageView中,这样imageView和当前任务就一一对应了
imageView.setImageDrawable(asyncDrawable); //调用AsyncTask的executeOnExecutor方法,这个AsyncTask和Android系统中的AsyncTask有些区别,但是使用上一样的
task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR, data);
}
} /**
* 设置加载过程中的默认图片
*
* @param bitmap
*/
public void setLoadingImage(Bitmap bitmap) {
mLoadingBitmap = bitmap;
} /**
* 将本地图片设置为默认图片
*
* @param resId
*/
public void setLoadingImage(int resId) {
mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);
} /**
* 添加一个缓冲对象,创建磁盘缓存时需要子线程中完成
* @param fragmentManager
* @param cacheParams The cache parameters to use for the image cache.
*/
public void addImageCache(FragmentManager fragmentManager,
ImageCache.ImageCacheParams cacheParams) {
mImageCacheParams = cacheParams;
mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
//完成磁盘缓存初始化
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
} /**
* Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
* caching.
* @param activity
* @param diskCacheDirectoryName See
* {@link ImageCache.ImageCacheParams#ImageCacheParams(Context, String)}.
*/
public void addImageCache(FragmentActivity activity, String diskCacheDirectoryName) {
mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName);
mImageCache = ImageCache.getInstance(activity.getSupportFragmentManager(), mImageCacheParams);
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
} /**
* 设置是否使用渐变效果
*/
public void setImageFadeIn(boolean fadeIn) {
mFadeInBitmap = fadeIn;
} //是否提前退出任务
public void setExitTasksEarly(boolean exitTasksEarly) {
mExitTasksEarly = exitTasksEarly;
setPauseWork(false);
} /**
* Subclasses should override this to define any processing or work that must happen to produce
* the final bitmap. This will be executed in a background thread and be long running. For
* example, you could resize a large bitmap here, or pull down an image from the network.
*
* @param data The data to identify which image to process, as provided by
* {@link ImageWorker#loadImage(Object, ImageView)}
* @return The processed bitmap
*/
protected abstract Bitmap processBitmap(Object data); /**
* @return The {@link ImageCache} object currently being used by this ImageWorker.
*/
protected ImageCache getImageCache() {
return mImageCache;
} /**
* Cancels any pending work attached to the provided ImageView.
* @param imageView
*/
public static void cancelWork(ImageView imageView) {
//通过ImageView找到task,为什么可以找到?因为imageView和task一一对应
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
//如果task不为空,那么取消
if (bitmapWorkerTask != null) {
bitmapWorkerTask.cancel(true);
if (BuildConfig.DEBUG) {
final Object bitmapData = bitmapWorkerTask.data;
Log.d(TAG, cancelWork - cancelled work for + bitmapData);
}
}
} /**
* Returns true if the current work has been canceled or if there was no work in
* progress on this image view.
* Returns false if the work in progress deals with the same data. The work is not
* stopped in that case.
*/
public static boolean cancelPotentialWork(Object data, ImageView imageView) {
//通过imageView找到task
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) {
//如果找到的task不为null,并且task的url和给定的url相同,那么取消任务
final Object bitmapData = bitmapWorkerTask.data;
if (bitmapData == null || !bitmapData.equals(data)) {
bitmapWorkerTask.cancel(true);
if (BuildConfig.DEBUG) {
Log.d(TAG, cancelPotentialWork - cancelled work for + data);
}
} else {
// The same work is already in progress.
return false;
}
}
return true;
} /**
* 通过iamgeView找到对应的Task
*/
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
} /**
* 一个请求图片的异步任务,
*/
private class BitmapWorkerTask extends AsyncTask<object, bitmapdrawable=""> {
//请求图片的url
private Object data;
//持有ImageView的弱引用
private final WeakReference imageViewReference; public BitmapWorkerTask(ImageView imageView) {
imageViewReference = new WeakReference(imageView);
} /**
* Background processing.
*/
@Override
protected BitmapDrawable doInBackground(Object... params) {
if (BuildConfig.DEBUG) {
Log.d(TAG, doInBackground - starting work);
} data = params[0];
final String dataString = String.valueOf(data);
Bitmap bitmap = null;
BitmapDrawable drawable = null; // 如果work已经暂停并且图片请求没有取消,那么就等待
synchronized (mPauseWorkLock) {
while (mPauseWork && !isCancelled()) {
try {
mPauseWorkLock.wait();
} catch (InterruptedException e) {}
}
} //如果有缓存,并且没有取消,当前弱引用中的imageView对应的task还是自己(task),那么从磁盘缓存中读取
//为什么在这里读磁盘缓存?因为磁盘缓存只能在异步线程读取,doingbackground就是在异步线程执行
if (mImageCache != null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly) {
bitmap = mImageCache.getBitmapFromDiskCache(dataString);
} //如果没有命中,并且没有取消,并且当前弱引用中的ImageView对应的task还是自己,那么请求网络图片,
//调用processBitmap方法,这个方法是个抽象的,在ImageFecter中实现
if (bitmap == null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly) {
bitmap = processBitmap(params[0]);
} // If the bitmap was processed and the image cache is available, then add the processed
// bitmap to the cache for future use. Note we don't check if the task was cancelled
// here, if it was, and the thread is still running, we may as well add the processed
// bitmap to our cache as it might be used again in the future
if (bitmap != null) {
if (Utils.hasHoneycomb()) {
// Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
drawable = new BitmapDrawable(mResources, bitmap);
} else {
// Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
// which will recycle automagically
drawable = new RecyclingBitmapDrawable(mResources, bitmap);
}
//将图片加入缓存
if (mImageCache != null) {
mImageCache.addBitmapToCache(dataString, drawable);
}
} if (BuildConfig.DEBUG) {
Log.d(TAG, doInBackground - finished work);
} return drawable;
} /**
* Once the image is processed, associates it to the imageView
*/
@Override
protected void onPostExecute(BitmapDrawable value) {
// 如果取消了或者提前退出,那么不显示这个图片,直接设置null
if (isCancelled() || mExitTasksEarly) {
value = null;
} final ImageView imageView = getAttachedImageView();
if (value != null && imageView != null) {
if (BuildConfig.DEBUG) {
Log.d(TAG, onPostExecute - setting bitmap);
}
//将图片显示出来
setImageDrawable(imageView, value);
}
} @Override
protected void onCancelled(BitmapDrawable value) {
super.onCancelled(value);
//任务取消了,必须通知后台线程停止等待
synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll();
}
} private ImageView getAttachedImageView() {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (this == bitmapWorkerTask) {
return imageView;
} return null;
}
} /**
*用于实现imageView和task一一对应的类
*/
private static class AsyncDrawable extends BitmapDrawable {
private final WeakReference bitmapWorkerTaskReference; public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference(bitmapWorkerTask);
} public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
} /**
* 显示图片,渐变显示或者普通显示
*
* @param imageView
* @param drawable
*/
private void setImageDrawable(ImageView imageView, Drawable drawable) {
if (mFadeInBitmap) {
// Transition drawable with a transparent drawable and the final drawable
final TransitionDrawable td =
new TransitionDrawable(new Drawable[] {
new ColorDrawable(android.R.color.transparent),
drawable
});
// Set background to loading bitmap
imageView.setBackgroundDrawable(
new BitmapDrawable(mResources, mLoadingBitmap)); imageView.setImageDrawable(td);
td.startTransition(FADE_IN_TIME);
} else {
imageView.setImageDrawable(drawable);
}
}
}
分析完ImageWorker之后,我们发现在ImageWorker中已经提供了获取网络图片的方法loadImage,当我调用了此方法后,首先会试图从内存缓存获取图片,如果获取成功,直接返回,如果没有获取成功,则启动一个BitmapWorkerTask,使用异步线程获取图片,在异步线程中,首先到磁盘中获取,如果磁盘没有获取,最后才从网络获取,我们发现在BitmapWorkerTask中是通过调用processBitmap方法完成图片获取的,但是这个方法是一个抽象方法,需要子类去实现,那我们到它的子类ImageResizer中
@Override
protected Bitmap processBitmap(Object data) {
return processBitmap(Integer.parseInt(String.valueOf(data)));
}
它调用的是另外一个重载的processBitmap方法,我们看看另外一个方法吧
private Bitmap processBitmap(int resId) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, processBitmap -  + resId);
        }
        return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
                mImageHeight, getImageCache());
    }
我们发现这个方法仅仅是用来加载本地图片的,那它是如何实现网络图片的加载的呢,如果你把ImageResizer源码通读一边,你会发现ImageResizer这个类的主要功能如下:
1、设置显示图片的sizse
2、从磁盘缓存中加载图片
所以从网络加载图片根本不是这个类的功能,聪明的同学马上就应该想到了ImageFetcher这个类,对!,我们就直接看看ImageFetcher这个类吧
private Bitmap processBitmap(String data) {
        final String key = ImageCache.hashKeyForDisk(data);
        FileDescriptor fileDescriptor = null;
        FileInputStream fileInputStream = null;
        DiskLruCache.Snapshot snapshot;
		//检查mHttpDiskCache是否已经初始化,这里一定要注意,mHttpDiskCache这个磁盘缓存是在ImageFetcher调用addImageCache时初始化的,如果你没有调用addImageCache
		//那么这里就会阻塞,从而无法获取图片,具体情况还请大家自己分析代码吧
        synchronized (mHttpDiskCacheLock) {
            // Wait for disk cache to initialize
            while (mHttpDiskCacheStarting) {
                try {
                    mHttpDiskCacheLock.wait();
                } catch (InterruptedException e) {}
            }
			//下面这段代码就是从mHttpDiskCache里面写入图片
            if (mHttpDiskCache != null) {
                try {
                    snapshot = mHttpDiskCache.get(key);
                    if (snapshot == null) {
                        if (BuildConfig.DEBUG) {
                            Log.d(TAG, processBitmap, not found in http cache, downloading...);
                        }
                        DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
                        if (editor != null) {
							//下载图片逻辑在这里
                            if (downloadUrlToStream(data,
                                    editor.newOutputStream(DISK_CACHE_INDEX))) {
                                editor.commit();
                            } else {
                                editor.abort();
                            }
                        }
                        snapshot = mHttpDiskCache.get(key);
                    }
                    if (snapshot != null) {
                        fileInputStream =
                                (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
                        fileDescriptor = fileInputStream.getFD();
                    }
                } catch (IOException e) {
                    Log.e(TAG, processBitmap -  + e);
                } catch (IllegalStateException e) {
                    Log.e(TAG, processBitmap -  + e);
                } finally {
                    if (fileDescriptor == null && fileInputStream != null) {
                        try {
                            fileInputStream.close();
                        } catch (IOException e) {}
                    }
                }
            }
        }
        Bitmap bitmap = null;
        if (fileDescriptor != null) {
			//调用ImageResizer中的方法来将mHttpDiskCache中的缓存生成指定大小的图片
            bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
                    mImageHeight, getImageCache());
        }
        if (fileInputStream != null) {
            try {
                fileInputStream.close();
            } catch (IOException e) {}
        }
        return bitmap;
    }
    /**
     * 从网络通过HttpURLConnection下载图片,并写入到磁盘缓存
     *
     * @param urlString The URL to fetch
     * @return true if successful, false otherwise
     */
    public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        disableConnectionReuseIfNecessary();
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            Log.e(TAG, Error in downloadBitmap -  + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {}
        }
        return false;
    }
好了,对于Bitmapfun的整个代码逻辑我就简单的分析到这里吧,其实了解了Bitmapfun的代码逻辑后,我们完全可以对其进行优化,我在这里仅仅提出一点可以优化的地方,优化的方法就交给大家完成吧
比如BitmapWorkerTask在获取图片的时候先是读取磁盘缓存,然后从网络获取,也就是说如果读取本地和读取网络图片时在同一条线程中完成的,这个时候就有可能出现一个问题,本地图片存在却无法加载出来:例如:在网络条件不好的情况下,前面的五个图片请求刚好用完了所有的线程,由于网络条件不好,一直没有返回,而第六个图片刚好有缓存,那么它是无法加载出来的,因为没有线程了,所以解决方案就是学习Volley(我前面的文章对于Volley已经介绍了)中的解决方案,让一条线程专门处理本地图片,其他线程用于处理网络图片。
就写到这里吧,如果大家有什么没看明白或者我写错了的,欢迎留言.....
Android图片处理神器BitmapFun源码分析的更多相关文章
- Android Small插件化框架源码分析
		Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ... 
- 【Android】Handler、Looper源码分析
		一.前言 源码分析使用的版本是 4.4.2_r1. Handler和Looper的入门知识以及讲解可以参考我的另外一篇博客:Android Handler机制 简单而言:Handler和Looper是 ... 
- Android View事件分发-从源码分析
		View事件分发-从源码分析 学习自 <Android开发艺术探索> https://blog.csdn.net/qian520ao/article/details/78555397?lo ... 
- Android线程间异步通信机制源码分析
		本文首先从整体架构分析了Android整个线程间消息传递机制,然后从源码角度介绍了各个组件的作用和完成的任务.文中并未对基础概念进行介绍,关于threadLacal和垃圾回收等等机制请自行研究. 基础 ... 
- Android 7.0 Gallery图库源码分析3 - 数据加载及显示流程
		前面分析Gallery启动流程时,说了传给DataManager的data的key是AlbumSetPage.KEY_MEDIA_PATH,value值,是”/combo/{/local/all,/p ... 
- Android JobService的使用及源码分析
		Google在Android 5.0中引入JobScheduler来执行一些需要满足特定条件但不紧急的后台任务,APP利用JobScheduler来执行这些特殊的后台任务时来减少电量的消耗.本文首先介 ... 
- Android服务之PackageManagerService启动源码分析
		了解了Android系统的启动过程的读者应该知道,Android的所有Java服务都是通过SystemServer进程启动的,并且驻留在SystemServer进程中.SystemServer进程在启 ... 
- [置顶]
        【Android实战】----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的
		一.简介 接上一篇[Android实战]----基于Retrofit实现多图片/文件.图文上传中曾说非常想搞明白为什么Retrofit那么屌.最近也看了一些其源码分析的文章以及亲自查看了源码,发现其对 ... 
- Android ViewManger解析 从ViewRoot 源码分析invalidate
		转载请标明出处:http://blog.csdn.net/sk719887916/article/details/48443429,作者:skay 通过学习了AndroidUI之绘图机基础知道 ... 
随机推荐
- bug记录_document.defaultview.getcomputedstyle()
			页面中使用document.defaultview.getcomputedstyle()在火狐下取不到值. 原本方法现在$(document).ready()中,换到window.onload里就可以 ... 
- Struts入门学习(一)
			刚开始学习框架的时候感觉很简单,都是用到javaEE的相关框架,自己就想研究源码,但是学了很久之后毫无头绪,所以还是扎扎实实学好Struts毕竟框架做起来要比自己写javaEE要简单,下面我们就来一步 ... 
- [ An Ac a Day ^_^ ] CodeForces 339A Helpful Maths
			熄灯了才想起来没写博客 赶紧水一道题碎觉…… #include<stdio.h> #include<iostream> #include<algorithm> #i ... 
- chapter8_2 预编译
			用luac程序可以生成一个预编译文件——二进制文件. 比如: luac -o prog.lc prog.lua --生成了prog.lc二进制文件 Lua解析器可以执行它就像执行普通lua代码一样. ... 
- Openjudge-计算概论(A)-求平均年龄
			描述: 班上有学生若干名,给出每名学生的年龄(整数),求班上所有学生的平均年龄,保留到小数点后两位. 输入第一行有一个整数n(1<= n <= 100),表示学生的人数.其后n行每行有1个 ... 
- 引用WCF地址报下载“https://xxx:8004/TerminalHandler.svc?disco”时出错问题
			服务发布了wcf服务后,在客户端引用发现出现以下错误 - 来自“DISCO 文档”的报告是“下载“https://servername:8004/TerminalHandler.svc?disco”时 ... 
- arguments对象,caller 和 callee
			arguments对象是比较特别的一个对象,arguments非常类似Array,但实际上又不是一个Array实例. 它指的是函数对象里的参数,且只能在函数内部使用. 使用 检测函数的参数个数,引用属 ... 
- Android:如何实现更换主题
			关键代码:setTheme(int ID); 注意点: 1.设置主题必须要在setContentView() 之前调用,所以需要写个Intent去重新开启Activity. 2.为了切换主题保证流畅性 ... 
- javaWEB总结(6):ServletRequest
			1.首先看ServletRequest的API javax.servlet Interface ServletRequest All Known Subinterfaces: HttpServletR ... 
- C#项目转php工作记录
			1.Visual Studio Ultimate 2012 静态激活密钥 RBCXF-CVBGR-382MK-DFHJ4-C69G8 http://blog.csdn.net/jpzy520/arti ... 
