我们知道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对象),代码如下
01.
package
com.example.bitmap;
02.
import
android.content.res.Resources;
03.
import
android.graphics.Bitmap;
04.
import
android.graphics.drawable.BitmapDrawable;
05.
public
class
RecycleBitmapDrawable
extends
BitmapDrawable {
06.
private
int
displayResCount =
0
;
07.
private
boolean
mHasBeenDisplayed;
08.
public
RecycleBitmapDrawable(Resources res, Bitmap bitmap) {
09.
super
(res, bitmap);
10.
}
11.
12.
13.
/**
14.
* @param isDisplay
15.
*/
16.
public
void
setIsDisplayed(
boolean
isDisplay){
17.
synchronized
(
this
) {
18.
if
(isDisplay){
19.
mHasBeenDisplayed =
true
;
20.
displayResCount ++;
21.
}
else
{
22.
displayResCount --;
23.
}
24.
}
25.
26.
checkState();
27.
28.
}
29.
30.
/**
31.
* 检查图片的一些状态,判断是否需要调用recycle
32.
*/
33.
private
synchronized
void
checkState() {
34.
if
(displayResCount <=
0
&& mHasBeenDisplayed
35.
&& hasValidBitmap()) {
36.
getBitmap().recycle();
37.
}
38.
}
39.
40.
41.
/**
42.
* 判断Bitmap是否为空且是否调用过recycle()
43.
* @return
44.
*/
45.
private
synchronized
boolean
hasValidBitmap() {
46.
Bitmap bitmap = getBitmap();
47.
return
bitmap !=
null
&& !bitmap.isRecycled();
48.
}
49.
}
除了上面这个RecycleBitmapDrawable之外呢,我们还需要一个自定义的ImageView来控制什么时候显示Bitmap以及什么时候隐藏Bitmap对象
01.
package
com.example.bitmap;
02.
import
android.content.Context;
03.
import
android.graphics.drawable.Drawable;
04.
import
android.graphics.drawable.LayerDrawable;
05.
import
android.util.AttributeSet;
06.
import
android.widget.ImageView;
07.
public
class
RecycleImageView
extends
ImageView {
08.
public
RecycleImageView(Context context) {
09.
super
(context);
10.
}
11.
public
RecycleImageView(Context context, AttributeSet attrs) {
12.
super
(context, attrs);
13.
}
14.
public
RecycleImageView(Context context, AttributeSet attrs,
int
defStyle) {
15.
super
(context, attrs, defStyle);
16.
}
17.
@Override
18.
public
void
setImageDrawable(Drawable drawable) {
19.
Drawable previousDrawable = getDrawable();
20.
super
.setImageDrawable(drawable);
21.
22.
//显示新的drawable
23.
notifyDrawable(drawable,
true
);
24.
//回收之前的图片
25.
notifyDrawable(previousDrawable,
false
);
26.
}
27.
@Override
28.
protected
void
onDetachedFromWindow() {
29.
//当View从窗口脱离的时候,清除drawable
30.
setImageDrawable(
null
);
31.
super
.onDetachedFromWindow();
32.
}
33.
/**
34.
* 通知该drawable显示或者隐藏
35.
*
36.
* @param drawable
37.
* @param isDisplayed
38.
*/
39.
public
static
void
notifyDrawable(Drawable drawable,
boolean
isDisplayed) {
40.
if
(drawable
instanceof
RecycleBitmapDrawable) {
41.
((RecycleBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
42.
}
else
if
(drawable
instanceof
LayerDrawable) {
43.
LayerDrawable layerDrawable = (LayerDrawable) drawable;
44.
for
(
int
i =
0
, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
45.
notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
46.
}
47.
}
48.
}
49.
}
这个自定类也比较简单,重写了setImageDrawable()方法,在这个方法中我们先获取ImageView上面的图片,然后通知之前显示在ImageView的Drawable不在显示了,Drawable会判断是否需要调用recycle(),当View从Window脱离的时候会回调onDetachedFromWindow(),我们在这个方法中回收显示在ImageView的图片,具体的使用方法
1.
ImageView imageView =
new
ImageView(context);
2.
imageView.setImageDrawable(
new
RecycleBitmapDrawable(context.getResource(), bitmap));
只需要用RecycleBitmapDrawable包装下 (Bitmap对象),然后设置到ImageView上面就可以啦,具体的内存释放我们不需要管,是不是很方便呢?这是在Android2.3以及以下的版本管理Bitmap的内存。
在Android 3.0及以上管理Bitmap
由于在Android3.0及以上的版本中,Bitmap的像素数据也存储在Dalvik heap中,所以内存的管理就直接交给垃圾回收器了,我们并不需要手动的去释放内存,而今天讲的主要是BitmapFactory.Options.inBitmap的这个字段,假如这个字段被设置了,我们在解码Bitmap的时候,他会去重用inBitmap设置的Bitmap,减少内存的分配和释放,提高了应用的性能,然而在Android 4.4之前,BitmapFactory.Options.inBitmap设置的Bitmap必须和我们需要解码的Bitmap的大小一致才行,在Android4.4以后,BitmapFactory.Options.inBitmap设置的Bitmap大于或者等于我们需要解码的Bitmap的大小就OK了,我们先假设一个场景,还是在使用ListView,GridView去加载大量的图片,为了提高应用的效率,我们通常会做相对应的内存缓存和硬盘缓存,这里我们只说内存缓存,而内存缓存官方推荐使用LruCache, 注意LruCache只是起到缓存数据作用,并没有回收内存。一般我们的代码会这么写
001.
package
com.example.bitmap;
002.
import
java.lang.ref.SoftReference;
003.
import
java.util.Collections;
004.
import
java.util.HashSet;
005.
import
java.util.Iterator;
006.
import
java.util.Set;
007.
import
android.annotation.TargetApi;
008.
import
android.graphics.Bitmap;
009.
import
android.graphics.Bitmap.Config;
010.
import
android.graphics.BitmapFactory;
011.
import
android.graphics.drawable.BitmapDrawable;
012.
import
android.os.Build;
013.
import
android.os.Build.VERSION_CODES;
014.
import
android.support.v4.util.LruCache;
015.
public
class
ImageCache {
016.
private
final
static
int
MAX_MEMORY =
4
*
102
*
1024
;
017.
private
LruCache<String, BitmapDrawable> mMemoryCache;
018.
private
Set<SoftReference<Bitmap>> mReusableBitmaps;
019.
private
void
init() {
020.
if
(hasHoneycomb()) {
021.
mReusableBitmaps = Collections
022.
.synchronizedSet(
new
HashSet<SoftReference<Bitmap>>());
023.
}
024.
mMemoryCache =
new
LruCache<String, BitmapDrawable>(MAX_MEMORY) {
025.
/**
026.
* 当保存的BitmapDrawable对象从LruCache中移除出来的时候回调的方法
027.
*/
028.
@Override
029.
protected
void
entryRemoved(
boolean
evicted, String key,
030.
BitmapDrawable oldValue, BitmapDrawable newValue) {
031.
if
(hasHoneycomb()) {
032.
mReusableBitmaps.add(
new
SoftReference<Bitmap>(oldValue
033.
.getBitmap()));
034.
}
035.
}
036.
};
037.
}
038.
039.
/**
040.
* 从mReusableBitmaps中获取满足 能设置到BitmapFactory.Options.inBitmap上面的Bitmap对象
041.
* @param options
042.
* @return
043.
*/
044.
protected
Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
045.
Bitmap bitmap =
null
;
046.
if
(mReusableBitmaps !=
null
&& !mReusableBitmaps.isEmpty()) {
047.
synchronized
(mReusableBitmaps) {
048.
final
Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps
049.
.iterator();
050.
Bitmap item;
051.
while
(iterator.hasNext()) {
052.
item = iterator.next().get();
053.
if
(
null
!= item && item.isMutable()) {
054.
if
(canUseForInBitmap(item, options)) {
055.
bitmap = item;
056.
iterator.remove();
057.
break
;
058.
}
059.
}
else
{
060.
iterator.remove();
061.
}
062.
}
063.
}
064.
}
065.
return
bitmap;
066.
}
067.
/**
068.
* 判断该Bitmap是否可以设置到BitmapFactory.Options.inBitmap上
069.
*
070.
* @param candidate
071.
* @param targetOptions
072.
* @return
073.
*/
074.
@TargetApi
(VERSION_CODES.KITKAT)
075.
public
static
boolean
canUseForInBitmap(Bitmap candidate,
076.
BitmapFactory.Options targetOptions) {
077.
// 在Anroid4.4以后,如果要使用inBitmap的话,只需要解码的Bitmap比inBitmap设置的小就行了,对inSampleSize
078.
// 没有限制
079.
if
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
080.
int
width = targetOptions.outWidth / targetOptions.inSampleSize;
081.
int
height = targetOptions.outHeight / targetOptions.inSampleSize;
082.
int
byteCount = width * height
083.
* getBytesPerPixel(candidate.getConfig());
084.
return
byteCount <= candidate.getAllocationByteCount();
085.
}
086.
// 在Android
087.
// 4.4之前,如果想使用inBitmap的话,解码的Bitmap必须和inBitmap设置的宽高相等,且inSampleSize为1
088.
return
candidate.getWidth() == targetOptions.outWidth
089.
&& candidate.getHeight() == targetOptions.outHeight
090.
&& targetOptions.inSampleSize ==
1
;
091.
}
092.
/**
093.
* 获取每个像素所占用的Byte数
094.
*
095.
* @param config
096.
* @return
097.
*/
098.
public
static
int
getBytesPerPixel(Config config) {
099.
if
(config == Config.ARGB_8888) {
100.
return
4
;
101.
}
else
if
(config == Config.RGB_565) {
102.
return
2
;
103.
}
else
if
(config == Config.ARGB_4444) {
104.
return
2
;
105.
}
else
if
(config == Config.ALPHA_8) {
106.
return
1
;
107.
}
108.
return
1
;
109.
}
110.
@TargetApi
(VERSION_CODES.HONEYCOMB)
111.
public
static
boolean
hasHoneycomb() {
112.
return
Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
113.
}
114.
}
上面只是一些事例性的代码,将从LruCache中移除的BitmapDrawable对象的弱引用保存在一个set中,然后从set中获取满足BitmapFactory.Options.inBitmap条件的Bitmap对象用来提高解码Bitmap性能,使用如下
01.
public
static
Bitmap decodeSampledBitmapFromFile(String filename,
02.
int
reqWidth,
int
reqHeight) {
03.
final
BitmapFactory.Options options =
new
BitmapFactory.Options();
04.
...
05.
BitmapFactory.decodeFile(filename, options);
06.
...
07.
// If we're running on Honeycomb or newer, try to use inBitmap.
08.
if
(ImageCache.hasHoneycomb()) {
09.
options.inMutable =
true
;
10.
if
(cache !=
null
) {
11.
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
12.
if
(inBitmap !=
null
) {
13.
options.inBitmap = inBitmap;
14.
}
15.
}
16.
}
17.
...
18.
return
BitmapFactory.decodeFile(filename, options);
19.
}
通过这篇文章你是不是对Bitmap对象有了更进一步的了解,在应用加载大量的Bitmap对象的时候,如果你做到上面几点,我相信应用发生OutOfMemoryError的概率会很小,并且性能会得到一定的提升,我经常会看到一些同学在评价一个图片加载框架好不好的时候,比较片面的以自己使用过程中是否发生OutOfMemoryError来定论,当然经常性的发生OutOfMemoryError你应该先检查你的代码是否存在问题,一般一些比较成熟的框架是不存在很严重的问题,毕竟它也经过很多的考验才被人熟知的
相关推荐
1. **Bitmap bitmap**: 这是你要绘制的位图对象,通常是从资源文件、内存缓存或者直接创建的Bitmap对象。 2. **Rect src**: 源矩形,用于指定Bitmap中要绘制的子区域。这个矩形的坐标是相对于Bitmap本身的,(0, 0)...
Bitmap对象存储实际的像素数据,可以通过Bitmap对象进行图像的显示、裁剪、旋转、缩放等操作。以下是一些关于Bitmap的重要知识点: 1. **内存管理**: - `recycle()` 方法用于回收Bitmap占用的内存,一旦调用此...
Android Drawable和Bitmap的转换实例详解 通常我们需要通过代码去设置图片,就需要设置图片Bitmap和Drawable的转换,下面整理了几种方式 一、Bitmap转Drawable Bitmap bm=xxx; //xxx根据你的情况获取 ...
Android 中 Glide 获取图片 Path、Bitmap 用法详解 在 Android 开发中,加载图片是一个非常常见的需求,而 Glide 是一个非常流行的图片加载框架。今天,我们将详细介绍如何使用 Glide 获取图片的 Path 和 Bitmap。 ...
Bitmap对象代表了图片像素的数据,而Matrix则是一个二维变换矩阵,可以用来对Bitmap进行复杂的几何变换,如缩放、旋转、平移等。下面将详细介绍这两个关键概念以及如何实现图片的放大、缩小和旋转功能。 1. **...
Bitmap类的构造方法都是私有的,所以开发者不能直接new出一个Bitmap对象,只能通过BitmapFactory类的各种静态方法来实例化一个Bitmap。仔细查看BitmapFactory的源代码可以看到,生成Bitmap对象最终都是通过JNI调用...
Bitmap对象不仅能够读取和显示图像,还能执行各种图像操作,如裁剪、旋转、缩放等。了解Bitmap及其内存优化对于开发高性能、低内存消耗的应用至关重要。 1. **Bitmap基本操作** - `recycle()`: 释放Bitmap占用的...
### Android Bitmap 概念与操作详解 #### 一、引言 在Android开发过程中,`Bitmap` 是一个非常重要的类,它主要用于表示图像数据。通过`Bitmap` 可以进行图像的各种操作,如加载、显示、缩放、旋转等。此外,`...
根据提供的标题“Android应用开发详解.pdf”以及描述“Android应用开发详解.pdf”,我们可以推断这份文档主要涵盖了关于Android平台上的应用程序开发的相关知识和技术。虽然提供的部分内容似乎并不包含具体的信息,...
2. 避免内存泄漏:资源对象没关闭、构造 Adapter 时不习惯使用缓存的 convertView、没有及时释放对象的引用、不再使用 Bitmap 对象时调用 recycle() 释放内存。 3. 使用 application 的 context 来替代 activity 的 ...
尽管部分给定内容与所需生成的知识点关联性较低,但基于“Android优化技术详解”的标题及描述,我们将聚焦于Android平台上的应用优化策略和技术要点。 ### Android优化技术详解 #### 一、前言 随着移动互联网的...
android 通过uri获取bitmap图片并压缩 在 Android 开发中,获取并压缩图片是一项非常重要的任务。特别是在调用图库选择图片时,需要通过uri获取bitmap图片并进行压缩,以避免OutOfMemoryError(OOM)。本文将详细...
在Android开发中,Bitmap对象是处理图像数据的核心类,但如果不正确地操作,它可能导致应用程序出现内存溢出(OOM)问题。这是因为Android系统对每个应用的内存限制相对较严,尤其是在加载大尺寸或高分辨率的图片时...
【Android自定义组件开发详解】 Android自定义组件的开发是Android应用开发中的一个重要部分,它涉及到自定义View和ViewGroup的创建,以及对canvas和paint的深入理解和运用。自定义组件能够满足开发者对于UI设计的...
- 然后,通过调用`getDrawingCache()`方法,我们可以获取到EditText的绘图缓存,即一个Bitmap对象。这个Bitmap包含了EditText在屏幕上的当前显示状态,包括用户输入的所有文本和样式。 - 如果Bitmap不为空,我们...
此外,Android提供了Bitmap.createBitmap()方法,可以创建一个新的Bitmap对象,用于图像的裁剪、旋转、缩放等操作。例如,若要旋转一个BMP图像,可以先创建一个适当大小的空白Bitmap,然后使用Canvas的drawBitmap()...