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即清空baosbaos.reset();//这里压缩options%,把压缩后的数据存放到baos中bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//每次都减少10options -= 10;}//把压缩后的数据baos存放到ByteArrayInputStream中ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把ByteArrayInputStream数据生成图片Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);return bitmap;}

我的Github
我的视频慕课网






更多相关文章

  1. 混合开发之ReactNative调用Android原生方法
  2. Android投屏电脑反向控制软件QtScrcpy使用方法
  3. 内存泄漏,关于异步回调导致的内存泄漏,使用LeakCanary检测内存泄漏
  4. Android内存管理机制和内存泄漏分析及优化
  5. Android中Activity全局共享方法AppContext
  6. 查看Android内存的8中方法
  7. Window下android 模拟器SD卡的使用方法
  8. Android虚拟键盘弹出时挡住EditText解决方法
  9. Android:用DialogFragment实现LoadingDialog等待加载框

随机推荐

  1. android打包apk流程
  2. androidのEditTex详细使用
  3. 关于Android的组件和进程的理解
  4. Android联系人数据库全解析(2)
  5. Android学习笔记:Android消息处理机制之Ha
  6. Android版本更新
  7. [Android5.1]开机动画显示工作流程分析
  8. 创建Android第一个工程
  9. 疯狂Android讲义下载
  10. Android快速开源框架--afinal