您的位置:首页 > 移动开发 > Android开发

Android 内存优化 Bitmap

2015-12-11 11:06 453 查看
OOM(OutOfMemory)
错误

getResources().getDrawable(R.drawable.img);

BitmapFactory.decodeResource(getResources(),
R.drawable.img);

bitmap[i]
= BitmapFactory.decodeResource(getResources(), R.drawable.img);//会占据大量内存,0.17秒

BitmapFactory.decodeStream(getResources().openRawResource(R.drawable.img));//占据更小的内存,更快速度0.08秒,相对decodeResource而言

Drawable.createFromStream(getResources().openRawResource(R.drawable.img),
null);//同decodeResource


BitmapFactory.decodeResource和BitmapFactory.decodeStream,相信对于有过android
app开发经验的人来说都是很熟悉了。关于Bitmap的OOM问题,网上也有很多文章进行了分析,不少文章都说为避免OOM,最好使用BitmapFactory.decodeStream,但是具体说明原因的我至今没有找到,所以趁着10.1期间有空,就调查了一番,希望结果对大家能有帮助。

先来张时序图,看了这张图,估计很多问题都不用说明了:



下面做些说明:

1,函数externalBytesAvailable(...)的内存计算方法详细请参见文章《Android
Bitmap内存限制》;

2,Bitmap.createScaledBitmap时可能会对Bitmap进行缩放,缩放所使用的Options请参见下面这段代码:

[java] view
plaincopy

public static Bitmap decodeResourceStream(Resources res, TypedValue value,

InputStream is, Rect pad, Options opts) {

if (opts == null) {

opts = new Options();

}

if (opts.inDensity == 0 && value != null) {

final int density = value.density;

if (density == TypedValue.DENSITY_DEFAULT) {

opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;

} else if (density != TypedValue.DENSITY_NONE) {

opts.inDensity = density;

}

}

if (opts.inTargetDensity == 0 && res != null) {

opts.inTargetDensity = res.getDisplayMetrics().densityDpi;

}

return decodeStream(is, pad, opts);

}

当放大时,会加大造成OOM的可能性;

3,BitmapFactory.decodeResource比起BitmapFactory.decodeStream来说,在没有参数Options opts的情况下,确实是多占用了内存,因为多了BitmapFactory.finishDecode这一步,这里多了一个Bitmap.createScaledBitmap操作;

4,关于使用BitmapFactory.decodeResource和BitmapFactory.decodeStream的效率对比,大家可以参见文章《Android
内存优化测试》

5,关于文章《Android
内存优化测试》提到的使用BitmapDrawable是最节省内存的方法,原因是frameworks\base\core\java\android\content\res\Resources.java对Bitmap做了缓存处理,文中所有的array其实都是指向同一个Bitmap,这一点也可以通过Bitmap.toString的输出信息来证明。
转载:http://blog.csdn.net/imyfriend/article/details/8039767

1, 比较Drawable与Bitmap占用内存大小

2, 比较BitmapFactory类的decodeResource方法与decodeStream方法的效率

好吧,先来看第1个测试!

以下这个是测试加载1000个Drawable对象的代码,很简单的,我就不解释了!

public
class
Main extends Activity

{

int number =
1000;

Drawable[] array;

@Override

public void onCreate(Bundle
savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

array = new BitmapDrawable[number];

for(int i
= 0; i < number; i++)

{

Log.e("", "测试第" +
(i+1) + "张图片");

array[i]
= getResources().getDrawable(R.drawable.img);

}

}

}

输出结果:

04-07 21:49:25.248: D/szipinf(7828): Initializing inflate state

04-07 21:49:25.398: E/(7828): 测试第1张图片

04-07 21:49:25.658: D/dalvikvm(7828): GC_EXTERNAL_ALLOC freed 48K, 50% free 2692K/5379K, external 0K/0K, paused 24ms

04-07 21:49:25.748: E/(7828): 测试第2张图片

04-07 21:49:25.748: E/(7828): 测试第3张图片

………………

………………

04-07 21:49:26.089: E/(7828): 测试第998张图片

04-07 21:49:26.089: E/(7828): 测试第999张图片

04-07 21:49:26.089: E/(7828): 测试第1000张图片

程序没有报错,正常运行,加载1000个Drawable对象没问题。

下面再来看一下加载1000个Bitmap对象的代码,同样的,代码很简单的,我就不解释了!

public class Main extends Activity

{

int number =
1000;

Bitmap
bitmap[];

@Override

public void onCreate(Bundle
savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

bitmap = new Bitmap[number];

for (int i
= 0; i < number;
i++)

{

Log.e("", "测试第" +
(i+1) + "张图片");

bitmap[i]
= BitmapFactory.decodeResource(getResources(), R.drawable.img);

}

}

}

输出结果:

04-07 22:06:05.344: D/szipinf(7937): Initializing inflate state

04-07 22:06:05.374: E/(7937): 测试第1张图片

04-07 22:06:05.544: D/dalvikvm(7937): GC_EXTERNAL_ALLOC freed 51K, 50% free 2692K/5379K, external 0K/0K, paused 40ms

04-07 22:06:05.664: E/(7937): 测试第2张图片

04-07 22:06:05.774: D/dalvikvm(7937): GC_EXTERNAL_ALLOC freed 1K, 50% free 2691K/5379K, external 6026K/7525K, paused 31ms

04-07 22:06:05.834: E/(7937): 测试第3张图片

04-07 22:06:05.934: D/dalvikvm(7937): GC_EXTERNAL_ALLOC freed <1K, 50% free 2691K/5379K, external 12052K/14100K, paused
24ms

04-07 22:06:06.004: E/(7937): 测试第4张图片

04-07 22:06:06.124: D/dalvikvm(7937): GC_EXTERNAL_ALLOC freed <1K, 50% free 2691K/5379K, external 18078K/20126K, paused
27ms

04-07 22:06:06.204: E/(7937): 测试第5张图片

04-07 22:06:06.315: D/dalvikvm(7937): GC_EXTERNAL_ALLOC freed <1K, 50% free 2691K/5379K, external 24104K/26152K, paused
26ms

04-07 22:06:06.395: E/(7937): 测试第6张图片

04-07 22:06:06.495: D/dalvikvm(7937): GC_EXTERNAL_ALLOC freed <1K, 50% free 2691K/5379K, external 30130K/32178K, paused
22ms

04-07 22:06:06.565: E/(7937): 测试第7张图片

04-07 22:06:06.665: D/dalvikvm(7937): GC_EXTERNAL_ALLOC freed <1K, 50% free 2691K/5379K, external 36156K/38204K, paused
22ms

04-07 22:06:06.745: E/(7937): 测试第8张图片

04-07 22:06:06.845: D/dalvikvm(7937): GC_EXTERNAL_ALLOC freed 2K, 51% free 2689K/5379K, external 42182K/44230K, paused 23ms

04-07 22:06:06.845: E/dalvikvm-heap(7937): 6170724-byte external allocation too large for this process.

04-07 22:06:06.885: I/dalvikvm-heap(7937): Clamp target GC heap from 48.239MB to 48.000MB

04-07 22:06:06.885: E/GraphicsJNI(7937): VM won't let us allocate 6170724 bytes

04-07 22:06:06.885: D/dalvikvm(7937): GC_FOR_MALLOC freed <1K, 51% free 2689K/5379K, external 42182K/44230K, paused 25ms

04-07 22:06:06.885: D/AndroidRuntime(7937): Shutting down VM

04-07 22:06:06.885: W/dalvikvm(7937): threadid=1: thread exiting with uncaught exception (group=0x40015560)

04-07 22:06:06.885: E/AndroidRuntime(7937): FATAL EXCEPTION: main

04-07 22:06:06.885: E/AndroidRuntime(7937): java.lang.OutOfMemoryError: bitmap size exceeds VM budget

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.Bitmap.nativeCreate(Native
Method)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.Bitmap.createBitmap(Bitmap.java:477)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.Bitmap.createBitmap(Bitmap.java:444)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:359)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:385)

04-07 22:06:06.885: E/AndroidRuntime(7937): at bassy.test.drawable.Main.onCreate(Main.java:37)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1722)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1784)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.app.ActivityThread.access$1500(ActivityThread.java:123)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:939)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.os.Handler.dispatchMessage(Handler.java:99)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.os.Looper.loop(Looper.java:130)

04-07 22:06:06.885: E/AndroidRuntime(7937): at android.app.ActivityThread.main(ActivityThread.java:3835)

04-07 22:06:06.885: E/AndroidRuntime(7937): at java.lang.reflect.Method.invokeNative(Native
Method)

04-07 22:06:06.885: E/AndroidRuntime(7937): at java.lang.reflect.Method.invoke(Method.java:507)

04-07 22:06:06.885: E/AndroidRuntime(7937): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847)

04-07 22:06:06.885: E/AndroidRuntime(7937): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605)

04-07 22:06:06.885: E/AndroidRuntime(7937): at dalvik.system.NativeStart.main(Native
Method)

看看上面的输出,才加载到第8张图片,程序就报错了“java.lang.OutOfMemoryError:
bitmap size exceeds VM budget
”。

通过上面的例子,可以看清楚地看出来,使用Drawable保存图片对象,占用更小的内存空间。

而使用Biamtp对象,则会占用很大内存空间,很容易就出现OOM了!

下面我们再来看一个例子,这个也是加载Bitmap对象。

只不过,之次不是使用BitmapFactory的decodeResource方法,

而是使用decodeStream方法,看代码。

public class Main extends Activity

{

int number =
1000;

Bitmap bitmap[];

@Override

public void onCreate(Bundle
savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

bitmap = new Bitmap[number];

for (int i
= 0; i < number; i++)

{

Log.e("", "测试第" +
(i+1) + "张图片");

bitmap[i]
=BitmapFactory.decodeStream(getResources().openRawResource(R.drawable.img));//这里换了方法

}

}

}

输出结果:

04-07 22:16:12.676: E/(8091): 测试第561张图片

04-07 22:16:12.756: E/(8091): 测试第562张图片

04-07 22:16:12.826: E/(8091): 测试第563张图片

04-07 22:16:12.906: E/(8091): 测试第564张图片

04-07 22:16:12.906: D/skia(8091): ---------- mmap failed for imageref_ashmem size=2744320 err=12

04-07 22:16:12.906: E/(8091): 测试第565张图片

04-07 22:16:12.906: D/skia(8091): ---------- mmap failed for imageref_ashmem size=2744320 err=12

04-07 22:16:12.906: E/(8091): 测试第566张图片

04-07 22:16:12.916: E/filemap(8091): mmap(0,416798) failed: Out of memory

04-07 22:16:12.916: D/filemap(8091): munmap(0x0, 0) failed

04-07 22:16:12.916: W/asset(8091): create map from entry failed

04-07 22:16:12.916: D/AndroidRuntime(8091): Shutting down VM

04-07 22:16:12.916: W/dalvikvm(8091): threadid=1: thread exiting with uncaught exception (group=0x40015560)

04-07 22:16:12.936: E/AndroidRuntime(8091): FATAL EXCEPTION: main

04-07 22:16:12.936: E/AndroidRuntime(8091): java.lang.RuntimeException: Unable to start activity ComponentInfo{bassy.test.drawable/bassy.test.drawable.Main}:
android.content.res.Resources$NotFoundException: File res/drawable-mdpi/img.png from drawable resource ID #0x7f020001

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1768)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1784)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.app.ActivityThread.access$1500(ActivityThread.java:123)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:939)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.os.Handler.dispatchMessage(Handler.java:99)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.os.Looper.loop(Looper.java:130)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.app.ActivityThread.main(ActivityThread.java:3835)

04-07 22:16:12.936: E/AndroidRuntime(8091): at java.lang.reflect.Method.invokeNative(Native
Method)

04-07 22:16:12.936: E/AndroidRuntime(8091): at java.lang.reflect.Method.invoke(Method.java:507)

04-07 22:16:12.936: E/AndroidRuntime(8091): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847)

04-07 22:16:12.936: E/AndroidRuntime(8091): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605)

04-07 22:16:12.936: E/AndroidRuntime(8091): at dalvik.system.NativeStart.main(Native
Method)

04-07 22:16:12.936: E/AndroidRuntime(8091): Caused by: android.content.res.Resources$NotFoundException: File res/drawable-mdpi/img.png
from drawable resource ID #0x7f020001

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.content.res.Resources.openRawResource(Resources.java:860)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.content.res.Resources.openRawResource(Resources.java:836)

04-07 22:16:12.936: E/AndroidRuntime(8091): at bassy.test.drawable.Main.onCreate(Main.java:43)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1722)

04-07 22:16:12.936: E/AndroidRuntime(8091): ... 11 more

04-07 22:16:12.936: E/AndroidRuntime(8091): Caused by: java.io.FileNotFoundException: res/drawable-mdpi/img.png

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.content.res.AssetManager.openNonAssetNative(Native
Method)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.content.res.AssetManager.openNonAsset(AssetManager.java:429)

04-07 22:16:12.936: E/AndroidRuntime(8091): at android.content.res.Resources.openRawResource(Resources.java:857)

04-07 22:16:12.936: E/AndroidRuntime(8091): ... 15 more

从上面可以看出,程序在加载到第566张的时候,就出现了OOM错误。

不过,跟第2个例子比起来,你会发现,程序可以加载更多的图片。

这说明了使用BitmapFactory的decodeResource方法会占据大量内存,

而使用使用decodeStream方法,则占据更小的内存。

从时间上来说,看看日志输出,大概估算了一下加载一张图片所需要的时间,发现,

decodeResource加载图片需要约0.17秒的时间,

而使用decodeStream方法,只需要约0.08秒的时间!

这说明了,decodeStream无论是时间上还是空间上,都比decodeResource方法更优秀!!

从上面三个例子,可以看出,用第一种方法(即用Drawable加载图片)可以加载更加的图片,加载32张图片的时间约为0.01秒!

我试着把Drawable的数量调至1000000,程序在运行时,停在了153761张图片里,手机提示,“应用程序无响应…”

个人猜测,Drawable应该不属于常驻内存的对象,不然的话,不可能不会出现OOM的~~

网上关于Drawable与Bitmap的资料太少,不能深入学习,真是遗憾~

刚才又做了个测试,把第一个例子中的

array[i] = getResources().getDrawable(R.drawable.img);

方法换成了

array[i] = Drawable.createFromStream(getResources().openRawResource(R.drawable.img),
null);


结果和第三个例子一样,在第566张图片中,出现了OOM错误!


Android Bitmap 内存限制

在编写 Android 程序的时候,我们总是难免会碰到 OOM 的错误,那么这个错误究竟是怎么来的呢?我们先来看一下这段异常信息:

08-14 05:15:04.764: ERROR/dalvikvm-heap(264): 3528000-byte external allocation too large for this process.

08-14 05:15:04.764: ERROR/(264): VM won’t let us allocate 3528000 bytes

08-14 05:15:04.764: DEBUG/skia(264): — decoder->decode returned false

08-14 05:15:04.774: DEBUG/AndroidRuntime(264): Shutting down VM

08-14 05:15:04.774: WARN/dalvikvm(264): threadid=3: thread exiting with uncaught exception (group=0x4001b188)

08-14 05:15:04.774: ERROR/AndroidRuntime(264): Uncaught handler: thread main exiting due to uncaught exception

08-14 05:15:04.794: ERROR/AndroidRuntime(264): java.lang.OutOfMemoryError: bitmap size exceeds VM budget

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:447)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:346)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:372)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at com.xixun.test.HelloListView.onCreate(HelloListView.java:33)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2459)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2512)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread.access$2200(ActivityThread.java:119)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1863)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.os.Handler.dispatchMessage(Handler.java:99)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.os.Looper.loop(Looper.java:123)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread.main(ActivityThread.java:4363)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at java.lang.reflect.Method.invokeNative(Native Method)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at java.lang.reflect.Method.invoke(Method.java:521)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)

08-14 05:15:04.794: ERROR/AndroidRuntime(264): at dalvik.system.NativeStart.main(Native Method)

从上面这段异常信息中,我们看到了一个 OOM(OutOfMemory) 错误,我称其为 (OMG 错误)。出现这个错误的原因是什么呢?为什么解码图像会出现这样的问题呢?关于这个问题,我纠结了一段时间,在网上查询了很多资料,甚至查看了 Android Issues,确实看到了相关的问题例如 Issue
3405,Issue 8488,尤其 Issue 8488 下面一楼的回复,让我觉得很雷人啊:

Comment 1 by romain…@android.com,
May 23, 2010

Yourappneedstouselessmemory.

当然我们承认不好的程序总是程序员自己错误的写法导致的 ,不过我们倒是非常想知道如何来规避这个问题,那么接下来就是解答这个问题的关键。

我们从上面的异常堆栈信息中,可以看出是在 BitmapFactory.nativeDecodeAsset(),对应该方法的 native 方法是在 BitmapFactory.cpp 中的 doDecode() 方法,在该方法中申请 JavaPixelAllocator 对象时,会调用到 Graphics.cpp 中的 setJavaPixelRef() 方法,在 setJavaPixelRef() 中会对解码需要申请的内存空间进行一个判断,代码如下:

bool r = env->CallBooleanMethod(gVMRuntime_singleton,

gVMRuntime_trackExternalAllocationMethodID,

jsize);

而 JNI 方法 ID — gVMRuntime_trackExternalAllocationMethodID 对应的方法实际上是 dalvik_system_VMRuntime.c 中的 Dalvik_dalvik_system_VMRuntime_trackExternalAllocation(),而在该方法中又会调用大 HeapSource.c 中的 dvmTrackExternalAllocation() 方法,继而调用到 externalAllocPossible() 方法,在该方法中这句代码是最关键的

heap = hs2heap(hs);

currentHeapSize = mspace_max_allowed_footprint(heap->msp);

if (currentHeapSize + hs->externalBytesAllocated + n <=

heap->absoluteMaxSize)

{

return true;

}

这段代码的意思应该就是当前堆已使用的大小 (由 currentHeapSize 和 hs->externalBytesAllocated 构成) 加上我们需要再次分配的内存大小不能超过堆的最大内存值。那么一个堆的最大内存值究竟是多大呢。通过下面这张图,我们也许可以看到一些线索 (自己画的,比较粗糙)



最终的决定权其实是在 Init.c 中,因为 Android 在启动系统的时候会去优先执行这个里面的函数,通过调用 dvmStartup() 方法来初始化虚拟机,最终调用到会调用到 HeapSource.c 中的 dvmHeapSourceStartup() 方法,而在 Init.c 中有这么两句代码:

gDvm.heapSizeStart = 2 * 1024 * 1024; // Spec says 16MB; too big for us.

gDvm.heapSizeMax = 16 * 1024 * 1024; // Spec says 75% physical mem

在另外一个地方也有类似的代码,那就是 AndroidRuntime.cpp 中的 startVM() 方法中:

strcpy(heapsizeOptsBuf, "-Xmx");

property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");

//LOGI("Heap size: %s", heapsizeOptsBuf);

opt.optionString = heapsizeOptsBuf;

同样也是默认值为 16M,虽然目前我看到了两个可以启动 VM 的方法,具体 Android 何时会调用这两个初始化 VM 的方法,还不是很清楚。不过可以肯定的一点就是,如果启动 DVM 时未指定参数,那么其初始化堆最大大小应该就是 16M,那么我们在网上查到了诸多关于解码图像超过 8M 就会出错的论断是如何得出来的呢?

我们来看看 HeapSource.c 中的这个方法的注释

/*

* External allocation tracking

*

* In some situations, memory outside of the heap is tied to the

* lifetime of objects in the heap. Since that memory is kept alive

* by heap objects, it should provide memory pressure that can influence

* GCs.

*/

static bool

externalAllocPossible(const HeapSource *hs, size_t n)

{

const Heap *heap;

size_t currentHeapSize;

/* Make sure that this allocation is even possible.

* Don’t let the external size plus the actual heap size

* go over the absolute max. This essentially treats

* external allocations as part of the active heap.

*

* Note that this will fail "mysteriously" if there’s

* a small softLimit but a large heap footprint.

*/

heap = hs2heap(hs);

currentHeapSize = mspace_max_allowed_footprint(heap->msp);

if (currentHeapSize + hs->externalBytesAllocated + n <=

heap->absoluteMaxSize)

{

return true;

}

HSTRACE("externalAllocPossible(): "

"footprint %zu + extAlloc %zu + n %zu >= max %zu (space for %zu)\n",

currentHeapSize, hs->externalBytesAllocated, n,

heap->absoluteMaxSize,

heap->absoluteMaxSize –

(currentHeapSize + hs->externalBytesAllocated));

return false;

}

标为红色的注释的意思应该是说,为了确保我们外部分配内存成功,我们应该保证当前已分配的内存加上当前需要分配的内存值,大小不能超过当前堆的最大内存值,而且内存管理上将外部内存完全当成了当前堆的一部分。也许我们可以这样理解,Bitmap 对象通过栈上的引用来指向堆上的 Bitmap 对象,而 Bitmap 对象又对应了一个使用了外部存储的 native 图像,实际上使用的是 byte[] 来存储的内存空间,如下图:





我想到现在大家应该已经对于 Bitmap 内存大小限制有了一个比较清楚的认识了。至于前几天从Android123 上看到 “Android
的 Btimap 处理大图片解决方法” 一文中提到的使用 BitmapFactory.Options 来设置 inTempStorage 大小,我当时看完之后就尝试了一下,这个设置并不能解决问题,而且很有可能会给你带来不必要的问题。从 BitmapFactory.cpp 中的代码来看,如果 option 不为 null 的话,那么会优先处理 option 中设置的各个参数,假设当前你设置 option 的 inTempStorage 为 1024*1024*4(4M) 大小的话,而且每次解码图像时均使用该
option 对象作为参数,那么你的程序极有可能会提前失败,在我的测试中,我使用了一张大小为 1.03M 的图片来进行解码,如果不使用 option 参数来解码,可以正常解码四次,也就是分配了四次内存,而如果我使用 option 的话,就会出现 OOM 错误,只能正常解码两次不出现 OOM 错误。那么这又是为什么呢?我想是因为这样的,Options 类似与一个预处理参数,当你传入 options 时,并且指定临时使用内存大小的话,Android 将默认先申请你所指定的内存大小,如果申请失败,就抛出 OOM
错误。而如果不指定内存大小,系统将会自动计算,如果当前还剩 3M 空间大小,而我解码只需要 2M 大小,那么在缺省情况下将能解码成功,而在设置 inTempStorage 大小为 4M 的情况下就将出现 OOM 错误。所以,我个人认为通过设置 Options 的 inTempStorage 大小根本不能作为解决大图像解码的方法,而且可能带来不必要的问题,因为 OOM 错误在某些情况是必然出现的,也就是上面我解释的那么多关于堆内存最大值的问题,只要解码需要的内存超过系统可分配的最大内存值,那么 OOM 错误必然会出现。当然对于 Android
开发网 为何发布了这么一篇文章,个人觉得很奇怪,我想作为一个技术人员发布一篇文章,至少应该自己尝试着去测试一下自己的程序吧,如果只是翻翻 SDK 文档,然后就出来一两篇文章声称是解决某问题的方案,恐怕并不是一种负责任的行为吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: