日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Android內(nèi)存泄露實(shí)踐分析

 卡瑞吉 2019-04-19

專項(xiàng):Android內(nèi)存泄露實(shí)踐分析

定義

內(nèi)存泄漏也稱作“存儲(chǔ)滲漏”,用動(dòng)態(tài)存儲(chǔ)分配函數(shù)動(dòng)態(tài)開辟的空間,在使用完畢后未釋放,結(jié)果導(dǎo)致一直占據(jù)該內(nèi)存單元。直到程序結(jié)束。(其實(shí)說白了就是該內(nèi)存空間使用完畢之后未回收)即所謂內(nèi)存泄漏。

內(nèi)存泄漏形象的比喻是“操作系統(tǒng)可提供給所有進(jìn)程的存儲(chǔ)空間正在被某個(gè)進(jìn)程榨干”,最終結(jié)果是程序運(yùn)行時(shí)間越長(zhǎng),占用存儲(chǔ)空間越來越多,最終用盡全部存儲(chǔ)空間,整個(gè)系統(tǒng)崩潰。所以“內(nèi)存泄漏”是從操作系統(tǒng)的角度來看的。這里的存儲(chǔ)空間并不是指物理內(nèi)存,而是指虛擬內(nèi)存大小,這個(gè)虛擬內(nèi)存大小取決于磁盤交換區(qū)設(shè)定的大小。由程序申請(qǐng)的一塊內(nèi)存,如果沒有任何一個(gè)指針指向它,那么這塊內(nèi)存就泄漏了。

——來自《百度百科》

影響

  • 導(dǎo)致OOM

  • 糟糕的用戶體驗(yàn)

  • 雞肋的App存活率

成效

  • 內(nèi)存泄露是一個(gè)持續(xù)的過程,隨著版本的迭代,效果越明顯

  • 由于某些原因無法改善的泄露(如框架限制),則盡量降低泄露的內(nèi)存大小

  • 內(nèi)存泄露實(shí)施后的版本,一定要驗(yàn)證,不必馬上推行到正式版,可作為beta版持續(xù)觀察是否影響/引發(fā)其他功能/問題

內(nèi)存泄露實(shí)施后,項(xiàng)目的收獲:

  • OOM減少30%以上

  • 平均使用內(nèi)存從80M穩(wěn)定到40M左右

  • 用戶體驗(yàn)上升,流暢度提升

  • 存活率上升,推送到達(dá)率提升

類型

  • IO

    • FileStream

    • Cursor

  • Bitmap

  • Context

    • 單例

    • Callback

  • Service

    • BraodcastReceiver

    • ContentObserver

  • Handler

  • Thread

技巧

  • 慎用Context

    • Context概念

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

  • 善用Reference

    類型垃圾回收時(shí)間生存時(shí)間
    強(qiáng)引用永遠(yuǎn)不會(huì)JVM停止運(yùn)行時(shí)終止
    軟引用內(nèi)存不足時(shí)內(nèi)存不足時(shí)終止
    弱引用垃圾回收時(shí)垃圾回收時(shí)終止
    虛引用垃圾回收時(shí)垃圾回收時(shí)終止
    • Java引用介紹

    • Java四種引用由高到低依次為:強(qiáng)引用  >  軟引用  >  弱引用  >  虛引用

    • 表格說明

  • 復(fù)用ConvertView

  • 對(duì)象釋放

    • 遵循誰創(chuàng)建誰釋放的原則

    • 示例:顯示調(diào)用clear列表、對(duì)象賦空值

分析

 原理

 根本原因

  • 關(guān)注堆內(nèi)存

 怎么解決

  • 詳見方案

 實(shí)踐分析

  • 詳見實(shí)踐

方案

  • StrictMode

    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy
    .Builder()
    .detectAll()
    .penaltyLog()
    .build());
    StrictMode.setVmPolicy(new StrictMode.VmPolicy
    .Builder()
    .detectAll()
    .penaltyLog()
    .build());
    • 主要檢查項(xiàng):內(nèi)存泄露、耗時(shí)操作等

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

  • Leakcanary

  • Leakcanary + StrictMode + monkey (推薦)

    • 使用階段:功能測(cè)試完成后,穩(wěn)定性測(cè)試開始時(shí)

    • 使用方法:安裝集成了Leakcanary的包,跑monkey

    • 收獲階段:一段時(shí)間后,會(huì)發(fā)現(xiàn)出現(xiàn)N個(gè)泄露

    • 實(shí)戰(zhàn)分析:逐條分析每個(gè)泄露并改善/修復(fù)

    • StrictMode:查看日志搜索StrictMode關(guān)鍵字

  • Adb命令

    • 手動(dòng)觸發(fā)GC

    • 通過adb shell dumpsys meminfo packagename -d查看

    • 查看Activity以及View的數(shù)量

    • 越接近0越好

    • 對(duì)比進(jìn)入Activity以及View前的數(shù)量和退出Activity以及View后的數(shù)量判斷

  • Android Monitor

  • MAT

實(shí)踐(示例)

Bitmap泄露

Bitmap泄露一般會(huì)泄露較多內(nèi)存,視圖片大小、位圖而定

  • 經(jīng)典場(chǎng)景:App啟動(dòng)圖

  • 解決內(nèi)存泄露前后內(nèi)存相差10M+,可謂驚人

  • 解決方案:

App啟動(dòng)圖Activity的onDestroy()中及時(shí)回收內(nèi)存

@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
recycleImageView(imgv_load_ad);
}

public static void recycleImageView(View view){
if(view==null) return;
if(view instanceof ImageView){
Drawable drawable=((ImageView) view).getDrawable();
if(drawable instanceof BitmapDrawable){
Bitmap bmp = ((BitmapDrawable)drawable).getBitmap();
if (bmp != null && !bmp.isRecycled()){
((ImageView) view).setImageBitmap(null);
bmp.recycle();
bmp=null;
}
}
}
}

IO流未關(guān)閉

  • 分析:通過日志可知FileOutputStream()未關(guān)閉

  • 問題代碼:

public static void copyFile(File source, File dest) {
FileChannel inChannel = null;
FileChannel outChannel = null;
Log.i(TAG, "source path: " + source.getAbsolutePath());
Log.i(TAG, "dest path: " + dest.getAbsolutePath());
try {
inChannel = new FileInputStream(source).getChannel();
outChannel = new FileOutputStream(dest).getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
e.printStackTrace();
}
}
  • 解決方案:

    • 及時(shí)關(guān)閉IO流,避免泄露

public static void copyFile(File source, File dest) {
FileChannel inChannel = null;
FileChannel outChannel = null;
Log.i(TAG, "source path: " + source.getAbsolutePath());
Log.i(TAG, "dest path: " + dest.getAbsolutePath());
try {
inChannel = new FileInputStream(source).getChannel();
outChannel = new FileOutputStream(dest).getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
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.<init>(FileOutputStream.java:89)
at java.io.FileOutputStream.<init>(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)

單例模式泄露

  • 分析:通過截圖我們發(fā)現(xiàn)SplashActivity被ActivityUtil的實(shí)例activityStack持有

  • 引用代碼:

ActivityUtil.getAppManager().add(this);
  • 持有代碼:

public void add(Activity activity) {
if (activityStack == null) {
synchronized (ActivityUtil.class){
if (activityStack == null) {
activityStack = new Stack<>();
}
}
}
activityStack.add(activity);
}
  • 解決方案:

    • 在SplashActivity的onDestroy()生命周期移除引用

@Override
protected void onDestroy() {
super.onDestroy();
ActivityUtil.getAppManager().remove(this);
}

靜態(tài)變量持有Context實(shí)例泄露

  • 分析:長(zhǎng)生命周期持有短什么周期引用導(dǎo)致泄露,詳見上文四大組件Context和Application的context使用

  • 示例引用代碼:

private static HttpRequest req;
public static void HttpUtilPost(Context context, int TaskId, String url, String requestBody,ArrayList<HttpHeader> Headers, RequestListener listener) {
// TODO Auto-generated constructor stub
req = new HttpRequest(context, url, TaskId, requestBody, Headers, listener);
req.post();
}
  • 解決方案:

    public static void cancel(int TaskId) {
    if(req != null && req.get() != null){
    req.get().AsyncCancel(TaskId);
    }
    }
    private static WeakReference<HttpRequest> req;
    public static void HttpUtilPost(Context context, int TaskId, String url, String requestBody,ArrayList<HttpHeader> Headers, RequestListener listener) {
    // TODO Auto-generated constructor stub
    req = new WeakReference<HttpRequest>(new HttpRequest(context, url, TaskId, requestBody, Headers, listener));
    req.get().post();
    }
    private static HttpRequest req;
    public static void HttpUtilPost(Context context, int TaskId, String url, String requestBody,ArrayList<HttpHeader> Headers, RequestListener listener) {
    // TODO Auto-generated constructor stub
    req = new HttpRequest(context.getApplicationContext(), url, TaskId, requestBody, Headers, listener);
    req.post();
    }
    • 改為長(zhǎng)生命周期

    • 改為弱引用

    • pass:弱引用隨時(shí)可能為空,使用前先判空

    • 示例代碼:

Context泄露

Callback泄露

服務(wù)未解綁注冊(cè)泄露

  • 分析:一般發(fā)生在注冊(cè)了某服務(wù),不用時(shí)未解綁服務(wù)導(dǎo)致泄露

  • 引用代碼:

private void initSensor() {
// 獲取傳感器管理器
sm = (SensorManager) container.activity.getSystemService(Context.SENSOR_SERVICE);
// 獲取距離傳感器
acceleromererSensor = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
// 設(shè)置傳感器監(jiān)聽器
acceleromererListener = new SensorEventListener() {
......
};
sm.registerListener(acceleromererListener, acceleromererSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
  • 解決方案:

    • 在Activity的onDestroy()方法解綁服務(wù)

@Override
protected void onDestroy() {
super.onDestroy();
sm.unregisterListener(acceleromererListener,acceleromererSensor);
}

Handler泄露

  • 分析:由于Activity已經(jīng)關(guān)閉,Handler任務(wù)還未執(zhí)行完成,其引用了Activity的實(shí)例導(dǎo)致內(nèi)存泄露

  • 引用代碼:

handler.sendEmptyMessage(0);
  • 解決方案:

    • 在Activity的onDestroy()方法回收Handler

@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
  • 圖片后續(xù)遇到再補(bǔ)上

異步線程泄露

  • 分析:一般發(fā)生在線程執(zhí)行耗時(shí)操作時(shí),如下載,此時(shí)Activity關(guān)閉后,由于其被異步線程引用,導(dǎo)致無法被正常回收,從而內(nèi)存泄露

  • 引用代碼:

new Thread() {
public void run() {
imageArray = loadImageFromUrl(imageUrl);
}.start();
  • 解決方案:

    • 把線程作為對(duì)象提取出來

    • 在Activity的onDestroy()方法阻塞線程

thread = new Thread() {
public void run() {
imageArray = loadImageFromUrl(imageUrl);
};
thread.start();

@Override
protected void onDestroy() {
super.onDestroy();
if(thread != null){
thread.interrupt();
thread = null;
}
}

后面

  • 歡迎補(bǔ)充實(shí)際中遇到的泄露類型

  • 文章如有錯(cuò)誤,歡迎指正

  • 如有更好的內(nèi)存泄露分享方法,歡迎一起討論

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多