最近除了那些忙着项目开发的事情,目前正在准备我的论文。短的时间没有写博客,今晚难得想总结。只要有一点时间。因此,为了凑合用,行。唠叨罗嗦,直接进入正题。

从事Android自移动终端的发展,想必是常常要与内存问题打交道的,说到Android开发中遇到的内存问题,像Bitmap这样的吃内存的大户略微处理不当就非常easy造成OOM,当然,眼下已经有非常多知名的开源图片载入框架,比如:ImageLoader。Picasso等等,这些框架已经能够非常好的攻克了Bitmap造成的OOM问题,尽管这些框架能够节省非常多开发人员的宝贵时间。可是也会遇到一种情况。非常多刚開始学习的人仅仅是会简单的去调用这些框架的提供的接口,被问到框架内部的一些实现原理,基本上都是脑中一片空白。从我的观点出发,我觉得假设能够掌握一些框架原理,想必对我们进行应用调优的意义是非常重大的,今天,主要是是想谈谈。假设没有了图片载入框架,我们要怎么去处理Bitmap的内存问题呢?

谈到Bitmap处理的问题,我们可能要先来了解一些基础的知识,关于Bitmap在Android虚拟机中的内存分配,在Google的站点上给出了以下的一段话



大致的意思也就是说。在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalvik的VM堆中。而像素数据的内存是分配在Native堆中,而到了Android3.0之后。Bitmap的内存则已经所有分配在VM堆上。这两种分配方式的差别在于,Native堆的内存不受Dalvik虚拟机的管理。我们想要释放Bitmap的内存,必须手动调用Recycle方法。而到了Android 3.0之后的平台,我们就能够将Bitmap的内存全然放心的交给虚拟机管理了,我们仅仅须要保证Bitmap对象遵守虚拟机的GC Root Tracing的回收规则就可以。OK。基础知识科普到此。接下来分几个要点来谈谈怎样优化Bitmap内存问题。

1.Bitmap的引用计数方式(针对Android3.0之前平台的优化方案,先上Demo Code)

  1. private int mCacheRefCount = 0;//缓存引用计数器
  2. private int mDisplayRefCount = 0;//显示引用计数器
  3. ...
  4. // 当前Bitmap是否被显示在UI界面上
  5. public void setIsDisplayed(boolean isDisplayed) {
  6. synchronized (this) {
  7. if (isDisplayed) {
  8. mDisplayRefCount++;
  9. mHasBeenDisplayed = true;
  10. } else {
  11. mDisplayRefCount--;
  12. }
  13. }
  14. checkState();
  15. }
  16. //标记是否被缓存
  17. public void setIsCached(boolean isCached) {
  18. synchronized (this) {
  19. if (isCached) {
  20. mCacheRefCount++;
  21. } else {
  22. mCacheRefCount--;
  23. }
  24. }
  25. checkState();
  26. }
  27. //用于检測Bitmap是否已经被回收
  28. private synchronized void checkState() {
  29. if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
  30. && hasValidBitmap()) {
  31. getBitmap().recycle();
  32. }
  33. }
  34. private synchronized boolean hasValidBitmap() {
  35. Bitmap bitmap = getBitmap();
  36. return bitmap != null && !bitmap.isRecycled();
  37. }

上面的实例代码,它使用了引用计数的方法(mDisplayRefCount 与 mCacheRefCount)来追踪一个bitmap眼下是否有被显示或者是在缓存中. 当以下条件满足时回收bitmap:

mDisplayRefCount 与 mCacheRefCount 的引用计数均为 0.

bitmap不为null, 而且它还没有被回收.

2.使用缓存,LruCache和DiskLruCache的结合

关于LruCache和DiskLruCache,大家一定不会陌生(有疑问的朋友能够去API官网搜一下LruCache,而DiskLrucCache能够參考一下这篇不错的文章:DiskLruCache使用介绍),出于对性能和app的考虑,我们肯定是想着第一次从网络中载入到图片之后,能够将图片缓存在内存和sd卡中。这样,我们就不用频繁的去网络中载入图片,为了非常好的控制内存问题,则会考虑使用LruCache作为Bitmap在内存中的存放容器,在sd卡则使用DiskLruCache来统一管理磁盘上的图片缓存。

3.SoftReference和inBitmap參数的结合

在第二点中提及到,能够採用LruCache作为存放Bitmap的容器,而在LruCache中有一个方法值得留意,那就是entryRemoved,依照文档给出的说法,在LruCache容器满了须要淘汰存放当中的对象腾出空间的时候会调用此方法(注意。这里仅仅是对象被淘汰出LruCache容器,但并不意味着对象的内存会马上被Dalvik虚拟机回收掉),此时能够在此方法中将Bitmap使用SoftReference包裹起来,并用事先准备好的一个HashSet容器来存放这些即将被回收的Bitmap。有人会问。这样存放有什么意义?之所以会这样存放,还须要再提及到inBitmap參数(在Android3.0才開始有的,详情查阅API中的BitmapFactory.Options參数信息)。这个參数主要是提供给我们进行复用内存中的Bitmap,假设设置了此參数,且满足以下条件的时候:

  • Bitmap一定要是可变的,即inmutable设置一定为ture;
  • Android4.4以下的平台,须要保证inBitmap和即将要得到decode的Bitmap的尺寸规格一致;
  • Android4.4及其以上的平台,仅仅须要满足inBitmap的尺寸大于要decode得到的Bitmap的尺寸规格就可以;

在满足以上条件的时候。系统对图片进行decoder的时候会检查内存中是否有可复用的Bitmap。避免我们频繁的去SD卡上载入图片而造成系统性能的下降,毕竟从直接从内存中复用要比在SD卡上进行IO操作的效率要提高几十倍。写了太多文字。以下接着给出几段Demo Code

  1. Set<SoftReference<Bitmap>> mReusableBitmaps;
  2. private LruCache<String, BitmapDrawable> mMemoryCache;
  3. // 用来盛放被LruCache淘汰出列的Bitmap
  4. if (Utils.hasHoneycomb()) {
  5. mReusableBitmaps =
  6. Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
  7. }
  8. mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
  9. // 当LruCache淘汰对象的时候被调用,用于在内存中重用Bitmap,提高载入图片的性能
  10. @Override
  11. protected void entryRemoved(boolean evicted, String key,
  12. BitmapDrawable oldValue, BitmapDrawable newValue) {
  13. if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
  14. ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
  15. } else {
  16. if (Utils.hasHoneycomb()) {
  17. mReusableBitmaps.add
  18. (new SoftReference<Bitmap>(oldValue.getBitmap()));
  19. }
  20. }
  21. }
  22. ....
  23. }
  24. private static void addInBitmapOptions(BitmapFactory.Options options,
  25. ImageCache cache) {
  26. //将inMutable设置true,inBitmap生效的条件之中的一个
  27. options.inMutable = true;
  28. if (cache != null) {
  29. // 尝试寻找能够内存中课复用的的Bitmap
  30. Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
  31. if (inBitmap != null) {
  32. options.inBitmap = inBitmap;
  33. }
  34. }
  35. }
  36. // 获取当前能够满足复用条件的Bitmap,存在则返回该Bitmap,不存在则返回null
  37. protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
  38. Bitmap bitmap = null;
  39. if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
  40. synchronized (mReusableBitmaps) {
  41. final Iterator<SoftReference<Bitmap>> iterator
  42. = mReusableBitmaps.iterator();
  43. Bitmap item;
  44. while (iterator.hasNext()) {
  45. item = iterator.next().get();
  46. if (null != item && item.isMutable()) {
  47. if (canUseForInBitmap(item, options)) {
  48. bitmap = item;
  49. iterator.remove();
  50. break;
  51. }
  52. } else {
  53. iterator.remove();
  54. }
  55. }
  56. }
  57. }
  58. return bitmap;
  59. }
  60. //推断是否满足使用inBitmap的条件
  61. static boolean canUseForInBitmap(
  62. Bitmap candidate, BitmapFactory.Options targetOptions) {
  63. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  64. // Android4.4開始,被复用的Bitmap尺寸规格大于等于须要的解码规格就可以满足复用条件
  65. int width = targetOptions.outWidth / targetOptions.inSampleSize;
  66. int height = targetOptions.outHeight / targetOptions.inSampleSize;
  67. int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
  68. return byteCount <= candidate.getAllocationByteCount();
  69. }
  70. // Android4.4之前,必须满足被复用的Bitmap和请求的Bitmap尺寸规格一致才干被复用
  71. return candidate.getWidth() == targetOptions.outWidth
  72. && candidate.getHeight() == targetOptions.outHeight
  73. && targetOptions.inSampleSize == 1;
  74. }

4.减少採样率,inSampleSize的计算

相信大家对inSampleSize是一定不会陌生的,所以此处不再做过多的介绍,关于减少採样率对inSampleSize的计算方法。我看到网上的算法有非常多。以下的这段算法应该是最好的算法了,当中还考虑了那种宽高相差非常悬殊的图片(比如:全景图)的处理。

  1. public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {
  2. // Raw height and width of image
  3. final int height = options.outHeight;
  4. final int width = options.outWidth;
  5. int inSampleSize = 1;
  6. if (height > reqHeight || width > reqWidth) {
  7. final int halfHeight = height / 2;
  8. final int halfWidth = width / 2;
  9. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
  10. inSampleSize *= 2;
  11. }
  12. long totalPixels = width / inSampleSize * height / inSampleSize ;
  13. final long totalReqPixelsCap = reqWidth * reqHeight * 2;
  14. while (totalPixels > totalReqPixelsCap) {
  15. inSampleSize *= 2;
  16. totalPixels /= 2;
  17. }
  18. }
  19. return inSampleSize;

5.採用decodeFileDescriptor来编码图片(临时不知道原理。欢迎高手指点迷津)

关于採用decodeFileDescriptor去处理图片能够节省内存这方面。我在写代码的时候进行过尝试。确实想比其它的decode方法要节省内存,查询了网上的解释。不是非常清楚,自己看了一些源码也弄不出个名堂,为什么使用这样的方式就能够节省内存一些呢,假设有明确当中原理的高手。欢迎解答我的疑惑

到此,关于Bitmap处理的几个优化点已经分析完成,就眼下来说,可能大家在开发的过程习惯了使用框架来载入图片,所以不大在意图片内存处理的相关问题,假设你想知道一些优化Bitmap内存原理或者想自己做一个优秀的图片载入框架。希望本文能够为你提供一点点思路。假设读者觉得文章有错误,欢迎在下方评论中批评指正。

版权声明:本文博客原创文章。博客,未经同意,不得转载。

Android性能优化:谈话Bitmap内存管理和优化的更多相关文章

  1. 性能优化-Bitmap内存管理及优化

    Bitmap作为重要Android应用之一,在很多时候如果应用不当,很容易造成内存溢出,那么这篇文章的目的就在于探讨Bitmap的有效运用及其优化 缓存介绍 当多次发送请求的时候,请求同一内容,为了使 ...

  2. 每个Android开发者必须知道的内存管理知识

    原文:每个Android开发者必须知道的内存管理知识 拷贝在此处,以备后续查看. 相信一步步走过来的Android从业者,每个人都会遇到OOM的情况.如何避免和防范OOM的出现,对于每一个程序员来说确 ...

  3. Android 内存管理之优化建议

    OOM(OutOfMemory)转:http://hukai.me/android-performance-oom/ 前面我们提到过使用getMemoryClass()的方法可以得到Dalvik He ...

  4. oracle基础——内存管理、优化

    内存图解: 自动管理:11g:AMM   10g:ASMM SGA(system global area):由所有服务进程和后台进程共享 PGA(program global area): 由每个服务 ...

  5. 【Android手机测试】linux内存管理 -- 一个进程占多少内存?四种计算方法:VSS/RSS/PSS/USS

    在Linux里面,一个进程占用的内存有不同种说法,可以是VSS/RSS/PSS/USS四种形式,这四种形式首字母分别是Virtual/Resident/Proportional/Unique的意思. ...

  6. iOS内存管理和优化 from 刘延军

  7. Android性能优化:手把手带你全面实现内存优化

      前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的内存优化,希望你们会喜欢 目录   1. 定义 优化处理 应用程序的内存使用.空间占用 2. 作用 避免因不正确使用内 ...

  8. android 管理Bitmap内存 - 开发文档翻译

    由于本人英文能力实在有限,不足之初敬请谅解 本博客只要没有注明“转”,那么均为原创,转贴请注明本博客链接链接   Managing Bitmap Memory 管理Bitmap内存 In additi ...

  9. Android性能优化--Listview优化

    ListView的工作原理 首先来了解一下ListView的工作原理(可参见http://mobile.51cto.com/abased-410889.htm),如图: ListView 针对每个it ...

随机推荐

  1. 代理下载android4.4源代码

    前提条件:需要有user, password, 代理人serverip和port(这一切都使自己的软件来完成下一个.例如freegate,它拥有一套可以自己作为一个代理server.创user/pas ...

  2. Java 理论与实践: 正确使用 Volatile 变量(转)

    Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”:与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少, ...

  3. 你所不了解的float(滥用float的怪异现象) (转)

    阅读目录 float设计初衷就是为了实现文字环绕效果 如何解决浮动造成的父容器塌陷? 兼容各浏览器清除浮动的通用方式 滥用浮动 运用浮动的一些特性 浮动与布局 浮动与单侧固定布局 浮动与智能自适应的流 ...

  4. GCD下载图片

    cell.myimage.layer.masksToBounds=YES;     cell.myimage.layer.cornerRadius=cell.myimage.frame.size.wi ...

  5. lsblk请参阅块设备

    lsblk可以查看分区和挂载的磁盘使用情况 lsblk全部的參数 -a, --all            显示全部设备  -b, --bytes          以bytes方式显示设备大小  - ...

  6. 杭州电 1052 Tian Ji -- The Horse Racing(贪婪)

    http://acm.hdu.edu.cn/showproblem.php? pid=1052 Tian Ji -- The Horse Racing Time Limit: 2000/1000 MS ...

  7. POJ 3280 间隔DP

    字符串,每次插入或删除字符需要一定的价格,问:我怎样才能使这个字符串转换成字符串回文,花最少. 间隔DP 当DP到区间[i,j+1]时,我们能够在i-1的位置加入一个str[j+1]字符,或者将在j+ ...

  8. Notepad++ 删除空行

    先选中要删部分文本内容,假设是整个文件那就全选Ctrl+A,然后使用Notepad++自带的Textfx插件,在长长的列表中找到Delete Blank Lines,点击就可以. 例如以下图:

  9. Zend_Db_Table::getDefaultAdapter is not working

    在Bootstrap中使用 $url = constant ( "APPLICATION_PATH" ) . DIRECTORY_SEPARATOR . 'configs' . D ...

  10. Js 对象添加属性

    var arr = new Array(); arr[0] = jQuery("#data1").val(); var obj = {}; obj.y='abc'; arr.pus ...