作为一名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找到taskfinal 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=""> {//请求图片的urlprivate Object data;//持有ImageView的弱引用private final WeakReference imageViewReference;public BitmapWorkerTask(ImageView imageView) {imageViewReference = new WeakReference(imageView);}/*** Background processing.*/@Overrideprotected 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 futureif (bitmap != null) {if (Utils.hasHoneycomb()) {// Running on Honeycomb or newer, so wrap in a standard BitmapDrawabledrawable = new BitmapDrawable(mResources, bitmap);} else {// Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable// which will recycle automagicallydrawable = 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*/@Overrideprotected void onPostExecute(BitmapDrawable value) {// 如果取消了或者提前退出,那么不显示这个图片,直接设置nullif (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);}}@Overrideprotected 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 drawablefinal TransitionDrawable td =new TransitionDrawable(new Drawable[] {new ColorDrawable(android.R.color.transparent),drawable});// Set background to loading bitmapimageView.setBackgroundDrawable(new BitmapDrawable(mResources, mLoadingBitmap));imageView.setImageDrawable(td);td.startTransition(FADE_IN_TIME);} else {imageView.setImageDrawable(drawable);}}
}

分析完ImageWorker之后,我们发现在ImageWorker中已经提供了获取网络图片的方法loadImage,当我调用了此方法后,首先会试图从内存缓存获取图片,如果获取成功,直接返回,如果没有获取成功,则启动一个BitmapWorkerTask,使用异步线程获取图片,在异步线程中,首先到磁盘中获取,如果磁盘没有获取,最后才从网络获取,我们发现在BitmapWorkerTask中是通过调用processBitmap方法完成图片获取的,但是这个方法是一个抽象方法,需要子类去实现,那我们到它的子类ImageResizer中

@Overrideprotected 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 initializewhile (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已经介绍了)中的解决方案,让一条线程专门处理本地图片,其他线程用于处理网络图片。

就写到这里吧,如果大家有什么没看明白或者我写错了的,欢迎留言.....

转载于:https://www.cnblogs.com/qingchen1984/p/5024615.html

Android图片处理神器BitmapFun源码分析相关推荐

  1. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  2. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  3. Android shortcut的使用及源码分析

    Android shortcut的使用及源码分析 最近遇到了一个切换国家码后部分应用的shortcut未更新的问题,就学习了shortcut的相关知识,在这里分享一下我了解的知识,希望能对大家有帮助. ...

  4. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

  5. 【Android 插件化】VirtualApp 源码分析 ( 启动应用源码分析 | HomePresenterImpl 启动应用方法 | VirtualCore 启动插件应用最终方法 )

    文章目录 一.启动应用源码分析 1.HomeActivity 启动应用点击方法 2.HomePresenterImpl 启动应用方法 3.VirtualCore 启动插件应用最终方法 一.启动应用源码 ...

  6. 【Android 插件化】VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )

    文章目录 一.添加应用源码分析 1.LaunchpadAdapter 适配器 2.适配器添加元素 3.PackageAppData 元素 一.添加应用源码分析 1.LaunchpadAdapter 适 ...

  7. 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | Instrumentation 源码分析 )

    文章目录 一.Instrumentation 源码分析 二.Instrumentation 创建 Application 相关的部分源码 dex 解密时 , 需要将 代理 Application 替换 ...

  8. 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | LoadedApk 源码分析 )

    文章目录 一.LoadedApk 源码分析 二.LoadedApk 源码 makeApplication 方法分析 dex 解密时 , 需要将 代理 Application 替换为 真实 Applic ...

  9. 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | ActivityThread 源码分析 )

    文章目录 一.ActivityThread 源码分析 二.ActivityThread 部分代码示例 dex 解密时 , 需要将 代理 Application 替换为 真实 Application ; ...

最新文章

  1. java拖动组件,[小娱乐] 一个能拖动组件、改变组件大小的容器
  2. ubuntu14 备份
  3. leetcode 101 Symmetric Tree
  4. demo:a spreadsheet-like application
  5. 利用linux shell自己主动顶贴
  6. python 如何快速判断列表是否相同_Python-检查列表中的所有元素是否相同
  7. ActiveMQ、RabbitMQ、RocketMQ、Kafka的介绍及优缺点说明
  8. Jenkins pipeline 入门到精通系列文章
  9. tomcat配置mysql数据源_Tomcat中配置mysql数据源
  10. MATLAB编程与应用系列-第3章 矩阵运算(4)
  11. delphi计算机语言排名,2020年3月TIOBE编程语言排行榜 Java继续蝉联榜首
  12. php主机卫士,Bypass 360主机卫士SQL注入防御(多姿势)
  13. 如何使用Python解锁星河远征军的科幻旅途
  14. 互联网创业赚钱规则,彻底释放自己的价值吧!丨国仁网络
  15. 广义相对论-学习记录5-第三章-张量分析与黎曼几何2
  16. 王爽《汇编语言》学习笔记
  17. 种草一个让程序员男友记住一辈子的神仙插件!
  18. 0x0000009f(0x0000009f蓝屏)
  19. 一文读懂 Git GitHub Gitee(码云)的操作
  20. 音视频环形缓冲区C语言实现

热门文章

  1. mysql exporter怎么配置_mysqld_exporter的源码分析和定制化(单个mysqld_exporter监控多个数据库实例)...
  2. Flash与组件:制作Slider组件
  3. 话里话外:按单制造企业用什么来做生产计划
  4. python人脸识别防小偷_基于python的人脸识别(检测人脸、眼睛、嘴巴、鼻子......)...
  5. go语言垃圾回收机制详解
  6. 快速创建精彩的Flash游戏 (二) Flash3D引擎简介
  7. VC中如何设置Socket的TCP/IP KeepAlive机制(MSDN)
  8. matlab imagesc clims,imagesc
  9. matlab求解集合覆盖问题,Set Cover Problem (集合覆盖问题)
  10. 计算机系统访问控制的功能,访问控制是为了限制访问主体对访问客体的访问权限,从而使计算机系统在合法范围内使用的安全措施,以下关于访问控制的叙述中,()是不正确的 - 信管网...