欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android內(nèi)存泄漏的輕松解決方法

 更新時間:2019年04月04日 10:41:00   作者:Android高級架構(gòu)師  
這篇文章主要給大家介紹了關(guān)于Android內(nèi)存泄漏的輕松解決方法,文中通過示例代碼介紹的非常詳細(xì),對各位Android具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧

前言

內(nèi)存管理的目的就是讓我們在開發(fā)過程中有效避免我們的應(yīng)用程序出現(xiàn)內(nèi)存泄露的問題。內(nèi)存泄露相信大家都不陌生,我們可以這樣理解:「沒有用的對象無法回收的現(xiàn)象就是內(nèi)存泄露」。

如果程序發(fā)生了內(nèi)存泄露,則會帶來以下這些問題

  • 應(yīng)用可用的內(nèi)存減少,增加了堆內(nèi)存的壓力
  • 降低了應(yīng)用的性能,比如會觸發(fā)更頻繁的 GC
  • 嚴(yán)重的時候可能會導(dǎo)致內(nèi)存溢出錯誤,即 OOM Error

下面我們從基礎(chǔ)說起

基礎(chǔ)知識

Java 的內(nèi)存分配簡述

  • 方法區(qū)(non-heap):編譯時就分配好,在程序整個運行期間都存在。它主要存放靜態(tài)數(shù)據(jù)和常量;
  • 棧區(qū):當(dāng)方法執(zhí)行時,會在棧區(qū)內(nèi)存中創(chuàng)建方法體內(nèi)部的局部變量,方法結(jié)束后自動釋放內(nèi)存;
  • 堆區(qū)(heap):通常用來存放 new 出來的對象。由 GC 負(fù)責(zé)回收。

Java四種不同的引用類型

  • 強(qiáng)引用(Strong Reference):JVM 寧愿拋出 OOM,也不會讓 GC 回收存在強(qiáng)引用的對象。
  • 軟引用(Soft Reference) :一個對象只具有軟引用,在內(nèi)存不足時,這個對象才會被 GC 回收。
  • 弱引用(weak Reference):在 GC 時,如果一個對象只存在弱引用,那么它將會被回收。
  • 虛引用(Phantom Reference):任何時候都可以被 GC 回收,當(dāng)垃圾回收器準(zhǔn)備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前,把這個虛引用加入到與之關(guān)聯(lián)的引用隊列中。程序可以通過判斷引用隊列中是否存在該對象的虛引用,來了解這個對象是否將要被回收。可以用來作為 GC 回收 Object 的標(biāo)志。

與 Android 中的差異:在 2.3 以后版本中,即使內(nèi)存夠用,Android 系統(tǒng)會優(yōu)先將 SoftReference 的對象提前回收掉, 其他和 Java 中是一樣的。
因此谷歌官方建議用LruCache(least recentlly use 最少最近使用算法)。會將內(nèi)存控制在一定的大小內(nèi), 超出最大值時會自動回收, 這個最大值開發(fā)者自己定。

什么是內(nèi)存泄漏?

  • 對于 C++ 來說,內(nèi)存泄漏就是 new 出來的對象沒有 delete,俗稱野指針;
  • 而對于 java 而言,就是存放在堆上的 Object 無法被 GC 正?;厥铡?/li>

內(nèi)存泄漏根本原因

長生命周期的對象持有短生命周期對象**強(qiáng)/軟引用**,導(dǎo)致本應(yīng)該被回收的短生命周期的對象卻無法被正?;厥?。

例如在單例模式中,我們常常在獲取單例對象時需要傳一個 Context 。單例對象是一個長生命周期的對象(應(yīng)用程序結(jié)束時才終結(jié)),而如果我們傳遞的是某一個 Activity 作為 context,那么這個 Activity 就會因為引用被持有而無法銷毀,從而導(dǎo)致內(nèi)存泄漏。

內(nèi)存泄漏的危害

  • 運行性能的問題: Android在運行的時候,如果內(nèi)存泄漏將導(dǎo)致其他組件可用的內(nèi)存變少,一方面會使得GC的頻率加劇,在發(fā)生GC的時候,所有進(jìn)程都必須進(jìn)行等待,GC的頻率越多,從而用戶越容易感知到卡頓。另一方面,內(nèi)存變少,將可能使得系統(tǒng)會額外分配給你一些內(nèi)存,而影響整個系統(tǒng)的運行狀況。
  • 運行崩潰問題: 內(nèi)存泄露是內(nèi)存溢出(OOM)的重要原因之一,會導(dǎo)致 Crash。如果應(yīng)用程序在消耗光了所有的可用堆空間,那么再試圖在堆上分配新對象時就會引起 OOM(Out Of Memory Error) 異常,此時應(yīng)用程序就會崩潰退出。

內(nèi)存泄漏的典型案例

永遠(yuǎn)的單例(Singleton)

由于單例模式的靜態(tài)特性,使得它的生命周期和我們的應(yīng)用一樣長,一不小心讓單例無限制的持有 Activity 的強(qiáng)引用就會導(dǎo)致內(nèi)存泄漏。

解決方案

把傳入的 Context 改為同應(yīng)用生命周期一樣長的 Application 中的 Context。

通過重寫 Application,提供 getContext 方法,那樣就不需要在獲取單例時傳入 context。

public class BaseApplication extends Application{
 private static ApplicationContext sContext;
 @Override
 public void onCreate(){
 super.onCreate();
 sContext = getApplicationContext();
 }
 public static Context getApplicationContext(){
 return sContext;
 }
}

Handler引發(fā)的內(nèi)存泄漏

由于 Handler 屬于 TLS(Thread Local Storage)變量,導(dǎo)致它的生命周期和 Activity 不一致。因此通過 Handler 來更新 UI 一般很難保證跟 View 或者 Activity 的生命周期一致,故很容易導(dǎo)致無法正確釋放。

例如:

public class HandlerBadActivity extends AppCompatActivity {
 private final Handler handler = new Handler(){//非靜態(tài)內(nèi)部類,持有外部類的強(qiáng)引用
 @Override
 public void handleMessage(Message msg) {
 super.handleMessage(msg);
 }
 };
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_handler_bad);
 // 延遲 5min 發(fā)送一個消息
 handler.postDelayed(new Runnable() {
 //內(nèi)部會將該 Runable 封裝為一個 Message 對象,同時將 Message.target 賦值為 handler
 @Override
 public void run() {
 //do something
 }
 }, 1000 * 60 * 5);
 this.finish();
 }
}

上面的代碼中發(fā)送了了一個延時 5 分鐘執(zhí)行的 Message,當(dāng)該 Activity 退出的時候,延時任務(wù)(Message)還在主線程的 MessageQueue 中等待,此時的 Message 持有 Handler 的強(qiáng)引用(創(chuàng)建時通過 Message.target 進(jìn)行指定),并且由于 Handler 是 HandlerBadActivity 的非靜態(tài)內(nèi)部類,所以 Handler 會持有一個指向 HandlerBadActivity 的強(qiáng)引用,所以雖然此時 HandlerBadActivity 調(diào)用了 finish 也無法進(jìn)行內(nèi)存回收,造成內(nèi)存泄漏。

解決方法

將 Handler 聲明為靜態(tài)內(nèi)部類,但是要注意**如果用到 Context 等外部類的 非static 對象,還是應(yīng)該使用 ApplicationContext 或者通過弱引用來持有這些外部對象**。

public class HandlerGoodActivity extends AppCompatActivity {
 private static final class MyHandler extends Handler{//聲明為靜態(tài)內(nèi)部類(避免持有外部類的強(qiáng)引用)
 private final WeakReference<HandlerGoodActivity> mActivity;
 public MyHandler(HandlerGoodActivity activity){
 this.mActivity = new WeakReference<HandlerGoodActivity>(activity);//使用弱引用
 }
 @Override
 public void handleMessage(Message msg) {
 HandlerGoodActivity activity = mActivity.get();
 if (activity == null || activity.isFinishing() || activity.isDestroyed()) {//判斷 activity 是否為空,以及是否正在被銷毀、或者已經(jīng)被銷毀
 removeCallbacksAndMessages(null);
 return;
 }
 // do something
 }
 }
 private final MyHandler myHandler = new MyHandler(this);
}

慎用 static 成員變量

static 修飾的變量位于內(nèi)存的方法區(qū),其生命周期與 App 的生命周期一致。 這必然會導(dǎo)致一系列問題,如果你的 app 進(jìn)程設(shè)計上是長駐內(nèi)存的,那即使 app 切到后臺,這部分內(nèi)存也不會被釋放。

解決方法

不要在類初始化時初始化靜態(tài)成員,也就是可以考慮懶加載。架構(gòu)設(shè)計上要思考是否真的有必要這樣做,盡量避免。如果架構(gòu)需要這么設(shè)計,那么此對象的生命周期你有責(zé)任管理起來。

當(dāng)然,Application 的 context 不是萬能的,所以也不能隨便亂用,對于有些地方則必須使用 Activity 的 Context,對于Application,Service,Activity三者的Context的應(yīng)用場景如下:

功能 Application Service Activity
Start an Activity NO1 NO1 YES
Show a Dialog NO NO YES
Layout Inflation YES YES YES
Start an Service YES YES YES
Bind an Service YES YES YES
Send a Broadcast YES YES YES
Register BroadcastReceiver YES YES YES
Load Resource Values YES YES YES
  • NO1 表示 Application 和 Service 可以啟動一個 Activity,不過需要創(chuàng)建一個新的 task 任務(wù)隊列。
  • 對于 Dialog 而言,只有在 Activity 中才能創(chuàng)建。

使用系統(tǒng)服務(wù)引發(fā)的內(nèi)存泄漏

為了方便我們使用一些常見的系統(tǒng)服務(wù),Activity 做了一些封裝。比如說,可以通過 getPackageManager在 Activtiy 中獲取 PackageManagerService,但是,里面實際上調(diào)用了 Activity 對應(yīng)的 ContextImpl 中的 getPackageManager 方法

ContextWrapper#getPackageManager

@Override
public PackageManager getPackageManager() {
 return mBase.getPackageManager();
}

ContextImpl#getPackageManager

@Override
public PackageManager getPackageManager() {
 if (mPackageManager != null) {
 return mPackageManager;
 }
 IPackageManager pm = ActivityThread.getPackageManager();
 if (pm != null) {
 // Doesn't matter if we make more than one instance.
 return (mPackageManager = new ApplicationPackageManager(this, pm));//創(chuàng)建 ApplicationPackageManager
 }
 return null;
}

ApplicationPackageManager#ApplicationPackageManager

ApplicationPackageManager(ContextImpl context,
    IPackageManager pm) {
 mContext = context;//保存 ContextImpl 的強(qiáng)引用
 mPM = pm;
}

private UserManagerService(Context context, PackageManagerService pm,
 Object packagesLock, File dataDir) {
 mContext = context;//持有外部 Context 引用
 mPm = pm;
 //代碼省略
}

PackageManagerService#PackageManagerService

public class PackageManagerService extends IPackageManager.Stub {
 static UserManagerService sUserManager;//持有 UMS 靜態(tài)引用
 public PackageManagerService(Context context, Installer installer,
 boolean factoryTest, boolean onlyCore) {
  sUserManager = new UserManagerService(context, this, mPackages);//初始化 UMS
 }
}

遇到的內(nèi)存泄漏問題是因為在 Activity 中調(diào)用了 getPackageManger 方法獲取 PMS ,該方法調(diào)用的是 ContextImpl,此時如果ContextImpl 中 PackageManager 為 null,就會創(chuàng)建一個 PackageManger(ContextImpl 會將自己傳遞進(jìn)去,而 ContextImpl 的 mOuterContext 為 Activity),創(chuàng)建 PackageManager 實際上會創(chuàng)建 PackageManagerService(簡稱 PMS),而 PMS 的構(gòu)造方法中會創(chuàng)建一個 UserManger(UserManger 初始化之后會持有 ContextImpl 的強(qiáng)引用)。
只要 PMS 的 class 未被銷毀,那么就會一直引用著 UserManger ,進(jìn)而導(dǎo)致其關(guān)聯(lián)到的資源無法正常釋放。

解決辦法

將getPackageManager()改為 getApplication()#getPackageManager() 。這樣引用的就是 Application Context,而非 Activity 了。

遠(yuǎn)離非靜態(tài)內(nèi)部類和匿名類

因為使用非靜態(tài)內(nèi)部類和匿名類都會默認(rèn)持有外部類的引用,如果生命周期不一致,就會導(dǎo)致內(nèi)存泄漏。

public class NestedClassLeakActivity extends AppCompatActivity {

 class InnerClass {//非靜態(tài)內(nèi)部類

 }

 private static InnerClass sInner;//指向非靜態(tài)內(nèi)部類的靜態(tài)引用

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_nested_class);
 if (sInner == null) {
  sInner = new InnerClass();//創(chuàng)建非靜態(tài)內(nèi)部類的實例
 }
 }
}

非靜態(tài)內(nèi)部類默認(rèn)會持有外部類的引用,而外部類中又有一個該非靜態(tài)內(nèi)部類的靜態(tài)實例,該靜態(tài)實例的生命周期和應(yīng)用的一樣長,而靜態(tài)實例又持有 Activity 的引用,因此導(dǎo)致 Activity 的內(nèi)存資源不能正常回收。

解決方法

將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類 也可以將該內(nèi)部類抽取出來封裝成一個單例

集合引發(fā)的內(nèi)存泄漏

我們通常會把一些對象的引用加入到集合容器(比如ArrayList)中,當(dāng)我們不再需要該對象時(通常會調(diào)用 remove 方法),并沒有把它的引用從集合中清理掉(其中的一種情況就是 remove 方法沒有將不再需要的引用賦值為 null),下面以 ArrayList 的 remove 方法為例

public E remove( int index) {
 // 數(shù)組越界檢查
 RangeCheck(index);
 modCount++;
 // 取出要刪除位置的元素,供返回使用
 E oldValue = (E) elementData[index];
 // 計算數(shù)組要復(fù)制的數(shù)量
 int numMoved = size - index - 1;
 // 數(shù)組復(fù)制,就是將index之后的元素往前移動一個位置
 if (numMoved > 0)
 System. arraycopy(elementData, index+1, elementData, index,
   numMoved);
 // 將數(shù)組最后一個元素置空(因為刪除了一個元素,然后index后面的元素都向前移動了,所以最后一個就沒用了),好讓gc盡快回收
 elementData[--size ] = null; // Let gc do its work
 return oldValue;
}

WebView 引發(fā)的內(nèi)存泄漏

WebView 解析網(wǎng)頁時會申請Native堆內(nèi)存用于保存頁面元素,當(dāng)頁面較復(fù)雜時會有很大的內(nèi)存占用。如果頁面包含圖片,內(nèi)存占用會更嚴(yán)重。并且打開新頁面時,為了能快速回退,之前頁面占用的內(nèi)存也不會釋放。有時瀏覽十幾個網(wǎng)頁,都會占用幾百兆的內(nèi)存。這樣加載網(wǎng)頁較多時,會導(dǎo)致系統(tǒng)不堪重負(fù),最終強(qiáng)制關(guān)閉應(yīng)用,也就是出現(xiàn)應(yīng)用閃退或重啟。
由于占用的都是Native 堆內(nèi)存,所以實際占用的內(nèi)存大小不會顯示在常用的 DDMS Heap 工具中( DMS Heap 工具看到的只是Java虛擬機(jī)分配的內(nèi)存,即使Native堆內(nèi)存已經(jīng)占用了幾百兆,這里顯示的還只是幾兆或十幾兆)。只有使用 adb shell 中的一些命令比如 dumpsys meminfo 包名,或者在程序中使用 Debug.getNativeHeapSize()才能看到 Native 堆內(nèi)存信息。

據(jù)說由于 WebView 的一個 BUG,即使它所在的 Activity(或者Service) 結(jié)束也就是 onDestroy() 之后,或者直接調(diào)用 WebView.destroy()之后,它所占用這些內(nèi)存也不會被釋放。

解決方法

把使用了 WebView 的 Activity (或者 Service) 放在單獨的進(jìn)程里。

  • 系統(tǒng)在檢測到應(yīng)用占用內(nèi)存過大有可能被系統(tǒng)干掉
  • 也可以在它所在的 Activity(或者 Service) 結(jié)束后,調(diào)用 System.exit(0),主動Kill掉進(jìn)程。由于系統(tǒng)的內(nèi)存分配是以進(jìn)程為準(zhǔn)的,進(jìn)程關(guān)閉后,系統(tǒng)會自動回收所有內(nèi)存。

使用 WebView 的頁面(Activity),在生命周期結(jié)束頁面退出(onDestory)的時候,主動調(diào)用WebView.onPause()==以及==WebView.destory()以便讓系統(tǒng)釋放 WebView 相關(guān)資源。

其他常見的引起內(nèi)存泄漏原因

Android 3.0 以下,Bitmap 在不使用的時候沒有使用 recycle() 釋放內(nèi)存。

非靜態(tài)內(nèi)部類的靜態(tài)實例容易造成內(nèi)存泄漏:即一個類中如果你不能夠控制它其中內(nèi)部類的生命周期(譬如Activity中的一些特殊Handler等),則盡量使用靜態(tài)類和弱引用來處理(譬如ViewRoot的實現(xiàn))。

警惕線程未終止造成的內(nèi)存泄露;譬如在 Activity 中關(guān)聯(lián)了一個生命周期超過 Activity 的 Thread,在退出 Activity 時切記結(jié)束線程。

一個典型的例子就是 HandlerThread 的 run 方法。該方法在這里是一個死循環(huán),它不會自己結(jié)束,線程的生命周期超過了 Activity 生命周期,我們必須手動在 Activity 的銷毀方法中中調(diào)用 thread.getLooper().quit() 才不會泄露。

對象的注冊與反注冊沒有成對出現(xiàn)造成的內(nèi)存泄露;譬如注冊廣播接收器、注冊觀察者(典型的譬如數(shù)據(jù)庫的監(jiān)聽)等。
創(chuàng)建與關(guān)閉沒有成對出現(xiàn)造成的泄露;譬如Cursor資源必須手動關(guān)閉,WebView必須手動銷毀,流等對象必須手動關(guān)閉等。

避免代碼設(shè)計模式的錯誤造成內(nèi)存泄露;譬如循環(huán)引用,A 持有 B,B 持有 C,C 持有 A,這樣的設(shè)計誰都得不到釋放。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。

相關(guān)文章

  • Android EditText限制輸入字符的方法總結(jié)

    Android EditText限制輸入字符的方法總結(jié)

    這篇文章主要介紹了 Android EditText限制輸入字符的方法總結(jié)的相關(guān)資料,這里提供了五種方法來實現(xiàn)并進(jìn)行比較,需要的朋友可以參考下
    2017-07-07
  • Android自定義進(jìn)度條的圓角橫向進(jìn)度條實例詳解

    Android自定義進(jìn)度條的圓角橫向進(jìn)度條實例詳解

    本文通過實例代碼給大家詳細(xì)介紹了Android自定義進(jìn)度條的圓角橫向進(jìn)度條的相關(guān)資料。非常不錯,具有參考借鑒價值,感興趣的朋友一起看看吧
    2016-09-09
  • Android實現(xiàn)自由拖動并顯示文字的懸浮框

    Android實現(xiàn)自由拖動并顯示文字的懸浮框

    這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)自由拖動并顯示文字的懸浮框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-01-01
  • Android自定義View實現(xiàn)QQ運動積分轉(zhuǎn)盤抽獎功能

    Android自定義View實現(xiàn)QQ運動積分轉(zhuǎn)盤抽獎功能

    這篇文章主要為大家詳細(xì)介紹了Android自定義View實現(xiàn)QQ運動積分轉(zhuǎn)盤抽獎功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Android基于TextView實現(xiàn)跑馬燈效果

    Android基于TextView實現(xiàn)跑馬燈效果

    這篇文章主要為大家詳細(xì)介紹了Android基于TextView實現(xiàn)跑馬燈效果的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 詳解Android中Handler的內(nèi)部實現(xiàn)原理

    詳解Android中Handler的內(nèi)部實現(xiàn)原理

    這篇文章主要介紹了Android中Handler的內(nèi)部實現(xiàn)原理,對Handler和消息循環(huán)的實現(xiàn)原理進(jìn)行源碼分析,需要的朋友可以參考下
    2015-12-12
  • 談?wù)凙ndroid的三種網(wǎng)絡(luò)通信方式

    談?wù)凙ndroid的三種網(wǎng)絡(luò)通信方式

    Android平臺有三種網(wǎng)絡(luò)接口可以使用,他們分別是:java.net.*(標(biāo)準(zhǔn)Java接口)、Org.apache接口和Android.net.*(Android網(wǎng)絡(luò)接口)。本文詳細(xì)的介紹,有興趣的可以了解一下。
    2017-01-01
  • Android發(fā)送短信功能代碼

    Android發(fā)送短信功能代碼

    這篇文章主要介紹了Android發(fā)送短信功能代碼,并附有較為詳盡的代碼說明,有助于讀者更好的理解代碼功能,需要的朋友可以參考下
    2014-09-09
  • Flutter?Dio?簡單封裝demo

    Flutter?Dio?簡單封裝demo

    這篇文章主要為大家介紹了Flutter Dio簡單封裝示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Android開發(fā)經(jīng)驗談:并發(fā)編程(線程與線程池)(推薦)

    Android開發(fā)經(jīng)驗談:并發(fā)編程(線程與線程池)(推薦)

    這篇文章主要介紹了Android開發(fā)經(jīng)驗談:并發(fā)編程(線程與線程池),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04

最新評論