Bug 1 :View用图片设置背景报OOM

java.lang.RuntimeException: Unable to start activity Co... 
com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInf...

 

        53Caused by: java.lang.OutOfMemoryError

        54    atandroid.graphics.BitmapFactory.nativeDecodeAsset(Native Method)

        55    atandroid.graphics.BitmapFactory.decodeStream(BitmapFactory.java:597)

        56    atandroid.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:432)

        57    atandroid.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:840)

        58    atandroid.content.res.Resources.createFromResourceStream(Resources.java:2477)

        59    atandroid.content.res.Resources.loadDrawable(Resources.java:2136)

 

我们在android程序中,无论是使用layout布局设置了背景还是使用了setBackgroundResource 设置背景,你都以为不需要释放内存?这绝对是一个错误的观念,绝对的错误。也许这一点的错误的认识,将会在大屏幕手机上,暴露出来你的oom的现象,特别是在三星的大屏幕手机,爆oom的几率更大。

也许,大家在使用开发中,会注意这一点,使用了一下方式去解决释放内存(网上很多例子都是使用该方式去释放内存),比如:

                 View view =findViewById(R.id.page_bg);

                 BitmapDrawable bitmapDrawable =(BitmapDrawable) view.getBackground();

                 view.setBackgroundResource(0);

                 bitmapDrawable.setCallback(null);

                 Bitmap bitmap =bitmapDrawable.getBitmap();

                 if(bitmap != null &&!bitmap.isRecycled()){

                          bitmap.recycle();

                          bitmap = null;

                 }

                 System.gc();



从我们的测试效果来看看logcat 打印出来的结果

05-0706:55:39.330: D/dalvikvm(6988): GC_FOR_ALLOC freed 4091K, 31% free9715K/13936K, paused 44ms, total 45ms

05-0706:55:39.340: I/dalvikvm-heap(6988): Grow heap (frag case) to 13.126MB for3686416-byte allocation

05-0706:55:39.421: D/dalvikvm(6988): GC_CONCURRENT freed

从以上logcat中,我们看到了GC_EXPLTCIT 为我们释放了10759k的内存,这个是很可观的,也许这一刻你会沾沾自喜,会认为你已经解决了程序中oom的潜在危机。

 

 

 

然而,实际上,你却引发了另外一个潜在的问题,如果有A和B界面,同时使用了一个背景,你在A中释放了,在B中去使用,就会导致了一下error的logcat

 

05-07 07:00:37.250:D/dalvikvm(6988): GC_EXPLICIT freed 6411K, 81% free 2692K/13936K, paused4ms+6ms, total 105ms

05-07 07:00:37.310:D/dalvikvm(6988): GC_EXPLICIT freed 88K, 82% free 2604K/13936K, paused 3ms+8ms,total 59ms

05-07 07:00:37.310:I/System.out(6988): onDestroy

05-07 07:00:37.891:D/AndroidRuntime(6988): Shutting down VM

05-07 07:00:37.891:W/dalvikvm(6988): threadid=1: thread exiting with uncaught exception(group=0x40a71930)

05-07 07:00:37.991:E/AndroidRuntime(6988): FATAL EXCEPTION: main

05-07 07:00:37.991:E/AndroidRuntime(6988): java.lang.RuntimeException: Canvas: trying to use arecycled bitmap android.graphics.Bitmap@417fc280

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.graphics.Canvas.throwIfRecycled(Canvas.java:1026)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.graphics.Canvas.drawBitmap(Canvas.java:1127)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:393)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.View.draw(View.java:13697)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.View.draw(View.java:13596)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewGroup.drawChild(ViewGroup.java:2928)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewGroup.dispatchDraw(ViewGroup.java:2797)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.View.draw(View.java:13594)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewGroup.drawChild(ViewGroup.java:2928)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewGroup.dispatchDraw(ViewGroup.java:2797)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.View.draw(View.java:13594)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewGroup.drawChild(ViewGroup.java:2928)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewGroup.dispatchDraw(ViewGroup.java:2797)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.View.draw(View.java:13715)

05-07 07:00:37.991:E/AndroidRuntime(6988):   at android.widget.FrameLayout.draw(FrameLayout.java:467)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atcom.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2211)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:2281)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewRootImpl.draw(ViewRootImpl.java:2177)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewRootImpl.performDraw(ViewRootImpl.java:2045)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1854)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewRootImpl.doTraversal(ViewRootImpl.java:989)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4351)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.Choreographer$CallbackRecord.run(Choreographer.java:749)

05-07 07:00:37.991:E/AndroidRuntime(6988):   at android.view.Choreographer.doCallbacks(Choreographer.java:562)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.Choreographer.doFrame(Choreographer.java:532)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.os.Handler.handleCallback(Handler.java:725)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.os.Handler.dispatchMessage(Handler.java:92)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.os.Looper.loop(Looper.java:137)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atandroid.app.ActivityThread.main(ActivityThread.java:5041)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atjava.lang.reflect.Method.invokeNative(Native Method)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atjava.lang.reflect.Method.invoke(Method.java:511)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)

05-07 07:00:37.991:E/AndroidRuntime(6988):   atdalvik.system.NativeStart.main(Native Method)

 

 

 

 

感觉很奇怪把,因为你在这句logcat中,根本找不到错误的地方和为什么发生这样的错误,但是这个错误却是致命的。从英文的字面意思理解:你使用了一个已经释放的Bitmap来绘制你的界面。

遇到这样的情况,也许40%的android程序员都不知所错,因为我们根据无法把握出错的地方,也许你认为你是按照Activity的生命周期来做填充背景和释放背景,却出现这样的错误。

 

网上的资料解释,你能理解,却还是无法找到具体的问题所在。

 

我经历过这个问题的痛苦,看了网上的解释和解决办法,根本就找不到靠谱的。我对待这个问题,始终不放弃,经过了多次的测试和验证,该问题出现的地点是有一下两种可能性:

情况1、你A界面中使用了a.png图片,然后做了跳转,finish了A界面,finish的时候,你使用了一下的代码释放了内存:

 

               View view =findViewById(R.id.page_bg);

               BitmapDrawable bitmapDrawable =(BitmapDrawable) view.getBackground();

               view.setBackgroundResource(0);

               bitmapDrawable.setCallback(null);

               Bitmap bitmap =bitmapDrawable.getBitmap();

               if(bitmap != null &&!bitmap.isRecycled()){

                       bitmap.recycle();

                       bitmap = null;

               }

               System.gc();

 

 

然后再次进入A界面,极有可能出现该问题。


情况2、你在A界面使用了a.png图片,然后在B界面也使用了a.png, A界面没有finish,B界面使用了finsh,并且使用上面同样的方式释放了内存。然后我们正常情况下,A界面会经历onResume的方式来显示,可是这个时候,我们的a.png在内存中已经释放了,所以就会引发上面的

 

 

05-07 07:00:37.991:E/AndroidRuntime(6988): java.lang.RuntimeException: Canvas: trying to use arecycled bitmap android.graphics.Bitmap@417fc280

 

 

看着字面很简单,很容易理解,实际中找到问题的所在却不是容易的一件事。

 

 

 

也许,你在这一刻是否已经抓狂了,难道就没办法来释放内存来解决潜在的oom问题?

答案是否。像google这样的大公司,他们提供的api,考虑的潜在问题比我们正常程序员考虑的还要深入N倍。

 

个人从新阅读了相关的api,得出了一下解决办法来释放内存,解除我们潜在的oom情况

 

无论你是在xml中布局使用了:

 

android:background 

 

还是在java代码中调用了:

 

setBackground(background);

 

 

setBackgroundDrawable(background)

 

setBackgroundResource(resid)

 

 的方式去设置了背景图片.

 

使用的时候,请调用一下对应的方法:
setBackgroundResource和android:background →setBackgroundResource(0);

 

setBackgroundDrawable(background)→

setBackgroundDrawable(null)

 

setBackground(background)

 → setBackground(null)

 

 

 

 

然后再onDestory中调用System.gc();

以上的代码,我们修改为:

 

              

               View view =findViewById(R.id.page_bg);

               view.setBackgroundResource(0);

               System.gc();




我们在看看我们的logcat的结果:

 

 

05-07 07:29:18.941:D/dalvikvm(7598): GC_FOR_ALLOC freed 27K, 65% free 9791K/27452K, paused 43ms,total 58ms

05-07 07:29:18.970:I/dalvikvm-heap(7598): Grow heap (frag case) to 13.203MB for 3686416-byteallocation

05-07 07:29:19.041:D/dalvikvm(7598): GC_FOR_ALLOC freed 83K, 52% free 13308K/27452K, paused 72ms,total 72ms

05-07 07:29:19.140:D/dalvikvm(7598): GC_CONCURRENT freed 以上的界面和我们调用了手动释放的方式是不是一样?呵呵,反复测试多次,看看还会不会导致java.lang.RuntimeException: Canvas: tryingto use a recycled bitmap

 

实际上,我们的程序不会再出现上面的问题了,并且解决了oom得潜在危机。如此的简单,如此的便捷。

 

 

 

同理:如果我们在程序中,使用了ImageView的话,我们也可以使用

imageView.setImageResource(0);  这种方式,来释放我们设置的android:src或者bitmap等等。

 

 

 

那么,关于图片的oom可能潜在的问题,我们告一段落,从这次的经历,让我更加的深入的了解java语言的内存回收机制,总结一句话:尽可能的让系统去释放内存,我们只负责标示需要释放的内存。通俗点:我们只是标记需要杀的人,谁来杀,就让杀手看着办。



PS:以上只是临时的处理,项目中建议采用ImageLoader调URI的方式,来处理图片

你可能感兴趣的:(android,oom,图片处理)