關於android 使用bitmap的OOM心得和解決方案
android開發,從2010年開始學習到現在的獨立完成一個app,這漫長的四年,已經經歷了很多次bug的折磨,無數次的加班訓練。然而,自以為自己已經比較瞭解android了,卻最近在一個專案上,因為oom而折騰了一個周,回到原地,認識了自己的不足,感覺自己是如此的菜鳥呀。
好了,不廢話,大家在使用開發android的時候,很少會注意或者意識到釋放記憶體的重要性,因為大家在使用過程中,涉及的圖片資源不多,或者比較穩定,來回切換介面,圖片也就那麼幾張或者使用的都是很小的圖片,根本不會感覺到圖片佔用記憶體可能引發的潛在危機。
如果你的程式中,使用了一下功能,你作為一個合格的android開發工程師,你有必要,注意oom的潛在危機
1.介面比較多,並且很多介面的背景圖片不一樣;
2.涉及到換膚功能,定義多種皮膚,皮膚的資源不是使用color 而是圖片資源;
以上兩種情況,如果你不注意合理的釋放記憶體,你將會為自己程式莫名其妙的崩潰付出代價的。
我們在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-07 06:55:39.330: D/dalvikvm(6988): GC_FOR_ALLOC freed 4091K, 31% free 9715K/13936K, paused 44ms, total 45ms
05-07 06:55:39.340: I/dalvikvm-heap(6988): Grow heap (frag case) to 13.126MB for 3686416-byte allocation
05-07 06:55:39.421: D/dalvikvm(6988): GC_CONCURRENT freed <1K, 5% free 13314K/13936K, paused 6ms+5ms, total 83ms
05-07 06:55:39.600: D/dalvikvm(6988): GC_FOR_ALLOC freed <1K, 5% free 13314K/13936K, paused 44ms, total 44ms
05-07 06:55:39.670: I/dalvikvm-heap(6988): Grow heap (frag case) to 19.377MB for 6554896-byte allocation
05-07 06:55:39.790: D/dalvikvm(6988): GC_CONCURRENT freed 0K, 4% free 19715K/20340K, paused 7ms+20ms, total 114ms
05-07 06:55:40.011: I/System.out(6988): onCreate
05-07 06:55:41.760: D/dalvikvm(6988): GC_EXPLICIT freed 10759K, 56% free 9063K/20340K, paused 4ms+7ms, total 111ms
05-07 06:55:41.821: D/dalvikvm(6988): GC_EXPLICIT freed <1K, 56% free 9062K/20340K, paused 4ms+7ms, total 62ms
05-07 06:55:41.821: I/System.out(6988): onDestroy
以上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, paused 4ms+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 a recycled bitmap android.graphics.Bitmap@417fc280
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.graphics.Canvas.throwIfRecycled(Canvas.java:1026)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.graphics.Canvas.drawBitmap(Canvas.java:1127)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:393)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.View.draw(View.java:13697)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.View.draw(View.java:13596)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewGroup.drawChild(ViewGroup.java:2928)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2797)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.View.draw(View.java:13594)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewGroup.drawChild(ViewGroup.java:2928)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2797)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.View.draw(View.java:13594)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewGroup.drawChild(ViewGroup.java:2928)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2797)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.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): at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2211)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:2281)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewRootImpl.draw(ViewRootImpl.java:2177)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2045)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1854)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:989)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4351)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.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): at android.view.Choreographer.doFrame(Choreographer.java:532)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.os.Handler.handleCallback(Handler.java:725)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.os.Handler.dispatchMessage(Handler.java:92)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.os.Looper.loop(Looper.java:137)
05-07 07:00:37.991: E/AndroidRuntime(6988): at android.app.ActivityThread.main(ActivityThread.java:5041)
05-07 07:00:37.991: E/AndroidRuntime(6988): at java.lang.reflect.Method.invokeNative(Native Method)
05-07 07:00:37.991: E/AndroidRuntime(6988): at java.lang.reflect.Method.invoke(Method.java:511)
05-07 07:00:37.991: E/AndroidRuntime(6988): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
05-07 07:00:37.991: E/AndroidRuntime(6988): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
05-07 07:00:37.991: E/AndroidRuntime(6988): at dalvik.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 a recycled 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-byte allocation
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 <1K, 52% free 13308K/27452K, paused 4ms+29ms, total 105ms
05-07 07:29:19.240: D/dalvikvm(7598): GC_FOR_ALLOC freed <1K, 52% free 13307K/27452K, paused 40ms, total 40ms
05-07 07:29:19.310: I/dalvikvm-heap(7598): Grow heap (frag case) to 19.373MB for 6554896-byte allocation
05-07 07:29:19.420: D/dalvikvm(7598): GC_CONCURRENT freed 0K, 29% free 19709K/27452K, paused 4ms+31ms, total 106ms
05-07 07:29:19.590: I/System.out(7598): onCreate
05-07 07:29:21.290: D/dalvikvm(7598): GC_EXPLICIT freed 10749K, 56% free 9064K/20340K, paused 3ms+8ms, total 125ms
05-07 07:29:21.290: I/System.out(7598): onDestroy
以上的介面和我們呼叫了手動釋放的方式是不是一樣? 呵呵,反覆測試多次,看看還會不會導致java.lang.RuntimeException: Canvas: trying to use a recycled bitmap
實際上,我們的程式不會再出現上面的問題了,並且解決了oom得潛在危機。如此的簡單,如此的便捷。
同理:如果我們在程式中,使用了ImageView的話, 我們也可以使用
imageView.setImageResource(0); 這種方式,來釋放我們設定的android:src或者bitmap等等。
那麼,關於圖片的oom可能潛在的問題,我們告一段落,從這次的經歷,讓我更加的深入的瞭解java語言的記憶體回收機制,總結一句話:儘可能的讓系統去釋放記憶體,我們只負責標示需要釋放的記憶體。通俗點:我們只是標記需要殺的人,誰來殺,就讓殺手看著辦。
作者:SpringSky出處:http://blog.csdn.net/springsky_/article/details/25212419
blog:http://blog.csdn.net/springsky_
相關文章
- 有關 Android Studio 重複引入包的問題和解決方案Android
- 關於$ is not defined的原因和解決辦法
- android 關於Bitmap壓縮處理解析Android
- Java中關於OOM的場景及解決方法JavaOOM
- android bitmap壓縮方案Android
- Android Bitmap 使用Android
- Android 載入大圖片時報OOM的解決方案(原始碼)AndroidOOM原始碼
- Java中關於OOM的場景及解決方法(轉)JavaOOM
- Android之批量載入圖片OOM問題解決方案AndroidOOM
- Debian 11 關閉 swap 遇到的問題和解決方案
- Bitmap 比你想的更費記憶體 | 吊打 OOM記憶體OOM
- Composer 使用過程中遇到的問題和解決方案
- 關於jQuery UI 使用心得及技巧jQueryUI
- 微軟推出Windows工具:提供了相關解讀和解決方案給使用者微軟Windows
- php7 使用 phpunit 部分錯誤和解決方案PHP
- 關於ORA-01779問題的分析和解決
- Android高效載入大圖、多圖解決方案,有效避免程式OOMAndroid圖解OOM
- 關於無法用 https 登入 SAP ABAP Netweaver 系統的問題和解決方案HTTP
- jmeter.bat配置(主要關於OOM)JMeterBATOOM
- Android IntentService 的使用和解析AndroidIntent
- svn的操作,報錯,和解決方案 一。
- time_wait的成因和解決方案AI
- 關於SCRUM的學習心得Scrum
- Android關於Typedarray的使用Android
- SpringMVC thymeleaf 關於layout使用的一些心得SpringMVC
- 關於rand和srand函式使用的一點心得函式
- 介面自動化的關鍵思路和解決方案,本文全講清楚了
- 報錯No bean named ' is defined的原因和解決方案Bean
- 分散式事務的概念和解決方案Seate分散式
- 關於ListView控制元件使用時的一些心得View控制元件
- 關於“日誌”的一點心得
- SOA 治理框架和解決方案架構框架架構
- Android Web3j OOM解決AndroidWebOOM
- Android對Bitmap的記憶體優化方案總結Android記憶體優化
- Android中的BitmapAndroid
- Android中使用Handler造成記憶體洩露的分析和解決Android記憶體洩露
- vs2010沒有 最近使用的專案和解決方案
- 分享關於JavaScript中執行上下文(this)的使用心得JavaScript