持續更新,嘿嘿~ Android內存泄漏解析
內存泄漏也稱作“存儲滲漏”,用動態存儲分配函數動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該內存單元。直到程序結束。(其實說白了就是該內存空間使用完畢之後未回收)即所謂內存泄漏。
內存泄漏形象的比喻是“操作系統可提供給所有進程的存儲空間正在被某個進程榨乾”,最終結果是程序運行時間越長,佔用存儲空間越來越多,最終用盡全部存儲空間,整個系統崩潰。所以“內存泄漏”是從操作系統的角度來看的。這裏的存儲空間並不是指物理內存,而是指虛擬內存大小,這個虛擬內存大小取決於磁盤交換區設定的大小。由程序申請的一塊內存,如果沒有任何一個指針指向它,那麼這塊內存就泄漏了。
——來自《百度百科》
導致OOM
糟糕的用戶體驗
雞肋的App存活率
內存泄露是一個持續的過程,隨着版本的迭代,效果越明顯
由於某些原因無法改善的泄露(如框架限制),則儘量降低泄露的內存大小
內存泄露實施後的版本,一定要驗證,不必馬上推行到正式版,可作爲beta版持續觀察是否影響/引發其他功能/問題
內存泄露實施後,項目的收穫:
OOM減少30%以上
平均使用內存從80M穩定到40M左右
用戶體驗上升,流暢度提升
存活率上升,推送到達率提升
IO
FileStream
Cursor
Bitmap
Context
單例
Callback
Service
BraodcastReceiver
ContentObserver
Handler
Thread
慎用Context
四大組件Context和Application的context使用參見下表
善用Reference
Java四種引用由高到低依次爲:強引用 > 軟引用 > 弱引用 > 虛引用
表格說明
複用ConvertView
對象釋放
遵循誰創建誰釋放的原則
示例:顯示調用clear列表、對象賦空值
原理
根本原因
關注堆內存
怎麼解決
詳見方案
實踐分析
詳見實踐
StrictMode
使用方法:AppContext的onCreate()方法加上
StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());StrictMode.setVmPolicy(newStrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
主要檢查項:內存泄露、耗時操作等
Leakcanary
Leakcanary + StrictMode + monkey(推薦)
使用階段:功能測試完成後,穩定性測試開始時
使用方法:安裝集成了Leakcanary的包,跑monkey
收穫階段:一段時間後,會發現出現N個泄露
實戰分析:逐條分析每個泄露並改善/修復
StrictMode:查看日誌搜索StrictMode關鍵字
Adb命令
手動觸發GC
通過adb shell dumpsys meminfo packagename -d查看
查看Activity以及View的數量
越接近0越好
對比進入Activity以及View前的數量和退出Activity以及View後的數量判斷
Android Monitor
MAT
Bitmap泄露
Bitmap泄露一般會泄露較多內存,視圖片大小、位圖而定
經典場景:App啓動圖
解決內存泄露前後內存相差10M+,可謂驚人
解決方案:
App啓動圖Activity的onDestroy()中及時回收內存
@OverrideprotectedvoidonDestroy(){// TODO Auto-generated method stubsuper.onDestroy();recycleImageView(imgv_load_ad);}publicstaticvoidrecycleImageView(Viewview){if(view==null)return;if(viewinstanceofImageView){Drawabledrawable=((ImageView)view).getDrawable();if(drawableinstanceofBitmapDrawable){Bitmapbmp=((BitmapDrawable)drawable).getBitmap();if(bmp!=null&&!bmp.isRecycled()){((ImageView)view).setImageBitmap(null);bmp.recycle();bmp=null;}}}}
IO流未關閉
分析:通過日誌可知FileOutputStream()未關閉
問題代碼:
publicstaticvoidcopyFile(Filesource,Filedest){FileChannelinChannel=null;FileChanneloutChannel=null;Log.i(TAG,"source path: "+source.getAbsolutePath());Log.i(TAG,"dest path: "+dest.getAbsolutePath());try{inChannel=newFileInputStream(source).getChannel();outChannel=newFileOutputStream(dest).getChannel();inChannel.transferTo(0,inChannel.size(),outChannel);}catch(IOExceptione){e.printStackTrace();}}
解決方案:
及時關閉IO流,避免泄露
publicstaticvoidcopyFile(Filesource,Filedest){FileChannelinChannel=null;FileChanneloutChannel=null;Log.i(TAG,"source path: "+source.getAbsolutePath());Log.i(TAG,"dest path: "+dest.getAbsolutePath());try{inChannel=newFileInputStream(source).getChannel();outChannel=newFileOutputStream(dest).getChannel();inChannel.transferTo(0,inChannel.size(),outChannel);}catch(IOExceptione){e.printStackTrace();}finally{if(inChannel!=null){try{inChannel.close();}catch(IOExceptione){e.printStackTrace();}}if(outChannel!=null){try{outChannel.close();}catch(IOExceptione){e.printStackTrace();}}}}
E/StrictMode: A resource was acquired at attached stack trace but never released.
See java.io.Closeable for information on avoiding resource leaks.
java.lang.Throwable: Explicit termination method 'close' not called
at dalvik.system.CloseGuard.open(CloseGuard.java:180)
at java.io.FileOutputStream.(FileOutputStream.java:89)
at java.io.FileOutputStream.(FileOutputStream.java:72)
at com.heyniu.lock.utils.FileUtil.copyFile(FileUtil.java:44)
at com.heyniu.lock.db.BackupData.backupData(BackupData.java:89)
at com.heyniu.lock.ui.HomeActivity$11.onClick(HomeActivity.java:675)
at android.support.v7.app.AlertController$ButtonHandler.handleMessage(AlertController.java:157)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
單例模式泄露
分析:通過截圖我們發現SplashActivity被ActivityUtil的實例activityStack持有
引用代碼:
ActivityUtil.getAppManager().add(this);
持有代碼:
publicvoidadd(Activityactivity){if(activityStack==null){synchronized(ActivityUtil.class){if(activityStack==null){activityStack=newStack<>();}}}activityStack.add(activity);}
解決方案:
在SplashActivity的onDestroy()生命週期移除引用
@OverrideprotectedvoidonDestroy(){super.onDestroy();ActivityUtil.getAppManager().remove(this);}
靜態變量持有Context實例泄露
分析:長生命週期持有短什麼週期引用導致泄露,詳見上文四大組件Context和Application的context使用
示例引用代碼:
privatestaticHttpRequestreq;publicstaticvoidHttpUtilPost(Contextcontext,intTaskId,Stringurl,StringrequestBody,ArrayListHeaders,RequestListenerlistener){// TODO Auto-generated constructor stubreq=newHttpRequest(context,url,TaskId,requestBody,Headers,listener);req.post();}
解決方案:
改爲弱引用
pass:弱引用隨時可能爲空,使用前先判空
示例代碼:
publicstaticvoidcancel(intTaskId){if(req!=null&&req.get()!=null){req.get().AsyncCancel(TaskId);}}
privatestaticWeakReferencereq;publicstaticvoidHttpUtilPost(Contextcontext,intTaskId,Stringurl,StringrequestBody,ArrayListHeaders,RequestListenerlistener){// TODO Auto-generated constructor stubreq=newWeakReference(newHttpRequest(context,url,TaskId,requestBody,Headers,listener));req.get().post();}
改爲長生命週期
privatestaticHttpRequestreq;publicstaticvoidHttpUtilPost(Contextcontext,intTaskId,Stringurl,StringrequestBody,ArrayListHeaders,RequestListenerlistener){// TODO Auto-generated constructor stubreq=newHttpRequest(context.getApplicationContext(),url,TaskId,requestBody,Headers,listener);req.post();}
Context泄露
Callback泄露
服務未解綁註冊泄露
分析:一般發生在註冊了某服務,不用時未解綁服務導致泄露
引用代碼:
privatevoidinitSensor(){// 獲取傳感器管理器sm=(SensorManager)container.activity.getSystemService(Context.SENSOR_SERVICE);// 獲取距離傳感器acceleromererSensor=sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);// 設置傳感器監聽器acceleromererListener=newSensorEventListener(){......};sm.registerListener(acceleromererListener,acceleromererSensor,SensorManager.SENSOR_DELAY_NORMAL);}
解決方案:
在Activity的onDestroy()方法解綁服務
@Override
protected void onDestroy(){
super.onDestroy();
sm.unregisterListener(acceleromererListener,acceleromererSensor);}
Handler泄露
分析:由於Activity已經關閉,Handler任務還未執行完成,其引用了Activity的實例導致內存泄露
引用代碼:
handler.sendEmptyMessage(0);
解決方案:
在Activity的onDestroy()方法回收Handler
@OverrideprotectedvoidonDestroy(){super.onDestroy();handler.removeCallbacksAndMessages(null);}
圖片後續遇到再補上
異步線程泄露
分析:一般發生在線程執行耗時操作時,如下載,此時Activity關閉後,由於其被異步線程引用,導致無法被正常回收,從而內存泄露
引用代碼:
newThread(){publicvoidrun(){imageArray=loadImageFromUrl(imageUrl);}.start();
解決方案:
把線程作爲對象提取出來
在Activity的onDestroy()方法阻塞線程
thread=newThread(){publicvoidrun(){imageArray=loadImageFromUrl(imageUrl);};thread.start();@OverrideprotectedvoidonDestroy(){super.onDestroy();if(thread!=null){thread.interrupt();thread=null;}}