Android開發之漫漫長途 番外篇——記憶體流失分析與解決

來源:互聯網
上載者:User

標籤:參數   std   pre   方法   移除   context   通知欄   另一個   single   

該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡量按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑒了其他的優質部落格,在此向各位大神表示感謝,膜拜!!!另外,本系列文章知識可能需要有一定Android開發基礎和項目經驗的同學才能更好理解,也就是說該系列文章面向的是Android中進階開發工程師。

前言

上一篇我們主要上了一個執行個體來把讀者帶進自訂ViewGroup的大門,只是帶進大門,自訂View的內容還有很多,我之後碰到一些好的自訂View的話一定還來這裡分享。本篇內容我們來分析App運行過程中出現的記憶體流失及如何解決。

記憶體流失概念及其影響

記憶體流失通俗的講是一個本該被回收的對象卻因為某些原因導致其不能回收。我們都知道對象是有生命週期的,從生到死,當對象的任務完成之後,由Android系統進行記憶體回收。我們知道Android系統為某個App分配的記憶體是有限的(這個可能根據機型的不同而不同),當一個應用中產生的記憶體流失比較多時,就難免會導致應用所需要的記憶體超過這個系統分配的記憶體限額,最終導致OOM(OutOfMemory)使程式崩潰。

記憶體流失檢查工具介紹

早在使用Eclipse的時候我們就知道了MAT效能分析工具,使用MAT當然能檢查記憶體流失,不過使用稍微有些麻煩,我這裡介紹另一個工具,同時呢,我們也拋棄了Eclipse,擁抱Android Studio。這個工具名叫LeakCanary。為什麼要使用這個工具呢,當然因為其簡單,傻瓜式操作。這個工具是在Github開源的,是Square公司出品的,不是有一句話嘛,Square出品必屬精品,https://github.com/square/leakcanary我們可以方便的引用它

In your build.gradle:

dependencies {   debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.5.4‘   releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.5.4‘ }

In your Application class:

public class ExampleApplication extends Application {  @Override public void onCreate() {    super.onCreate();    if (LeakCanary.isInAnalyzerProcess(this)) {      // This process is dedicated to LeakCanary for heap analysis.      // You should not init your app in this process.      return;    }    LeakCanary.install(this);    // Normal app init code...  }}

就是如此簡單,那麼下面我們就來用一下把 結合下面的記憶體流失情境應用。

常見的記憶體流失

在我們平時的開發中可能已經造成了記憶體流失而不自知,下面就羅列其中幾種,看看你的程式裡是不是有這樣的代碼。

靜態變數造成的記憶體流失
public class MainActivity extends Activity{    private static final String TAG = "MainActivity";    private static Context sContext;        private static View sView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //這裡直接把當前Activity賦值給了靜態變數sContext        sContext = this;        //這種寫法和上面的類似        sView = new View(this);    }}

上面這種方法估計小學生都知道會造成記憶體流失,原因是當MainActivity對象完成任務需要回收時,卻有一個靜態變數引用它(靜態變數的生命週期與Application相同),造成記憶體流失。我們使用LeakCanary分析就是如

當我們的App發生記憶體流失時會在通知欄顯示通知,點擊該通知可得到記憶體流失的詳細資料,或者點擊中的Leaks表徵圖獲得App運行過程中所有的記憶體流失,上面例子中得到的記憶體流失資訊如所示

單例模式造成的記憶體流失

上面的記憶體流失太明顯,估計大家都不會這樣寫,但是單例模式就不一樣了,我們往往會忽略掉錯誤使用單例模式而造成的泄漏。比如說我們常在開發中用到的dp轉px,px轉dp等往往會封裝成一個單例類。如下

public class DisplayUtils {    private static volatile DisplayUtils instance = null;    private Context mContext;    private DisplayUtils(Context context){        this.mContext = context;    }    public static DisplayUtils getInstance(Context context){        if (instance != null){            synchronized (DisplayUtils.class){                if (instance !=null){                    instance = new DisplayUtils(context);                }            }        }        return instance;    }    public int dip2px(float dpValue) {        final float scale = mContext.getResources().getDisplayMetrics().density;        return (int) (dpValue * scale + 0.5f);    }}

然後我們去調用它

public class SingleActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_single);        //這裡我們把當前SingleActivity傳入        DisplayUtils.getInstance(this).dip2px(5);    }}

就這樣記憶體流失產生了,我們可以看圖。

這個圖和上面的記憶體流失的圖很相像。但是我們常常忽略了這種記憶體流失,是因為我們沒有直接使用靜態變數指向傳遞進來的參數,解決辦法要保證Context和AppLication的生命週期一樣,修改後代碼如下:

public class DisplayUtils {    private static volatile DisplayUtils instance = null;    private Context mContext;    private DisplayUtils(Context context){        //這裡變化了,把當前Context指向個應用程式的Context        this.mContext = context.getApplicationContext();    }    public static DisplayUtils getInstance(Context context){        if (instance != null){            synchronized (DisplayUtils.class){                if (instance !=null){                    instance = new DisplayUtils(context);                }            }        }        return instance;    }    public int dip2px(float dpValue) {        final float scale = mContext.getResources().getDisplayMetrics().density;        return (int) (dpValue * scale + 0.5f);    }}
非靜態內部類建立靜態執行個體造成的記憶體流失

我們在程式中基本上不能避免使用ListView或者RecyclerView,談到這些列表展示的類,那麼我們的Adapter基本上也是不可缺少,我們在最佳化ListView的Adapter的時候會使用ViewHolder(RecyclerView本身已經做了最佳化),我們在使用ViewHolder的使用建議使用靜態內部類。那麼為什麼會由此建議呢?這就是我們下面要談到的。非靜態內部類建立靜態執行個體可能造成的記憶體流失

public class NonStaticActivity extends AppCompatActivity {    private static Config sConfig;        @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_non_static);        //Config類並不是靜態類,        sConfig = new Config();        }        class Config {        }}

造成記憶體流失的原因是內部類會隱式持有外部類的引用,這裡的外部類是NonStaticActivity,然而內部類sConfig又是static靜態變數其生命週期與Application生命週期一樣,所以在NonStaticActivity關閉的時候,內部類靜態執行個體依然持有對NonStaticActivity的引用,導致NonStaticActivity無法被回收釋放,引發記憶體流失。
解決辦法就是把內部類生命為靜態內部類,與外部類解耦。,這也是在使用ViewHolder的使用建議使用靜態內部類的原因。

WebView造成的記憶體流失

對於使用Android的WebView造成的記憶體流失。我在此建議使用https://github.com/delight-im/Android-AdvancedWebView,使用這個最佳化後的WebView,按照提示進行操作。

Handler造成的記憶體流失

我在我的項目中使用了handler,此時mHandler會隱式地持有一個外部類對象引用這裡就是MainActivity,當執行postDelayed方法時,該方法會將你的Handler裝入一個Message,並把這條Message推到MessageQueue中,MessageQueue是在一個Looper線程中不斷輪詢處理訊息,那麼當這個Activity退出時訊息佇列中還有未處理的訊息或者正在處理訊息,而訊息佇列中的Message持有mHandler執行個體的引用,mHandler又持有Activity的引用,所以導致該Activity的記憶體資源無法及時回收,引發記憶體流失。

public class HandlerActivity extends AppCompatActivity {    private Handler mHandler = new Handler();    private TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_handler);        mTextView = (TextView) findViewById(R.id.text);//類比記憶體泄露        mHandler.postDelayed(new Runnable() {            @Override            public void run() {                mTextView.setText("test");            }        }, 5 * 60 * 1000);    }}

用LeakCanary可以看到類似

解決辦法是 在HandlerActivity onDestroy裡面移除訊息佇列中所有訊息和所有的Runnable。

@Overrideprotected void onDestroy() {    super.onDestroy();    mHandler.removeCallbacksAndMessages(null);    mHandler = null;}
其他原因造成的記憶體流失

造成記憶體流失的原因有很多,我們這裡只是列舉了其中比較典型的幾種,當然還有好多原因會造成記憶體流失,比如資源開啟但是未關閉、多線程等等等等。但是我們有LeakCanary這個利器哈。

本篇總結

本篇只是稍微介紹了下LeakCanary以及幾種常見的記憶體流失,記憶體流失以及記憶體效能最佳化是個持久的過程。我這裡只是向你們介紹其中一種方法。編程無止境,效能最佳化也是。

下篇預告

好了,我們下一篇介紹正篇Android的訊息機制Looper、Handler、MessageQueue,Message

此致,敬禮

Android開發之漫漫長途 番外篇——記憶體流失分析與解決

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.