歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Android開發,中可能會導致內存洩露的問題

Android開發,中可能會導致內存洩露的問題

日期:2017/3/1 9:20:03   编辑:Linux編程

在Android編碼中,會有一些簡便的寫法和編碼習慣,會導致我們的代碼有很多內存洩露的問題。在這裡做一個已知錯誤的總結(其中有一些是個人總結和參考其他博主的文章,在此表示感謝)。

本文會不定時更新,將自己遇到的內存洩漏相關的問題記錄下來並提供解決辦法。

1,編寫單例的時候常出現的錯誤。

錯誤方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 public class Foo{
      private static Foo foo;
      private Context mContext;
      private Foo(Context mContext){
           this.mContext = mContext;
      }
      // 普通單例,非線程安全
      public static Foo getInstance(Context mContext){
           if(foo == null)
                foo = new Foo(mContext);
           return foo;
      }
      public void otherAction(){
           mContext.xxxx();
           ....
      }
 }

錯誤原因:

如果我們在Activity A中或者其他地方使用Foo.getInstance()時,我們總是會順手寫一個『this』或者『mContext』(這個變量也是指向this)。試想一下,當前我們所用的Foo是單例,意味著被初始化後會一直存在與內存中,以方便我們以後調用的時候不會在此次創建Foo對象。但Foo中的『mContext』變量一直都會持有Activity A中的『Context』,導致Activity A即使執行了onDestroy方法,也不能夠將自己銷毀。但『applicationContext』就不同了,它一直伴隨著我們應用存在(中途也可能會被銷毀,但也會自動reCreate),所以就不用擔心Foo中的『mContext』會持有某Activity的引用,讓其無法銷毀。

正確方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 public class Foo{
      private static Foo foo;
      private Context mContext;
      private Foo(Context mContext){
           this.mContext = mContext;
      }
      // 普通單例,非線程安全
      public static Foo getInstance(Context mContext){
           if(foo == null)
                foo = new Foo(mContext.getApplicationContext());
           return foo;
      }
      public void otherAction(){
           mContext.xxxx();
           ....
      }
 }

2,使用匿名內部類的時候經常出現的錯誤

錯誤方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 public class FooActivity extends Activity{
      private TextView textView;
      private Handler handler = new Handler(){
           @override
           public void handlerMessage(Message msg){
           }
      };
      @override
      public void onCreate(Bundle bundle){
           super.onCreate(bundle);
           setContextView(R.layout.activity_foo_layout);
           textView = (TextView)findViewById(R.id.textView);
           handler.postDelayed(new Runnable(){
                @override
                public void run(){
                     textView.setText(“ok”);
                };
           },1000 * 60 * 10);
      }
 }

錯誤原因:

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

注意上面的new Runnable這裡也是匿名內部類實現的,同樣也會持有FooActivity的引用,也會阻止FooActivity被回收。

一個靜態的匿名內部類實例不會持有外部類的引用。

正確方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class FooActivity extends Activity{
     private TextView textView;
     private static class MyHandler extends Handler {
     private final WeakReference<FooActivity> mActivity;
     public MyHandler(FooActivity activity) {
          mActivity = new WeakReference<FooActivity>(activity);
     }
     @Override
     public void handleMessage(Message msg) {
          FooActivity 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);
          textView = (TextView)findViewById(R.id.textView);
          handler.postDelayed(new MyRunnable(textView),1000 * 60 * 10);
     }
     private static class MyRunnable implements Runnable{
          private WeakReference<TextView> textViewWeakReference;
          public MyRunnable(TextView textView){
               textViewWeakReference = new WeakReference<TextView>(textView);
          }
               @override
               public void run(){
                    final TextView textView = textViewWeakReference.get();
                    if(textView != null){
                         textView.setText("OK");
                    }
               };
     }
}

3,關於 handler 的使用

使用 handler 後,記得在 onDestroy 裡面調用 handler.removeCallbacksAndMessages(object token);

1
2
3
4
5
6
7
@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
    // removeCallbacksAndMessages,當參數為null的時候,可以清除掉所有跟當前handler相關的Runnable和Message。
    // 我們在onDestroy中調用次方法也就不會發生內存洩漏了。
}

4,InputMethodManager內存洩露現象

現象為:某個界面上出現InputMethodManager持有一Activity,導致該Activity無法回收.如果該Activity再次被打開,則舊的會釋放掉,但新打開的會被繼續持有無法釋放回收.

此問題為部分系統問題。現已知的有:> 三星note3 N9008 官方ROM 4.4.2> 天語k_touch_v9官方ROM 4.0.4

這裡只是給出了解決方法,具體的解決思路請參考下面: [Android][Memory Leak] InputMethodManager內存洩露現象及解決

使用如下代碼在 Activity 的 onDestroy 中調用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static void fixInputMethodManagerLeak(Context destContext) {
  if (destContext == null) {
    return;
  }
  InputMethodManager imm = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE);
  if (imm == null) {
    return;
  }
  String [] arr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
  Field f = null;
  Object obj_get = null;
  for (int i = 0;i < arr.length;i ++) {
    String param = arr[i];
    try{
      f = imm.getClass().getDeclaredField(param);
      if (f.isAccessible() == false) {
        f.setAccessible(true);
      } // author: sodino mail:[email protected]
      obj_get = f.get(imm);
      if (obj_get != null && obj_get instanceof View) {
        View v_get = (View) obj_get;
        if (v_get.getContext() == destContext) { // 被InputMethodManager持有引用的context是想要目標銷毀的
          f.set(imm, null); // 置空,破壞掉path to gc節點
        } else { // 不是想要目標銷毀的,即為又進了另一層界面了,不要處理,避免影響原邏輯,也就不用繼續for循環了
          }
          break;
        }
      }
    }catch(Throwable t){
      t.printStackTrace();
    }
  }
}

開發中需要注意的點以免內存洩漏:

*不要讓生命周期長於Activity的對象持有到Activity的引用

*盡量使用Application的Context而不是Activity的Context

*盡量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類實例的引用(具體可以查看細話Java:”失效”的private修飾符了解)。如果使用靜態內部類,將外部實例引用作為弱引用持有。

*垃圾回收不能解決內存洩露,了解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也可以。

大家注意看到有一些NO上添加了一些數字,其實這些從能力上來說是YES,但是為什麼說是NO呢?下面一個一個解釋:

數字1:啟動Activity在這些類中是可以的,但是需要創建一個新的task。一般情況不推薦。

數字2:在這些類中去layout inflate是合法的,但是會使用系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。

數字3:在receiver為null時允許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(可以無視)

注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因為在其內部方法中都有一個context用於使用。

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

更多Android相關信息見Android 專題頁面 http://www.linuxidc.com/topicnews.aspx?tid=11

Copyright © Linux教程網 All Rights Reserved