Android异步加载全解析之Bitmap

Android异步加载全解析之Bitmap

在这篇文章中,我们分析了Android在对大图处理时的一些策略——Android异步加载全解析之大图处理  戳我戳我

那么在这篇中,我们来对图像——Bitmap进行一个更加细致的分析,掌握Bitmap的点点滴滴。


引入

Bitmap这玩意儿号称Android App头号杀手,特别是3.0之前的版本,简直就是皇帝般的存在,碰不得、摔不得。虽然后面的版本Android对Bitmap的管理也进行了一系列的优化,但是它依然是非常难处理的一个东西。在Android异步加载中,很多时候,访问网络比较慢,慢的也就是图片了,文字刷一下就出来了,但是图片却始终不出来,这让很多看&**&%&)*&图片的人,非常郁闷啊。但没办法,大家都喜欢看图片,所以还是得好好搞搞图像。

Bitmap内存

在曾经的版本中,也就是Android2.3时代,Bitmap的对象引用与它真实的像素数据其实是分开存储的,Bitmap的对象引用存储在Dalvik虚拟机堆内存heap中,但像素数据保存在系统内存中,我们需要调用recycle()方法来释放像素信息的内存,但是如果你释放之后又使用了Bitmap,那程序就会FC了。
进化到Android3.0之后,Bitmap的对象引用与像素数据都保存到Dalvik虚拟机堆内存heap中了。这样,Bitmap内存资源的回收就完全交给GC了,所以这样可以避免非常多的OOM问题,而且,在2.3时代,GC是单线程的,一回收就卡界面,而到3.0之后,GC就改为并发执行的了,不会在阻塞工作线程。
虽然系统给我们做了大量幕后的事,但是由于各种使用Bitmap的代码问题导致的OOM同样会让程序FC,所以我们在使用Bitmap的时候,同样还是非常需要注意的。例如避免内存泄露、内存溢出,当然,你也可以在mainifest文件的application中增加android:largeHeap = “true”来让系统给你特殊照顾,使用更多的内存空间,但是,这样毕竟不是解决问题的方法,只能作为保命技能来使用,切记、切记!

Android异步加载全解析之大图处理

这个已经不用再讲了,请参考 戳我戳我

Android异步加载全解析之引入缓存

缓存是非常重要的一个优化Bitmap存储策略的方法,这个也不用再讲了,请参考  先戳我,再戳我
不过这里有几点需要再提下,不管是LruCache还是DiskLruCache,设置缓存的大小,都不是一概而论的,需要根据项目中Bitmap的使用频率、Bitmap像素大小等值来进行综合判断。

SoftReference和BitmapFactory.Options.inBitmap参数

从Android3.0开始,引入了BitmapFactory.Options.inBitmap,这果这个字段被设置了,则解码时会把重用这个字段所引用的那张Bitmap,避免重新分配内存。但使用这个属性时有一些条件:
1、重用的Bitmap和即将被解码的Bitmap必须是相同的尺寸,且是JPEG或者PNG格式的。
2、 BitmapFactory.Options.inPreferredConfig字段设置无效,因为会被重用的Bitmap的configuration所覆盖。
3、一定要使用解码方法返回的Bitmap,因为重用可能会失败。
4、Bitmap一定要是可变的,即inmutable设置一定为ture,记住,加载进来的原图,都是不可修改的。

另外,在Android 3.0 (API Level 11),及以上的版本中,当Bitmap被从LruCache中挤出时,我们可以把这个Bitmap的soft reference存起来当作以后解码时的inBitmap来使用。
这种方式你可以在开发者网站上找到详细的示例,我就不拿出来装比拉~~
开发者必备利器——http://developer.android.com/training/displaying-bitmaps/manage-memory.html
这里我们对官网的Demo再做一点进一步的解释。
我们在使用缓存来优化Bitmap的时候,使用到了LruCache,而LruCache中有这样一个方法——entryRemoved,按照文档给出的说法,在LruCache容器满了需要淘汰存放其中的对象腾出空间的时候会调用此方法不过需要注意的是,这里只是对象被淘汰出LruCache容器,但并不意味着对象的内存会立即被Dalvik虚拟机回收掉。这个时候,我们可以在此方法中将Bitmap使用SoftReference包裹起来,并用事先准备好的一个HashSet容器来存放这些即将被回收的Bitmap。
当满足上面我们列举出来的条件的时候,系统对图片进行decoder的时候会检查内存中是否有可复用的Bitmap,避免我们频繁的去SD卡上加载图片而造成系统性能的下降,毕竟SD卡比内存速度慢太多了。
剩下的大家就使用官网的Demo就OK了,好了,装比结束。

再PS下,我们通常在使用decodeStream、decodeFile来解析获取到的流信息,例如:
URL url = new URL(urlString);  
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
        is = new BufferedInputStream(conn.getInputStream());  
        bitmap = BitmapFactory.decodeStream(is);  

其实,系统还给我们提供了一个更加高效的方法——decodeFileDescriptor,它为啥高效呢,其实是一个历史原因,大家都知道,C比Java略快,所以,decodeFileDescriptor比decodeStream略快,就是因为decodeFileDescriptor在源码中是使用底层库来解析的,例如:
FileInputStream is = = new FileInputStream(path);
bmp = BitmapFactory.decodeFileDescriptor(is.getFD(), null, opts);
因此,在调用DiskLruCache来使用的时候,这个方法就可以比decodeFile更快。这里我们也提供一个完整的调用代码:
public static OutputStream decodeBitmap(String path) {
 
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;// 设置成了true,不占用内存,只获取bitmap宽高
        BitmapFactory.decodeFile(path, opts);
        opts.inSampleSize = computeSampleSize(opts, -1, 1024 * 800);
 
        opts.inJustDecodeBounds = false;// 这里一定要将其设置回false,因为之前我们将其设置成了true
        opts.inPurgeable = true;
        opts.inInputShareable = true;
        opts.inDither = false;
        opts.inPurgeable = true;
        opts.inTempStorage = new byte[16 * 1024];
        FileInputStream is = null;
        Bitmap bmp = null;
        InputStream ins = null;
        ByteArrayOutputStream baos = null;
        try {
            is = new FileInputStream(path);
            bmp = BitmapFactory.decodeFileDescriptor(is.getFD(), null, opts);           
            double scale = getScaling(opts.outWidth * opts.outHeight, 1024 * 600);
            Bitmap bmp2 = Bitmap.createScaledBitmap(bmp,
                    (int) (opts.outWidth * scale),
                    (int) (opts.outHeight * scale), true);
            bmp.recycle();
            baos = new ByteArrayOutputStream();
            bmp2.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            bmp2.recycle();
            return baos;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                ins.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.gc();
        }
        return baos;
    }
 
private static double getScaling(int src, int des) {
/**
 * 目标尺寸÷原尺寸 sqrt开方,得出宽高百分比
 */
    double scale = Math.sqrt((double) des / (double) src);
    return scale;
}

Bitmap二次采样技术

请原谅我有一次装比了,其实二次采样技术,听上去很牛逼,实际上就是利用我们在《大图处理》中使用的方法  戳我戳我 也就是inSampleSize,当我们加载大图的时候,可以先快速的加载一张质量非常低的缩略图,等下载原图完毕后,再加载。

Bitmap图像压缩

大小压缩

通过inSampleSize方法,其实我们就已经实现了对Bitmap的压缩,不过这种压缩是基于大小来进行压缩的,由于前面我们已经讲过了,这里就不再说了。

质量压缩

下面我们介绍一种基于质量的压缩方法,当然,这种方式也是API——compress方法,所以也不需要太多算法。
private Bitmap compressImage(Bitmap bitmap) {
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
	bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
	int options = 100;
	//循环判断如果压缩后图片是否大于100kb,大于继续压缩
	while (baos.toByteArray().length / 1024 > 100) {
		//重置baos即清空baos
		baos.reset();
		//这里压缩options%,把压缩后的数据存放到baos中
		bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
		//每次都减少10
		options -= 10;
	}
	//把压缩后的数据baos存放到ByteArrayInputStream中
	ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
	//把ByteArrayInputStream数据生成图片
	Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
	return bitmap;
}

我的Github
我的视频 慕课网






郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。