BitmapFun使用与深入学习

转载请注明出处:http://blog.csdn.net/evan_man/article/details/51347340

背景介绍

根据Android开发文档的说明:每个应用程序会获得至少16MB的独立内存空间;因此要确保自己的图片加载程序在16MB的情况下依然能运转起来。

在ListView、Gridview、RecyclerView中图片加载存在的问题有

  • 图片显示错位
  • 无法确保所有的异步任务能够按顺序执行
  • 当用户快速滑动时,ImageView已经被回收,而绑定的线程还在运行,浪费CPU,浪费内存。

解决方案(思路)

  • ImageView和Task无法对应时则取消任务
  • ImageView和Task绑定准确的加载对应图片
  • ImageView和Task相互持有对方的引用

Android加载图片常用优化手段(该部分主要体现在ImageResizer部分)

获取当前应用要求的图片显示的高度和宽度

  • 全屏模式下显示的最大宽度、长度(如点击看大图的场景):

    • final DisplayMetrics displayMetrics = new DisplayMetrics();
    • getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    • final int height = displayMetrics.heightPixels; //当前手机屏幕高度
    • final int width = displayMetrics.widthPixels;  //当前手机屏幕宽度
    • final int longest = (height > width ? height : width) / 2; //在此我们限定图片最长宽度的二分之一,这里会降低图片分辨率,但是大大节省了加载内存
  • listView中显示的最大宽度、长度(查看缩略图场景):
    • 下面的代码是在GridView场景下的代码
    • mImageThumbSize = 100dp;
    • mImageThumbSpacing = 1dp;
    • final int numColumns = (int) Math.floor( mGridView.getWidth() / (mImageThumbSize + mImageThumbSpacing));
    • final int columnWidth =   (mGridView.getWidth() / numColumns) - mImageThumbSpacing;
    • 其实我们可以直接设置缩略图为mImageThumbSize、但是为了更精确所以求取columnWidth作为预期值

获取待加载图片的维度和类型信息

BitmapFactory有decodeByteArray(), decodeFile(), decodeResource()三种方式将图片资源加载成Bitmap类型数据。默认情况下这些方法都会尝试为构造得到的Bitmap分配内存,因此容易造成OOM。三种方式都有一个额外的签名——BitmapFactory.Options,通过它可以设置解码的一些选项。设置inJustDecodeBounds属性为真,三个方法将不会分配内存,但是会设置对应BitmapFactory.Options的outWidth,
outHeight 和outMimeType属性,通过这样的方法获取即将加载的图片的大小和类型。

检测待加载图片的维度和类型信息的代码:

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource(getResources(), R.id.myimage, options);

int imageHeight = options.outHeight;

int imageWidth = options.outWidth;

String imageType = options.outMimeType;

载入低维度图片进入内存

BitmapFactory.Options的inSampleSize属性,它的值用于产生一个比当前图片更小的图。inSampleSize等于4,则通过BitmapFactory获得的Bitmap为原始图片的四分之一宽度高度,inSampleSize小于1,等价于inSampleSize等于1.

下面是相关代码:

options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

options.inJustDecodeBounds = false;

Bitmap bitmap = BitmapFactory.decodeResource(res, resId, options); //获得预期的缩小版Bitmap

//下面的方法第二个是预期宽度,第三个是预期高度

public static int calculateInSampleSize(  BitmapFactory.Options options, int reqWidth, int reqHeight) {

final int height = options.outHeight;

final int width = options.outWidth;

int inSampleSize = 1;

if (height > reqHeight || width > reqWidth) {

final int halfHeight = height / 2;

final int halfWidth = width / 2;

while ((halfHeight / inSampleSize) > reqHeight  && (halfWidth / inSampleSize)
> reqWidth) {

inSampleSize *= 2;

}

}

return inSampleSize;

}

设置内存缓存大小——根据当前系统资源

LruCache<String, Bitmap> mMemoryCache;
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); //这是VM所能提供的最大内存使用数量,超过这个值将抛出OOM异常
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override   protected int sizeOf(String key, Bitmap bitmap) {
            return bitmap.getByteCount() / 1024; //根据记载图片实际大小计算size而不是根据图片数量
        }
};

针对不同Android版本的优化

2.3.3(level 10)以及更低版本Bitmap的原生像素是直接存储在原生内存中,而Bitmap是存在Dalvik堆中的。原生内存的垃圾回收不是高效的,容易造成OOM。因此一旦确定一个bitmap不会再被使用就调用Bitmap的recycle方法。

3.0(level 11)以及更高版本Bitmap的原生像素和关联Bitmap是存在Dalvik堆中的。BitmapFactory.Options.inBitmap属性标志构造bitmap的时候尝试重复使用已经加载好的bitmap内容。

简单使用:

一、创建一个ImageFetcher对象

private ImageFetcher mImageFetcher;
mImageFetcher = new ImageFetcher(getActivity(), mImageThumbSize);
mImageFetcher.setLoadingImage(R.drawable.empty_photo);
mImageFetcher.addImageCache(getActivity().getSupportFragmentManager(), cacheParams);
  • ImageCache.ImageCacheParams cacheParams = new ImageCache.ImageCacheParams(getActivity(), IMAGE_CACHE_DIR);
  • cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory

二、使用ImageFetcher对象加载图片

mImageFetcher.loadImage(mImageUrl, mImageView);

第一个参数是网络地址(String),第二个参数是网络加载图片要显示的ImageView控件

三、ImageFetcher一些高级使用

mImageFetcher.flushCache();
mImageFetcher.closeCache();
mImageFetcher.clearCache();
mImageFetcher.setImageSize(height);
mImageFetcher.setPauseWork(true);
mImageFetcher.setExitTasksEarly(false);

源码分析

分析目的:

  • 创建ImageFetcher对象
  • ImageFetcher.loadImage方法内部原理
  • 了解ImageFetcher的缓存、网络访问、破解图片显示混乱、取消无效任务的实现
  • 其它高级功能的底层实现(需要注意的地方)
    • 调用mImageFetcher.flushCache()、mImageFetcher.closeCache() 、mImageFetcher.clearCache()其效果依次是调用ImageWorker的mImageCache.flush()、{mImageCache.close(); mImageCache = null;}、mImageCache.clearCache()和ImageFetcher的flushCacheInternal()、closeCacheInternal()、clearCacheInternal()方法;
    • 调用mImageFetcher.setImageSize(height)其效果是调用ImageResizer的同名方法
    • 调用mImageFetcher.setPauseWork(true)其效果是调用ImageWork的同名方法
    • 调用mImageFetcher.setExitTasksEarly(false)其效果是调用ImageWork的同名方法

ImageFetcher.class

ImageFetcher主要任务是完成从网络上下载图片资源。

private File mHttpCacheDir; //网络文件的缓存目录
private final Object mHttpDiskCacheLock = new Object(); //对象锁
private boolean mHttpDiskCacheStarting = true;   //缓存初始化标志
private DiskLruCache mHttpDiskCache;             //磁盘缓存对象
private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB  //缓存文件最大容量

ImageFetcher().ImageFetcher.class

public ImageFetcher(Context context, int imageWidth, int imageHeight) {
        super(context, imageWidth, imageHeight);
        init(context);
}
public ImageFetcher(Context context, int imageSize) {
        super(context, imageSize);
        init(context);
}

都是调用父类的同名构造器进行,同时执行init方法。

init()@ImageFetcher.class

private void init(Context context) {
        checkConnection(context);
        mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR);
}
private void checkConnection(Context context) {
        final ConnectivityManager cm =  (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
        if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) { Toast.makeText(context, R.string.no_network_connection_toast, Toast.LENGTH_LONG).show(); }
}

init方法很简单就是首先检查当前网络是否可能,不可用弹出一条toast;

给ImageFetcher对象的mHttpCacheDir域进行初始化,即创建一个用于缓存网络图片的文件夹;

setLoadingImage()@ImageFetcher.class

ImageFetcher和其父类ImageSizer都没有重写该方法,该方法的定义只会出现在ImageWorker类中。

addImageCache()@ImageFetcher.class

ImageFetcher和其父类ImageSizer都没有重写该方法,该方法的定义只会出现在ImageWorker类中。虽然ImageFetcher中没有重写addImageCache方法,但是该方法最终会调用initDiskLruCache方法,而ImageFetcher重写了该方法!下面我们看看该方法完成了哪些工作

initDiskCacheInternal()@ImageFetcher.class

@Override protected void initDiskCacheInternal() {
        super.initDiskCacheInternal();
        initHttpDiskCache();
}
private void initHttpDiskCache() {
        if (!mHttpCacheDir.exists()) {  mHttpCacheDir.mkdirs(); }  //note1
        synchronized (mHttpDiskCacheLock) {
            if (ImageCache.getUsableSpace(mHttpCacheDir) > HTTP_CACHE_SIZE) { //HTTP_CACHE_SIZE 等于 10MB
                 mHttpDiskCache = DiskLruCache.open(mHttpCacheDir, 1, 1, HTTP_CACHE_SIZE); //note2
            }
            mHttpDiskCacheStarting = false;
            mHttpDiskCacheLock.notifyAll(); //note3
        }
}

1、没有目录则创建目录

2、如果当前目录下之前有缓存文件,则利用日志文件恢复缓存信息;如果没有缓存文件则创建日志文件

3、唤醒正在等待的线程

上面的代码都是介绍创建ImageFetcher对象所涉及的源代码,接下来我们介绍下如何利用ImageFetcher获取指定url的图片资源。

loadImage()@ImageFetcher.class

ImageFetcher和其父类ImageSizer都没有重写该方法,该方法的定义只会出现在ImageWorker类中。该方法会先从ImageWork的内存缓存中尝试获取数据,如果获取不到则最终会调用下面的processBitmap方法,从磁盘中或者网络中获取数据。

processBitmap@mageResizer.class

@Override   protected Bitmap processBitmap(Object data) {
        return processBitmap(String.valueOf(data));
}
private Bitmap processBitmap(String data) {
        final String key = ImageCache.hashKeyForDisk(data);
        FileDescriptor fileDescriptor = null;
        FileInputStream fileInputStream = null;
        DiskLruCache.Snapshot snapshot;
        synchronized (mHttpDiskCacheLock) {
            while (mHttpDiskCacheStarting) {
                try { mHttpDiskCacheLock.wait();} catch (InterruptedException e) {} //note1
            }
            if (mHttpDiskCache != null) {
                try {
                    snapshot = mHttpDiskCache.get(key);
                    if (snapshot == null) {//note2
                        DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
                        if (editor != null) {//note3
                            if (downloadUrlToStream(data,editor.newOutputStream(DISK_CACHE_INDEX))) {
                                editor.commit();
                            } else {editor.abort();}
                        }
                        snapshot = mHttpDiskCache.get(key);
                    }
                    if (snapshot != null) { //note4
                        fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
                        fileDescriptor = fileInputStream.getFD();
                    }
                }
                catch (IOException e) {.... }
                finally {
                    if (fileDescriptor == null && fileInputStream != null) {
                        try { fileInputStream.close();} catch (IOException e) {}
                    }
                }//end of finally
            }//end of if
        }//end of synchronized
        Bitmap bitmap = null;
        if (fileDescriptor != null) {
            bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth, mImageHeight, getImageCache());//note5
        }
        if (fileInputStream != null) {
            try { fileInputStream.close(); } catch (IOException e) {}
        }
        return bitmap;
}

1、能进入到这里,表明缓存区还没初始化结束,进入休眠状态;当缓存区初始化结束会收到一个唤醒信号。

2、利用key从缓存区中获取到对应的DiskLruCache.Snapshot对象,该对象会有一个对缓存文件的InputStream引用,可以利用它读取文件数据

3、如果获取不到snapshot则证明当前磁盘中没有对应的缓存,因此需要从网上下载;先获取到一个DiskLruCache.Editor对象(会先创建一个相应文件),该对象是一个向key对应的文件进行写入数据的工具。

4、从Snapshot中获取到对应InputStream,并利用InputStream创建一个FileDescriptor对象

5、利用ImageResizer中定义的ecodeSampledBitmapFromDescriptor(fileDescrip方法获取一个Bitmap对象;这里的 mImageWidth, mImageHeight来自于ImageResizer类

注意:这里我们对于DiskLruCache的介绍并不详细,只是稍微过了一遍,更加深入的分析可以参考另一篇博客《OkHttp深入学习(三)——Cache》里面的DiskLruCache跟这里的DiskLruCache如出一辙,甚至怀疑它们有相互抄袭的可能,有兴趣的读者可以跳过去看看。

downloadUrlToStream()@mageResizer.class

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;
        }
        ....
        return false;
}

利用url创建一个HttpUrlConnection连接,然后获取输入输出流,逻辑很简单这里不细讲了。

ImageResizer.class

ImageResizer类的主要作用就是对图像的处理,如在限制大小的情况下利用给定的文件描述符得到一个Bitmap对象。

public class ImageFetcher extends ImageResizer

protected int mImageWidth;
protected int mImageHeight;

ImageResizer()@ImagerResizer.class

public ImageResizer(Context context, int imageWidth, int imageHeight) {
        super(context);
        setImageSize(imageWidth, imageHeight);
}
public ImageResizer(Context context, int imageSize) {
        super(context);
        setImageSize(imageSize);
}

该构造器首先调用父类的同名构造方法,随后调用setImageSize方法

SetImageSize()@mageResizer.class

public void setImageSize(int width, int height) {
        mImageWidth = width;
        mImageHeight = height;
}
public void setImageSize(int size) {
        setImageSize(size, size);
}

该方法只是对ImagerFetcher的protected int mImageWidth; protected int mImageHeight;两个域进行初始化

decodeSampledBitmapFromDescriptor()@mageResizer.class

public static Bitmap decodeSampledBitmapFromDescriptor( FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {
        final BitmapFactory.Options options = new BitmapFactory.Options();  // note1
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); //note2
        options.inJustDecodeBounds = false;
        if (Utils.hasHoneycomb()) {addInBitmapOptions(options, cache);} //note3
        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
    }

1、使用inJustDecodeBounds=true检查维度

2、计算缩小的倍数,2的幂

3、Android 3.0(level-11)及以上版本则会执行这行代码;一种针对3.0更高版本的优化

4、返回Bitmap对象

ImageWorker.class

ImageWork是整个BitmapFun最核心的一个内,前面的ImageFetcher和ImageResizer都是继承自该类。图片加载的除了网络部分和产生Bitmap对象由子类实现,其它内容都是由ImageWork实现。

public class ImageResizer extends ImageWorker

protected Resources mResources;
private ImageCache mImageCache;
private Bitmap mLoadingBitmap;
private ImageCache.ImageCacheParams mImageCacheParams;

private final Object mPauseWorkLock = new Object();
protected boolean mPauseWork = false; //暂停标志位
private boolean mExitTasksEarly = false;  //提前退出标志位

ImageWorker()@ImageWorker.class

protected ImageWorker(Context context) {
        mResources = context.getResources();
}

从context中获取到对应的getResources对象;

setLoadingImage()@ImageWorker.class

public void setLoadingImage(Bitmap bitmap) {
        mLoadingBitmap = bitmap;
}

只是简单的对ImageWorker的mLoadingBitmap域进设置;该域用于指定在异步加载时ImageView显示的图片。

addImageCache()@ImageWorker.class

public void addImageCache(FragmentManager fragmentManager,  ImageCache.ImageCacheParams cacheParams) {
        mImageCacheParams = cacheParams;
        mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE); //note1
}

1、在非UI线程中调用initDiskCacheInternal()方法

2、这里使用Fragment的好处在于,当手机屏幕旋转的时候Fragment可以重复被使用,很多资源不需要重新加载

initDiskCacheInternal()@ImageWorker.class

protected void initDiskCacheInternal() {
        if (mImageCache != null) {   mImageCache.initDiskCache();  }
}

该方法会被子类ImageFetcher重写,但是最后也还是会调用到这里。

loadImage()@ImageWorker.class

public void loadImage(Object data, ImageView imageView) {
        if (data == null) { return; }
        BitmapDrawable value = null;
        if (mImageCache != null) {
            value = mImageCache.getBitmapFromMemCache(String.valueOf(data)); //note1
        }
        if (value != null) { imageView.setImageDrawable(value); }
        else if (cancelPotentialWork(data, imageView)) { //note2
            final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView);
            final AsyncDrawable asyncDrawable =   new AsyncDrawable(mResources, mLoadingBitmap, task); //note3
            imageView.setImageDrawable(asyncDrawable); //note4
            task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR); //note5
        }
}

1、从缓存中(内存)尝试获取文件,获取到则直接将结果显示出来

2、调用cancelPotentialWork方法取消与当前ImageView绑定的异步任务

3、利用网址(String)和ImageView控件创建一个BitmapWorkTask对象,随后又利用该对象创建一个AsynDrawable对象

4、将该对象设置给imageView空间进行显示

5、开启BitmapWorkTask对象的任务;等价于将task的DoInBackground方法交给一个 Executors.newFixedThreadPool(2, sThreadFactory);线程池去执行。而这个线程池最多两个线程同时工作,而且更要命的是磁盘缓存文件和网络请求文件两个功能都是在这个线程池中工作,因此一旦两条线程在访问网络的过程中阻塞,那么我们也无法获取到本地的缓存文件。所以这部分如果要优化可以将缓存部分单独通过一条线程去访问。Volley也就是这么干的!

cancelPotentialWork()@ImageWorker.class

public static boolean cancelPotentialWork(Object data, ImageView imageView) {
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); //note1
        if (bitmapWorkerTask != null) {
            final Object bitmapData = bitmapWorkerTask.mData; //note2
            if (bitmapData == null || !bitmapData.equals(data)) {  bitmapWorkerTask.cancel(true); }
            else {  return false; }
        }
        return true; //note3
}

1、获取到与当前imageView绑定的异步任务

2、如果获取到的异步任务对应的url地址跟当前url地址不同则取消之前的异步任务,否则返回false;

3、该方法返回true表明针对当前的imageView需要创建一个新的BitmapWorkTask对象。

getBitmapWorkerTask()@ImageWorker.class

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;
}

看到这里突然想到了”ImageView和Task相互持有对方的引用“这句话。通过ImageView的setImageDrawable方法向其传递一个自定义的Drawable对象——AsyncDrawable,而AsyncDrawable中有一个异步任务——BitmapWorkerTask;BitmapWorkerTask的构建又是由url和ImageView所创建的;这样ImageView和BitmapWorkerTask之间实现了相互引用。

setExitTasksEarly()@ImageWorker.class

public void setExitTasksEarly(boolean exitTasksEarly) {
        mExitTasksEarly = exitTasksEarly;
        setPauseWork(false);
}

设置mExitTasksEarly提前退出任务标志位和mPauseWork暂停工作标志位,这些标志位会在BitmapWorkTask的DoInBackground方法中被不断检测

setPauseWork()@ImageWorker.class

public void setPauseWork(boolean pauseWork) {
        synchronized (mPauseWorkLock) {
            mPauseWork = pauseWork;
            if (!mPauseWork) {
                mPauseWorkLock.notifyAll();
            }
        }
}

设置mPauseWork暂停工作标志位、该标志位会在BitmapWorkTask的DoInBackground方法中被不断检测

[email protected]

BitmapWorkerTask实现了AsynTask方法,用于异步网上加载图片。

private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable>

private Object mData;
private final WeakReference<ImageView> imageViewReference; //所引用,不影响垃圾回收

BitmapWorkerTask()@BitmapWorkerTask.class

public BitmapWorkerTask(Object data, ImageView imageView) {
            mData = data;
            imageViewReference = new WeakReference<ImageView>(imageView);
}

doInBackground()@BitmapWorkerTask.class

@Override protected BitmapDrawable doInBackground(Void... params) {
            final String dataString = String.valueOf(mData);
            Bitmap bitmap = null;
            BitmapDrawable drawable = null;
            synchronized (mPauseWorkLock) {
                while (mPauseWork && !isCancelled()) { //note1
                    try {  mPauseWorkLock.wait(); } catch (InterruptedException e) {}
                }
            }
            if (mImageCache != null && !isCancelled() && getAttachedImageView() != null  && !mExitTasksEarly) {
                bitmap = mImageCache.getBitmapFromDiskCache(dataString); //note2
            }
            if (bitmap == null && !isCancelled() && getAttachedImageView() != null  && !mExitTasksEarly) {
                bitmap = processBitmap(mData); //note3
            }
            if (bitmap != null) {
                if (Utils.hasHoneycomb()) {  drawable = new BitmapDrawable(mResources, bitmap); }//note4
                else {  drawable = new RecyclingBitmapDrawable(mResources, bitmap); }
                if (mImageCache != null) {   mImageCache.addBitmapToCache(dataString, drawable); } //note5
            }
            return drawable;
        }

1、对定义在ImageWorker的Object mPauseWorkLock = new Object()对象、定义在ImageWorker的boolean mPauseWork = false对象进行判断。如果当前的BitmapWorkTask任务被暂停同时没有被取消则等待被唤醒。

2、从缓存中(磁盘中)获取目标图片

3、缓存中没有获取到数据,则通过processBitmap方法从网络上获取数据,ImageWorker的porcessBitmap方法如下:protected abstract
Bitmap processBitmap(Object data);即该方法是一个抽象方法。它的实现在其子类中。

4、根据当前系统版本的不同创建对应的Drawable类型对象,

5、将网上获取到的Bitmap存入缓存中

onPostExecute()@BitmapWorkerTask.class

@Override  protected void onPostExecute(BitmapDrawable value) {
            if (isCancelled() || mExitTasksEarly) {
                value = null;
            }
            final ImageView imageView = getAttachedImageView();
            if (value != null && imageView != null) {
                setImageDrawable(imageView, value); //note1
            }
}

1、设置一个真正的Drawable对象给Imageview,之前通过etImageDrawable方法传入的AsyncDrawable并不能显示什么数据,不过可以显示一个正在加载的图片。setImageDrawable(imageView, value)可以近似看成imageView.setImageDrawable(value);

onCancelled()@BitmapWorkTask.class

@Override   protected void onCancelled(BitmapDrawable value) {
            super.onCancelled(value);
            synchronized (mPauseWorkLock) {
                mPauseWorkLock.notifyAll(); //note1
            }
}

1、唤醒正在等待mPauseWorkLock锁的方法,但是这个时候调用isCancelled方法会返回true!

ImageCache.class

private DiskLruCache mDiskLruCache;  //磁盘缓存图片,这里的缓存是保证之前在视图中显示过;而ImageFetcher的DiskLruCache中文缓存可能还没有被显示过。

private LruCache<String, BitmapDrawable> mMemoryCache;//内存缓存图片

getDiskCacheDir()@getDiskCacheDir.class

public static File getDiskCacheDir(Context context, String uniqueName) {
        // Check if media is mounted or storage is built-in, if so, try and use external cache dir
        // otherwise use internal cache dir
        final String cachePath = ...获取一个绝对路径
        return new File(cachePath + File.separator + uniqueName);
}

BitmapFun到此就介绍完毕了,内容主要涉及到ImageWork、ImageResizer、ImageFetcher、ImageCache、ImageWorkTask这几个类。ImageWork是核心,其loadImage方法是加载图片的核心,该方法接收一个String
url网路地址和一个ImageView控件。该方法会首先从ImageCache的LruCache<String, BitmapDrawable>中获取一个BitmapDrawable对象,如果不行则将url和ImageView包装成一个BitmapWorkTask类进行异步网络访问,该类的DoInBackground方法内部又会从磁盘中即ImageCache的DiskLruCache中获取Bitmap对象,如果获取不到则通过ImageFetcher的processBitmap方法获取,该方法内部又会从ImageFetcher的DiskLruCache中获取一个文件读入流,如果没有这样一个对应的文件读入流则通过ImageFetcher的 downloadUrlToStream方法从网上获取图片,最后调用ImageResizer的decodeSampledBitmapFromDescriptor()方法根据之前设置的图片宽度和高度大小将得到的二进制文件转换成一个合适的BitmapDrawable对象,最后imageWork会将结果显示到ImageView中。

BitmapFun几个难点就是ImageView和ImageWorkTask相互引用,避免图片显示混乱的情况。BitmapWorkTask的doInBackgroud方法中会一直检测mPauseWork和mExitTasksEarly
两个标志位,使得我们可以暂停或者取消当前正在执行的异步的网络请求任务。Android加载Bitmap资源避免OOM异常。 最后对于缓存内容本节并没有深入的介绍,DiskLruCache可以参考博客《OkHttp深入学习(三)——Cache》。文中源码下载地址

reference:https://developer.android.com/intl/zh-cn/training/displaying-bitmaps/index.html

时间: 2024-08-13 18:32:36

BitmapFun使用与深入学习的相关文章

Android图片处理神器BitmapFun源码分析

作为一名Android开发人员,相信大家对图片OOM的问题已经耳熟能详了,关于图片缓存和解决OOM的开源项目也是相当的多,被大家熟知的就是Universal_image_loader和Volley了,Volley在前面的文章中已经有介绍.Universal_image_loader在图片缓存功能方面应该算功能最强的,但是感觉很多功能用不上,所以在项目中我一般不太喜欢使用Universal_image_loader(因为本身自己的App源码非常多,加入这些开源库就就更大了,容易出现无法编译的问题,

Vue.js学习笔记:属性绑定 v-bind

v-bind  主要用于属性绑定,Vue官方提供了一个简写方式 :bind,例如: <!-- 完整语法 --> <a v-bind:href="url"></a> <!-- 缩写 --> <a :href="url"></a> 绑定HTML Class 一.对象语法: 我们可以给v-bind:class 一个对象,以动态地切换class.注意:v-bind:class指令可以与普通的class特

Java多线程学习(吐血超详细总结)

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递 本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程

微信小程序学习总结(2)------- 之for循环,绑定点击事件

最近公司有小程序的项目,本人有幸参与其中,一个项目做下来感觉受益匪浅,与大家做下分享,欢迎沟通交流互相学习. 先说一下此次项目本人体会较深的几个关键点:微信地图.用户静默授权.用户弹窗授权.微信充值等等. 言归正传,今天分享我遇到的关于wx:for循环绑定数据的一个tips:  1. 想必大家的都知道wx:for,如下就不用我啰嗦了: <view class="myNew" wx:for="{{list}}">{{item.title}}<view

【安全牛学习笔记】

弱点扫描 ╋━━━━━━━━━━━━━━━━━━━━╋ ┃发现弱点                                ┃ ┃发现漏洞                                ┃ ┃  基于端口五福扫描结果版本信息(速度慢)┃ ┃  搜索已公开的漏洞数据库(数量大)      ┃ ┃  使用弱点扫描器实现漏洞管理            ┃ ╋━━━━━━━━━━━━━━━━━━━━╋ [email protected]:~# searchsploit Usage:

winform学习日志(二十三)---------------socket(TCP)发送文件

一:由于在上一个随笔的基础之上拓展的所以直接上代码,客户端: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Net.Sockets; using Sys

零基础的人该怎么学习JAVA

对于JAVA有所兴趣但又是零基础的人,该如何学习JAVA呢?对于想要学习开发技术的学子来说找到一个合适自己的培训机构是非常难的事情,在选择的过程中总是 因为这样或那样的问题让你犹豫不决,阻碍你前进的步伐,今天就让小编为您推荐培训机构新起之秀--乐橙谷Java培训机构,助力你成就好未来. 选择java培训就到乐橙谷 北京有什么好的Java培训机构?来乐橙谷北京学Java,零基础走起,乐橙谷Java基础班授课老师经验非常丰富,课程内容安排合理,适合于有一点点Java基础甚至一点都不会Java的同学学

最全解析如何正确学习JavaScript指南,必看!

划重点 鉴于时不时,有同学私信问我:怎么学前端的问题.这里统一回复一下,如下次再遇到问我此问题同学,就直接把本文链接地址发给你了. "前端怎么学"应该因人而异,别人的方法未必适合自己.就说说我的学习方法吧:我把大部分时间放在学习js上了.因为这个js的学习曲线,先平后陡.项目实践和练习啥的,我不说了,主要说下工作之外的时间利用问题.我是怎么学的呢,看书,分析源码.个人这几天统计了一下,前端书籍目前看了50多本吧,大部分都是js的.市面上的书基本,差不多都看过. 第一个问题:看书有啥好处

轻松学习C语言编程的秘诀:总结+灵感

目前在准备一套C语言的学习教程,所以我这里就以C语言编程的学习来讲.注意,讲的是"轻松学习",那种不注重方法,拼命玩命的方式也有其效果,但不是我提倡的.我讲究的是在方式方法对头.适合你.减轻你学习负担和心里压力的前提下,才适当的抓紧时间. 因此,探索一种很好的学习方法就是我所研究的主要内容. 众所周知,学习C语言并非易事,要学好它更是难上加难.这和你期末考试背会几个题目的答案考上满分没多大关系,也就是说你考试满分也说明不了你学好.学精通了C语言.那么怎么才算学精通C语言?闭着眼睛对自己