博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android图片处理神器BitmapFun源码分析
阅读量:5135 次
发布时间:2019-06-13

本文共 18112 字,大约阅读时间需要 60 分钟。

作为一名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
{ //请求图片的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已经介绍了)中的解决方案,让一条线程专门处理本地图片,其他线程用于处理网络图片。
就写到这里吧,如果大家有什么没看明白或者我写错了的,欢迎留言.....

 

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

你可能感兴趣的文章
Red and Black(poj-1979)
查看>>
安装 Express
查看>>
存储(硬件方面的一些基本术语)
查看>>
观察者模式
查看>>
Weka中数据挖掘与机器学习系列之基本概念(三)
查看>>
Win磁盘MBR转换为GUID
查看>>
Java SE和Java EE应用的性能调优
查看>>
leetcode-Sort List
查看>>
中文词频统计
查看>>
了解node.js
查看>>
想做移动开发,先看看别人怎么做
查看>>
Eclipse相关集锦
查看>>
继承条款effecitve c++ 条款41-45
查看>>
Java泛型的基本使用
查看>>
1076 Wifi密码 (15 分)
查看>>
bzoj2038 [2009国家集训队]小Z的袜子(hose)
查看>>
Java反射机制及其Class类浅析
查看>>
Postman-----如何导入和导出
查看>>
移动设备显示尺寸大全 CSS3媒体查询
查看>>
图片等比例缩放及图片上下剧中
查看>>