Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發編碼規范導致的內存洩露問題

Android開發編碼規范導致的內存洩露問題

編輯:關於Android編程

在很久很久之前,看過一篇關於內存洩露的文章,裡面列舉了比較全的應該注意的問題,後來找不到原文地址,今天翻了微博,找到了該文章,為了方便日後自己查看,將注意的問題提取出來。在android開發中,我們的編碼習慣可能會讓我們編寫出一些容易導致內存洩露的代碼。所以我們應該要養成一個良好的編碼習慣。

單例

平時,我們可能會這樣寫單例

public class Singleton{
    private static Singleton instance;
    private Context mContext;
    private Singleton(Context mContext){
        this.mContext = mContext;
    }
    public static Singleton getInstance(Context context){
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(context);
                }
            }
        }
        return instance;
    }
}

然後這樣寫會有隱患,原因是如果我們再Activity中或者其他地方使用Singleton.getInstance()時,我們會順手寫this或者mContext(這個變量也是指向this)作為參數傳進去。我們的Singleton是單例,意味著被初始化後就應該一直存在內存中,以方便我們以後調用的時候不會再次創建Singleton對象,但是Singleton中的mContext變量一直都會持有Activity中的Context,導致Activity即使執行了onDestroy方法,也不能夠將自己銷毀,但是ApplicationContext就顯然有所不同了,它一直伴隨著我們的應用存在,所以就不用擔心前面所說的導致Activity無法銷毀的問題了。於是就誕生了正確的寫法。

public class Singleton{
    private static Singleton instance;
    private Context mContext;
    private Singleton(Context mContext){
        this.mContext = mContext;
    }
    public static Singleton getInstance(Context context){
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(context.getApplicationContext());
                }
            }
        }
        return instance;
    }
}

匿名內部類

在Android開發中,很多地方會用到匿名內部類,比如事件的監聽,Handler的消息處理等等,而一旦使用錯誤,也會導致內存洩露。

public class MainActivity extends Activity{   
    private Button btn;
    private Handler handler = new Handler(){
       @override
       public void handlerMessage(Message msg){   
       }
    };
    @override
    public void onCreate(Bundle bundle){
       super.onCreate(bundle);
       setContextView(R.layout.activity_main);
       btn=(Button)findViewById(R.id.btn);
       btn.setOnClickListner(View.OnClickListener(){
            @override
            public void onClick(View view){
                Message message = Message.obtain();
                handler.sendMessage(message);
            }
       });

    }
}

當我們執行了MainActivity的finish方法,被延遲的消息會在被處理之前存在於主線程消息隊列中10分鐘,而這個消息中又包含了Handler的引用,而Handler是一個匿名內部類的實例,其持有外面的MainActivity的引用,所以這導致了MainActivity無法回收,進而導致MainActivity持有的很多資源都無法回收,所以產生了內存洩露。然而一個靜態的匿名內部類實例是不會持有外部類的引用的。所以正確的寫法應該是這樣的。

 mActivity;
        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivity.get();
            if (activity != null) {
                 // ...
            }
        }
    }
    private final MyHandler handler = new MyHandler(this);
    @override
    public void onCreate(Bundle bundle){
       super.onCreate(bundle);
       setContextView(R.layout.activity_foo_layout);
       btn=(Button)findViewById(R.id.btn);
       btn.setOnClickListner(View.OnClickListener(){
            @override
            public void onClick(View view){
                Message message = Message.obtain();
                handler.sendMessage(message);
            }
       });
    }
} data-snippet-id=ext.1acf9c83b2466fba0477741dbdd20005 data-snippet-saved=false data-csrftoken=XoHltxJD-v4Ok64jhiO1pF8WiVeTQGTWyND8 data-codota-status=done>public class MainActivity extends Activity{
    private Button btn;
    private static class MyHandler extends Handler {
        private final WeakReference mActivity;
        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivity.get();
            if (activity != null) {
                 // ...
            }
        }
    }
    private final MyHandler handler = new MyHandler(this);
    @override
    public void onCreate(Bundle bundle){
       super.onCreate(bundle);
       setContextView(R.layout.activity_foo_layout);
       btn=(Button)findViewById(R.id.btn);
       btn.setOnClickListner(View.OnClickListener(){
            @override
            public void onClick(View view){
                Message message = Message.obtain();
                handler.sendMessage(message);
            }
       });
    }
}

很多時候我們甚至會使用handler發送一個匿名Runnable對象,同樣的也會導致內存洩露的問題。因此Runnable對象應該也要使用靜態的匿名內部類。

 textViewWeakReference;
    public MyRunnable(TextView textView){
        textViewWeakReference = new WeakReference(textView);
    }
    @override
    public void run(){
         final TextView textView = textViewWeakReference.get();
         if(textView != null){
              textView.setText(OK);
         }
    };
} data-snippet-id=ext.3155e5950545f4de89fbe7248585052f data-snippet-saved=false data-csrftoken=n1F4MNFa-eZF9L3VNdkKwQawidFjU1CVZRPU data-codota-status=done>private static class MyRunnable implements Runnable{
    private WeakReference textViewWeakReference;
    public MyRunnable(TextView textView){
        textViewWeakReference = new WeakReference(textView);
    }
    @override
    public void run(){
         final TextView textView = textViewWeakReference.get();
         if(textView != null){
              textView.setText(OK);
         }
    };
}

使用的時候這樣用就可以了

handler.postDelayed(new MyRunnable(textView),1000 * 60 * 10);

Handler

在使用了Handler之後,記得在onDestroy裡面調用

handler.removeCallbacksAndMessages(object token);

移除相關消息。

我們可以使用

handler.removeCallbacksAndMessages(null);

當參數為null時,可以清除掉所有跟次handler相關的Runnable和Message,我們在onDestroy中調用次方法也就不會發生內存洩漏了。

對以上幾點做一個總結
- 不要讓生命周期長於Activity的對象持有到Activity的引用
- 盡量使用Application的Context而不是Activity的Context
- 盡量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類實例的引用。如果使用靜態內部類,將外部實例引用作為弱引用持有。
- 垃圾回收不能解決內存洩露,了解Android中垃圾回收機制

Context

注意Context與ApplicationContext的區別
- View.getContext,返回當前View對象的Context對象,通常是當前正在展示的Activity對象。
- Activity.getApplicationContext,獲取當前Activity所在的(應用)進程的Context對象,通常我們使用Context對象時,要優先考慮這個全局的進程Context。
- ContextWrapper.getBaseContext():用來獲取一個ContextWrapper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發中使用並不多,也不建議使用。
- Activity.this 返回當前的Activity實例,如果是UI控件需要使用Activity作為Context對象,但是默認的Toast實際上使用ApplicationContext也可以。

這裡寫圖片描述系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。
- 數字3:在receiver為null時允許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(可以無視)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因為在其內部方法中都有一個context用於使用。

好了,這裡我們看下表格,重點看Activity和Application,可以看到,和UI相關的方法基本都不建議或者不可使用Application,並且,前三個操作基本不可能在Application中出現。實際上,只要把握住一點,凡是跟UI相關的,都應該使用Activity做為Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,當然了,注意Context引用的持有,防止內存洩漏。
關於Context更詳細的內容見Android Context 上下文 你必須知道的一切

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved