Android 那些你所不知道的Bitmap对象详解

我们知道Android系统分配给每个应用程序的内存是有限的, Bitmap作为消耗内存大户,我们对 Bitmap的管理稍有不当就可能引发 OutOfMemoryError,而 Bitmap对象在不同的Android版本中存在一些差异,今天就给大家介绍下这些差异,并提供一些在使用 Bitmap的需要注意的地方。
在Android2.3.3(API 10)及之前的版本中, Bitmap对象与其像素数据是分开存储的, Bitmap对象存储在Dalvik heap中,而 Bitmap对象的像素数据则存储在Native Memory(本地内存)中或者说Derict Memory(直接内存)中,这使得存储在Native Memory中的像素数据的释放是不可预知的,我们可以调用recycle()方法来对Native Memory中的像素数据进行释放,前提是你可以清楚的确定 Bitmap已不再使用了,如果你调用了 Bitmap对象recycle()之后再将 Bitmap绘制出来,就会出现"Canvas: trying to use a recycled bitmap"错误,而在Android3.0(API 11)之后, Bitmap的像素数据和 Bitmap对象一起存储在Dalvik heap中,所以我们不用手动调用recycle()来释放 Bitmap对象,内存的释放都交给垃圾回收器来做,也许你会问,为什么我在显示 Bitmap对象的时候还是会出现 OutOfMemoryError呢?
在说这个问题之前我顺便提一下,在Android2.2(API 8)之前,使用的是Serial垃圾收集器,从名字可以看出这是一个单线程的收集器,这里的”单线程"的意思并不仅仅是使用一个CPU或者一条收集线程去收集垃圾,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,Android2.3之后,这种收集器就被代替了,使用的是并发的垃圾收集器,这意味着我们的垃圾收集线程和我们的工作线程互不影响。
简单的了解垃圾收集器之后,我们对上面的问题举一个简单的例子,假如系统启动了垃圾回收线程去收集垃圾,而此时我们一下子产生大量的 Bitmap对象,此时是有可能会产生 OutOfMemoryError,因为垃圾回收器首先要判断某个对象是否还存活(JAVA语言判断对象是否存活使用的是根搜索算法 GC Root Tracing),然后利用垃圾回收算法来对垃圾进行回收,不同的垃圾回收器具有不同的回收算法,这些都是需要时间的, 发生 OutOfMemoryError的时候,我们要明确到底是因为内存泄露(Memory Leak)引发的还是内存溢出(Memory overflow)引发的,如果是内存泄露我们需要利用工具(比如MAT)查明内存泄露的代码并进行改正,如果不存在泄露,换句话来说就是内存中的对象确实还必须活着,那我们可以看看是否可以通过某种途径,减少对象对内存的消耗,比如我们在使用 Bitmap的时候,应该根据View的大小利用 BitmapFactory.Options计算合适的inSimpleSize来对 Bitmap进行相对应的裁剪,以减少 Bitmap对内存的使用,如果上面都做好了还是存在 OutOfMemoryError(一般这种情况很少发生)的话,那我们只能调大Dalvik heap的大小了,在Android 3.1以及更高的版本中,我们可以在AndroidManifest.xml的application标签中增加一个值等于“true”的 android:largeHeap属性来通知Dalvik虚拟机应用程序需要使用较大的Java Heap,但是我们也不鼓励这么做。
 
在Android 2.3及以下管理 Bitmap
从上面我们知道,在Android2.3及以下我们推荐使用recycle()方法来释放内存,我们在使用ListView或者GridView的时候,该在什么时候去调用recycle()呢?这里我们用到引用计数,使用一个变量(dispalyRefCount)来记录 Bitmap显示情况,如果 Bitmap绘制在View上面displayRefCount加一, 否则就减一, 只有在displayResCount为0且 Bitmap不为空且 Bitmap没有调用过recycle()的时候,我们才需求对该 Bitmap对象进行recycle(),所以我们需要用一个类来包装下 Bitmap对象,代码如下
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
     
     package  com.example.bitmap;
 
 
     private  int  displayResCount =  0 ;
     private  boolean  mHasBeenDisplayed;
 
         super (res, bitmap);
     }
     
     
     /**
      * @param isDisplay
      */
     public  void  setIsDisplayed( boolean  isDisplay){
         synchronized  ( this ) {
             if (isDisplay){
                 mHasBeenDisplayed =  true ;
                 displayResCount ++;
             } else {
                 displayResCount --;
             }
         }
         
         checkState();
         
     }
     
     /**
      * 检查图片的一些状态,判断是否需要调用recycle
      */
     private  synchronized  void  checkState() {
         if  (displayResCount <=  0  && mHasBeenDisplayed
         }
     }
     
     
     /**
      * 判断Bitmap是否为空且是否调用过recycle()
      * @return
      */
     private  synchronized  boolean  hasValid "Bitmap" href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmap() {
         return  bitmap !=  null  && !bitmap.isRecycled();
     }
 
}

除了上面这个RecycleBitmapDrawable之外呢,我们还需要一个自定义的ImageView来控制什么时候显示Bitmap以及什么时候隐藏Bitmap对象

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
     package  com.example.bitmap;
 
 
public  class  RecycleImageView  extends  ImageView {
     public  RecycleImageView(Context context) {
         super (context);
     }
 
     public  RecycleImageView(Context context, AttributeSet attrs) {
         super (context, attrs);
     }
 
     public  RecycleImageView(Context context, AttributeSet attrs,  int  defStyle) {
         super (context, attrs, defStyle);
     }
 
     @Override
     public  void  setImageDrawable(Drawable drawable) {
         Drawable previousDrawable = getDrawable();
         super .setImageDrawable(drawable);
         
         //显示新的drawable
         notifyDrawable(drawable,  true );
 
         //回收之前的图片
         notifyDrawable(previousDrawable,  false );
     }
 
     @Override
     protected  void  onDetachedFromWindow() {
         //当View从窗口脱离的时候,清除drawable
         setImageDrawable( null );
 
         super .onDetachedFromWindow();
     }
 
     /**
      * 通知该drawable显示或者隐藏
      *
      * @param drawable
      * @param isDisplayed
      */
     public  static  void  notifyDrawable(Drawable drawable,  boolean  isDisplayed) {
         if  (drawable  instanceof  Recycle "Bitmap" href= "http://www.android-study.com/wangluobiancheng/602.html" >BitmapDrawable) {
             ((Recycle "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >BitmapDrawable) drawable).setIsDisplayed(isDisplayed);
         else  if  (drawable  instanceof  LayerDrawable) {
             LayerDrawable layerDrawable = (LayerDrawable) drawable;
             for  ( int  i =  0 , z = layerDrawable.getNumberOfLayers(); i < z; i++) {
                 notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
             }
         }
     }
 
}

这个自定类也比较简单,重写了setImageDrawable()方法,在这个方法中我们先获取ImageView上面的图片,然后通知之前显示在ImageView的Drawable不在显示了,Drawable会判断是否需要调用recycle(),当View从Window脱离的时候会回调onDetachedFromWindow(),我们在这个方法中回收显示在ImageView的图片,具体的使用方法

?
1
2
3
ImageView imageView =  new  ImageView(context);
     imageView.setImageDrawable( new  Recycle "Bitmap" href= "http://www.android-study.com/wangluobiancheng/602.html" >BitmapDrawable(context.getResource(), bitmap));
    
只需要用Recycle BitmapDrawable包装 Bitmap对象,然后设置到ImageView上面就可以啦,具体的内存释放我们不需要管,是不是很方便呢?这是在Android2.3以及以下的版本管理 Bitmap的内存。
 
在Android 3.0及以上管理 Bitmap
由于在Android3.0及以上的版本中, Bitmap的像素数据也存储在Dalvik heap中,所以内存的管理就直接交给垃圾回收器了,我们并不需要手动的去释放内存,而今天讲的主要是 BitmapFactory.Options.in Bitmap的这个字段,假如这个字段被设置了,我们在解码 Bitmap的时候,他会去重用in Bitmap设置的 Bitmap,减少内存的分配和释放,提高了应用的性能,然而在Android 4.4之前, BitmapFactory.Options.in Bitmap设置的 Bitmap必须和我们需要解码的 Bitmap的大小一致才行,在Android4.4以后, BitmapFactory.Options.in Bitmap设置的 Bitmap大于或者等于我们需要解码的 Bitmap的大小就OK了,我们先假设一个场景,还是在使用ListView,GridView去加载大量的图片,为了提高应用的效率,我们通常会做相对应的内存缓存和硬盘缓存,这里我们只说内存缓存,而内存缓存官方推荐使用LruCache, 注意LruCache只是起到缓存数据作用,并没有回收内存。一般我们的代码会这么写

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
     
     package  com.example.bitmap;
 
import  java.lang.ref.SoftReference;
import  java.util.Collections;
import  java.util.HashSet;
import  java.util.Iterator;
import  java.util.Set;
 
 
public  class  ImageCache {
     private  final  static  int  MAX_MEMORY =  4  102  1024 ;
     private  LruCache "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >BitmapDrawable> mMemoryCache;
 
     private  Set "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmap>> mReusable "Bitmap" href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmaps;
 
     private  void  init() {
         if  (hasHoneycomb()) {
                     .synchronizedSet( new  HashSet "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmap>>());
         }
 
         mMemoryCache =  new  LruCache "Bitmap" href= "http://www.android-study.com/wangluobiancheng/602.html" >BitmapDrawable>(MAX_MEMORY) {
 
             /**
              * 当保存的BitmapDrawable对象从LruCache中移除出来的时候回调的方法
              */
             @Override
             protected  void  entryRemoved( boolean  evicted, String key,
 
                 if  (hasHoneycomb()) {
                             .get "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmap()));
                 }
             }
 
         };
     }
 
     
     /**
      * 从mReusableBitmaps中获取满足 能设置到BitmapFactory.Options.inBitmap上面的Bitmap对象
      * @param options
      * @return
      */
 
             synchronized  (mReusable "Bitmap" href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmaps) {
                 final  Iterator "Bitmap" href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmap>> iterator = mReusable "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmaps
                         .iterator();
 
                 while  (iterator.hasNext()) {
                     item = iterator.next().get();
 
                     if  ( null  != item && item.isMutable()) {
                         if  (canUseForIn "Bitmap" href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmap(item, options)) {
                             bitmap = item;
                             iterator.remove();
                             break ;
                         }
                     else  {
                         iterator.remove();
                     }
                 }
             }
         }
         return  bitmap;
     }
 
     /**
      * 判断该Bitmap是否可以设置到BitmapFactory.Options.inBitmap
      *
      * @param candidate
      * @param targetOptions
      * @return
      */
     @TargetApi (VERSION_CODES.KITKAT)
             "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >BitmapFactory.Options targetOptions) {
 
         // 在Anroid4.4以后,如果要使用inBitmap的话,只需要解码的Bitmap比inBitmap设置的小就行了,对inSampleSize
         // 没有限制
         if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
             int  width = targetOptions.outWidth / targetOptions.inSampleSize;
             int  height = targetOptions.outHeight / targetOptions.inSampleSize;
             int  byteCount = width * height
                     * getBytesPerPixel(candidate.getConfig());
             return  byteCount <= candidate.getAllocationByteCount();
         }
 
         // 在Android
         // 4.4之前,如果想使用inBitmap的话,解码的Bitmap必须和inBitmap设置的宽高相等,且inSampleSize为1
         return  candidate.getWidth() == targetOptions.outWidth
                 && candidate.getHeight() == targetOptions.outHeight
                 && targetOptions.inSampleSize ==  1 ;
     }
 
     /**
      * 获取每个像素所占用的Byte数
      *
      * @param config
      * @return
      */
     public  static  int  getBytesPerPixel(Config config) {
         if  (config == Config.ARGB_8888) {
             return  4 ;
         else  if  (config == Config.RGB_565) {
             return  2 ;
         else  if  (config == Config.ARGB_4444) {
             return  2 ;
         else  if  (config == Config.ALPHA_8) {
             return  1 ;
         }
         return  1 ;
     }
 
     @TargetApi (VERSION_CODES.HONEYCOMB)
     public  static  boolean  hasHoneycomb() {
         return  Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
     }
 
}
上面只是一些事例性的代码,将从LruCache中移除的 BitmapDrawable对象的弱引用保存在一个set中,然后从set中获取满足 BitmapFactory.Options.in Bitmap条件的 Bitmap对象用来提高解码 Bitmap性能,使用如下

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
         int  reqWidth,  int  reqHeight) {
 
     ...
     ...
 
     // If we're running on Honeycomb or newer, try to use inBitmap.
     if  (ImageCache.hasHoneycomb()) {
          options.inMutable =  true ;
 
             if  (cache !=  null ) {
 
                 if  (in "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >Bitmap !=  null ) {
                 }
             }
     }
     ...
     return  "Bitmap"  href= "http://www.android-study.com/wangluobiancheng/602.html" >BitmapFactory.decodeFile(filename, options);
}
通过这篇文章你是不是对 Bitmap对象有了更进一步的了解,在应用加载大量的 Bitmap对象的时候,如果你做到上面几点,我相信应用发生 OutOfMemoryError的概率会很小,并且性能会得到一定的提升,我经常会看到一些同学在评价一个图片加载框架好不好的时候,比较片面的以自己使用过程中是否发生 OutOfMemoryError来定论,当然经常性的发生 OutOfMemoryError你应该先检查你的代码是否存在问题,一般一些比较成熟的框架是不存在很严重的问题,毕竟它也经过很多的考验才被人熟知的,今天的讲解就到这里了,有疑问的同学可以在下面留言!

原著:http://www.android-study.com/wangluobiancheng/602.html

你可能感兴趣的:(Android图片,Android图片,bitmap)