2018-03-07

持續更新,嘿嘿~   Android內存泄漏解析

​內存泄漏也稱作“存儲滲漏”,用動態存儲分配函數動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該內存單元。直到程序結束。(其實說白了就是該內存空間使用完畢之後未回收)即所謂內存泄漏。

內存泄漏形象的比喻是“操作系統可提供給所有進程的存儲空間正在被某個進程榨乾”,最終結果是程序運行時間越長,佔用存儲空間越來越多,最終用盡全部存儲空間,整個系統崩潰。所以“內存泄漏”是從操作系統的角度來看的。這裏的存儲空間並不是指物理內存,而是指虛擬內存大小,這個虛擬內存大小取決於磁盤交換區設定的大小。由程序申請的一塊內存,如果沒有任何一個指針指向它,那麼這塊內存就泄漏了。

​ ——來自《百度百科》

影響

導致OOM

糟糕的用戶體驗

雞肋的App存活率

成效

內存泄露是一個持續的過程,隨着版本的迭代,效果越明顯

由於某些原因無法改善的泄露(如框架限制),則儘量降低泄露的內存大小

內存泄露實施後的版本,一定要驗證,不必馬上推行到正式版,可作爲beta版持續觀察是否影響/引發其他功能/問題

內存泄露實施後,項目的收穫:

OOM減少30%以上

平均使用內存從80M穩定到40M左右

用戶體驗上升,流暢度提升

存活率上升,推送到達率提升

類型

IO

FileStream

Cursor

Bitmap

Context

單例

Callback

Service

BraodcastReceiver

ContentObserver

Handler

Thread

技巧

慎用Context

Context概念

四大組件Context和Application的context使用參見下表

善用Reference

Java引用介紹

Java四種引用由高到低依次爲:強引用  >  軟引用  >  弱引用  >  虛引用

表格說明

複用ConvertView

複用詳解

對象釋放

遵循誰創建誰釋放的原則

示例:顯示調用clear列表、對象賦空值

分析

​原理

Java內存分配機制

Java垃圾回收機制

​根本原因

關注堆內存

​怎麼解決

詳見方案

​實踐分析

詳見實踐

方案

StrictMode

使用方法:AppContext的onCreate()方法加上

StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());StrictMode.setVmPolicy(newStrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());

主要檢查項:內存泄露、耗時操作等

Leakcanary

GitHub地址

使用方法

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;}}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章