1. 程式人生 > >Android 如何有效的解決記憶體洩漏的問題

Android 如何有效的解決記憶體洩漏的問題

前言:最近在研究Handler的知識,其中涉及到一個問題,如何避免Handler帶來的記憶體溢位問題。在網上找了很多資料,有很多都是互相抄的,沒有實際的作用。

本文的記憶體洩漏檢測工具是:LeakCanary  github地址:https://github.com/square/leakcanary

 

 

什麼是記憶體洩漏?

  • 記憶體洩漏是當程式不再使用到的記憶體時,釋放記憶體失敗而產生了無用的記憶體消耗。記憶體洩漏並不是指物理上的記憶體消失,這裡的記憶體洩漏是值由程式分配的記憶體但是由於程式邏輯錯誤而導致程式失去了對該記憶體的控制,使得記憶體浪費。

 

怎樣會導致記憶體洩漏?

  • 資源物件沒關閉造成的記憶體洩漏,如查詢資料庫後沒有關閉遊標cursor
  • 構造Adapter時,沒有使用 convertView 重用
  • Bitmap物件不在使用時呼叫recycle()釋放記憶體
  • 物件被生命週期長的物件引用,如activity被靜態集合引用導致activity不能釋放

 

記憶體洩漏有什麼危害?

  • 記憶體洩漏對於app沒有直接的危害,即使app有發生記憶體洩漏的情況,也不一定會引起app崩潰,但是會增加app記憶體的佔用。記憶體得不到釋放,慢慢的會造成app記憶體溢位。所以我們解決記憶體洩漏的目的就是防止app發生記憶體溢位。

 

1、新建執行緒引起的Activity記憶體洩漏

例子:

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

package rxnet.zyj.com.myapplication;

 

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;

 

public class Activity6 extends AppCompatActivity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_6);

 

        findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        new Thread(new Runnable() {

            @Override

            public void run() {

                try {<br>                    //模擬耗時操作

                    Thread.sleep( 15000 );

                catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }).start();

 

    }

}

  執行上面的程式碼後,點選finish按鈕,過一會兒發生了記憶體洩漏的問題。

 為什麼Activity6會發生記憶體洩漏?

進入Activity6 介面,然後點選finish按鈕,Activity6銷燬,但是Activity6裡面的執行緒還在執行,匿名內部類Runnable物件引用了Activity6的例項,導致Activity6所佔用的記憶體不能被GC及時回收。

 

 如何改進?

Runnable改為靜態非匿名內部類即可。

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

package rxnet.zyj.com.myapplication;

 

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;

 

public class Activity6 extends AppCompatActivity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_6);

 

        findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        new Thread( new MyRunnable()).start();

 

    }

 

    private static class MyRunnable implements Runnable {

 

        @Override

        public void run() {

            try {

                Thread.sleep( 15000 );

            catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

     

}

  

 2、Activity新增監聽器造成Activity記憶體洩漏

1

2

3

4

5

6

7

8

9

10

11

12

package rxnet.zyj.com.myapplication;

 

import android.app.Activity;

import android.os.Bundle;

 

public class LeakActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        NastyManager.getInstance().addListener(this);

    }

}

  這個是在開發中經常會犯的錯誤,NastyManager.getInstance() 是一個單例,當我們通過 addListener(this) 將 Activity 作為 Listener 和 NastyManager 繫結起來的時候,不好的事情就發生了。

如何改進?

想要修復這樣的 Bug,其實相當簡單,就是在你的 Acitivity 被銷燬的時候,將他和 NastyManager 取消掉繫結就好了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

package rxnet.zyj.com.myapplication;

 

import android.app.Activity;

import android.os.Bundle;

 

public class LeakActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        NastyManager.getInstance().addListener(this);

    }

 

    @Override

    protected void onDestroy() {

        super.onDestroy();

        NastyManager.getInstance().removeListener(this);

    }

}

  

3、Handler 匿名內部類造成記憶體溢位?

先看著一段程式碼

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

package rxnet.zyj.com.myapplication;

 

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.View;

 

public class HandlerActivity extends AppCompatActivity {

 

    private final static int MESSAGECODE = 1 ;

 

    private final Handler handler = new Handler(){

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            Log.d("mmmmmmmm" "handler " + msg.what ) ;

        }

    };

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_handler);

 

        findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        new Thread(new Runnable() {

            @Override

            public void run() {

                handler.sendEmptyMessage( MESSAGECODE ) ;

                try {

                    Thread.sleep( 8000 );

                catch (InterruptedException e) {

                    e.printStackTrace();

                }

                handler.sendEmptyMessage( MESSAGECODE ) ;

            }

        }).start() ;

 

    }

}

  這段程式碼執行起來後,立即點選 finish 按鈕,通過檢測,發現 HandlerActivity 出現了記憶體洩漏。當Activity finish後,延時訊息會繼續存在主執行緒訊息佇列中8秒鐘,然後處理訊息。而該訊息引用了Activity的Handler物件,然後這個Handler又引用了這個Activity。這些引用物件會保持到該訊息被處理完,這樣就導致該Activity物件無法被回收,從而導致了上面說的 Activity洩露。Handler 是個很常用也很有用的類,非同步,執行緒安全等等。如果有下面這樣的程式碼,會發生什麼呢? handler.postDeslayed ,假設 delay 時間是幾個小時… 這意味著什麼?意味著只要 handler 的訊息還沒有被處理結束,它就一直存活著,包含它的 Activity 就跟著活著。我們來想辦法修復它,修復的方案是 WeakReference ,也就是所謂的弱引用。垃圾回收器在回收的時候,是會忽視掉弱引用的,所以包含它的 Activity 會被正常清理掉。

如何避免

  • 使用靜態內部類
  • 使用弱引用

修改後程式碼是這樣的。

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

51

52

53

54

55

56

57

58

59

60

61

62

package rxnet.zyj.com.myapplication;

 

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.View;

 

import java.lang.ref.WeakReference;

 

public class HandlerActivity extends AppCompatActivity {

 

    private final static int MESSAGECODE = 1 ;

    private static Handler handler ;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_handler);

 

        findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        handler = new MyHandler( this ) ;

 

        new Thread(new Runnable() {

            @Override

            public void run() {

                handler.sendEmptyMessage( MESSAGECODE ) ;

                try {

                    Thread.sleep( 8000 );

                catch (InterruptedException e) {

                    e.printStackTrace();

                }

                handler.sendEmptyMessage( MESSAGECODE ) ;

            }

        }).start() ;

 

    }

 

    private static class MyHandler extends Handler {

        WeakReference<HandlerActivity> weakReference ;

 

        public MyHandler(HandlerActivity activity ){

            weakReference  = new WeakReference<HandlerActivity>( activity) ;

        }

 

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            if ( weakReference.get() != null ){

                // update android ui

                Log.d("mmmmmmmm" "handler " + msg.what ) ;

            }

        }

    }

}

  這個Handler已經使用了靜態內部類,並且使用了弱引用。但是這個並沒有完全解決 HandlerActivity 記憶體洩漏的問題,罪魁禍首是執行緒建立的方式出了問題,就像本文的第一個例子一樣。改進的方式,是把Runnable類寫成靜態內部類。

最終完整的程式碼如下:

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

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

package rxnet.zyj.com.myapplication;

 

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.View;

 

import java.lang.ref.WeakReference;

 

public class HandlerActivity extends AppCompatActivity {

 

    private final static int MESSAGECODE = 1 ;

    private static Handler handler ;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_handler);

 

        findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        //建立Handler

        handler = new MyHandler( this ) ;

 

        //建立執行緒並且啟動執行緒

        new Thread( new MyRunnable() ).start();

    }

 

    private static class MyHandler extends Handler {

        WeakReference<HandlerActivity> weakReference ;

 

        public MyHandler(HandlerActivity activity ){

            weakReference  = new WeakReference<HandlerActivity>( activity) ;

        }

 

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            if ( weakReference.get() != null ){

                // update android ui

                Log.d("mmmmmmmm" "handler " + msg.what ) ;

            }

        }

    }

 

    private static class MyRunnable implements Runnable {

 

        @Override

        public void run() {

            handler.sendEmptyMessage( MESSAGECODE ) ;

            try {

                Thread.sleep( 8000 );

            catch (InterruptedException e) {

                e.printStackTrace();

            }

            handler.sendEmptyMessage( MESSAGECODE ) ;

        }

    }

}

  等等,還沒完呢?

上面這個程式碼已經有效的解決了Handler,Runnable 引用Activity例項從而導致記憶體洩漏的問題,但是這不夠。因為記憶體洩漏的核心原因就是這個某個物件應該被系統回收記憶體的時候,卻被其他物件引用,造成該記憶體無法回收。所以我們在寫程式碼的時候,要始終繃著這個弦。再回到上面這個問題,噹噹前Activity呼叫finish銷燬的時候,在這個Activity裡面所有執行緒是不是應該在OnDestory()方法裡,取消執行緒。當然是否取消非同步任務,要看專案具體的需求,比如在Activity銷燬的時候,啟動一個執行緒,非同步寫log日誌到本地磁碟,針對這個需求卻需要在OnDestory()方法裡開啟執行緒。所以根據當前環境做出選擇才是正解。

所以我們還可以修改程式碼為:在onDestroy() 裡面移除所有的callback 和 Message 。

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

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

package rxnet.zyj.com.myapplication;

 

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.View;

 

import java.lang.ref.WeakReference;

 

public class HandlerActivity extends AppCompatActivity {

 

    private final static int MESSAGECODE = 1 ;

    private static Handler handler ;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_handler);

 

        findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        //建立Handler

        handler = new MyHandler( this ) ;

 

        //建立執行緒並且啟動執行緒

        new Thread( new MyRunnable() ).start();

    }

 

    private static class MyHandler extends Handler {

        WeakReference<HandlerActivity> weakReference ;

 

        public MyHandler(HandlerActivity activity ){

            weakReference  = new WeakReference<HandlerActivity>( activity) ;

        }

 

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            if ( weakReference.get() != null ){

                // update android ui

                Log.d("mmmmmmmm" "handler " + msg.what ) ;

            }

        }

    }

 

    private static class MyRunnable implements Runnable {

 

        @Override

        public void run() {

            handler.sendEmptyMessage( MESSAGECODE ) ;

            try {

                Thread.sleep( 8000 );

            catch (InterruptedException e) {

                e.printStackTrace();

            }

            handler.sendEmptyMessage( MESSAGECODE ) ;

        }

    }

 

    @Override

    protected void onDestroy() {

        super.onDestroy();

 

       //如果引數為null的話,會將所有的Callbacks和Messages全部清除掉。

        handler.removeCallbacksAndMessages( null );

    }

}

  

 

 

4、AsyncTask造成記憶體洩漏

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

package rxnet.zyj.com.myapplication;

 

import android.os.AsyncTask;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

 

public class Activity2 extends AppCompatActivity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_2);

 

        findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

 

        new AsyncTask<String,Integer,String>(){

 

            @Override

            protected String doInBackground(String... params) {

                try {

                    Thread.sleep( 6000 );

                catch (InterruptedException e) {

                }

                return "ssss";

            }

 

            @Override

            protected void onPostExecute(String s) {

                super.onPostExecute(s);

                Log.d( "mmmmmm activity2 " "" + s ) ;

            }

 

        }.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "" ) ;

         

    }

}

  為什麼?

上面程式碼在activity中建立了一個匿名類AsyncTask,匿名類和非靜態內部類相同,會持有外部類物件,這裡也就是activity,因此如果你在Activity裡宣告且例項化一個匿名的AsyncTask物件,則可能會發生記憶體洩漏,如果這個執行緒在Activity銷燬後還一直在後臺執行,那這個執行緒會繼續持有這個Activity的引用從而不會被GC回收,直到執行緒執行完成。

   怎麼解決?

  •  自定義靜態AsyncTask類
  • AsyncTask的週期和Activity週期保持一致。也就是在Activity生命週期結束時要將AsyncTask cancel掉。

 

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

51

package rxnet.zyj.com.myapplication;

 

import android.os.AsyncTask;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;

 

public class AsyncTaskActivity extends AppCompatActivity {

 

    private static MyTask myTask ;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_asynctask);

 

        findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        myTask = new MyTask() ;

        myTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "") ;

 

    }

 

    private static class MyTask extends AsyncTask{

 

        @Override

        protected Object doInBackground(Object[] params) {

            try {

                //模擬耗時操作

                Thread.sleep( 15000 );

            catch (InterruptedException e) {

                e.printStackTrace();

            }

            return "";

        }

    }

 

    @Override

    protected void onDestroy() {

        super.onDestroy();

 

        //取消非同步任務

        if ( myTask != null ){

            myTask.cancel(true ) ;

        }

    }

}

 

5、Timer Tasks 造成記憶體洩漏

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

package rxnet.zyj.com.myapplication;

 

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.view.View;

 

import java.util.Timer;

import java.util.TimerTask;

 

public class TimerActivity extends AppCompatActivity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_2);

 

        findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        //開始定時任務

        timer();

    }

 

    void timer(){

        new Timer().schedule(new TimerTask() {

            @Override

            public void run() {

                while(true);

            }

        },1000 );  // 1秒後啟動一個任務

    }

}

  

  為什麼? 

這裡記憶體洩漏在於Timer和TimerTask沒有進行Cancel,從而導致Timer和TimerTask一直引用外部類Activity。

  怎麼解決? 

  • 在適當的時機進行Cancel。
  • TimerTask用靜態內部類

   注意:在網上看到一些資料說,解決TimerTask記憶體洩漏可以使用在適當的時機進行Cancel。經過測試,證明單單使用在適當的時機進行Cancel , 還是有記憶體洩漏的問題。所以一定要用靜態內部類配合使用。

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

51

52

53

54

55

package rxnet.zyj.com.myapplication;

 

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.View;

 

import java.util.Timer;

import java.util.TimerTask;

 

public class TimerActivity extends AppCompatActivity {

 

    private TimerTask timerTask ;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_2);

 

        findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                finish();

            }

        });

 

        //開始定時任務

        timer();

    }

 

    void timer(){

        timerTask = new MyTimerTask() ;

        new Timer().schedule( timerTask ,1000 );  // 1秒後啟動一個任務

    }

 

    private static class MyTimerTask extends TimerTask{

 

        @Override

        public void run() {

            while(true){

                Log.d( "ttttttttt" "timerTask" ) ;

            }

        }

    }

 

    @Override

    protected void onDestroy() {

        super.onDestroy();

 

        //取消定時任務

        if ( timerTask != null ){

            timerTask.cancel() ;

        }

    }

}

  

 參考資料

 深入Android記憶體洩露

轉自:http://www.cnblogs.com/zhaoyanjun/p/5981386.html