天天看點

Android性能優化--記憶體洩露

概述:

記憶體洩露是指當一個對象不再使用的時候,本該被回收,而被其他對象所持有導緻該對象無法被GC回收,這種導緻了本該被回收的對象不能被回收而停留在堆記憶體中,就産生了記憶體洩漏。

記憶體洩漏與記憶體溢出的差別

記憶體洩漏(Memory Leak)

程序中某些對象已經沒有使用的價值了,但是他們卻還可以直接或間接地被引用到GC Root導緻無法回收。當記憶體洩漏過多的時候,再加上應用本身占用的記憶體,最終可能就會導緻記憶體溢出OOM。

記憶體溢出(OOM)

當應用的heap資源超過了Dalvik虛拟機配置設定的記憶體就會記憶體溢出

記憶體洩漏帶來的影響

應用卡頓

應用異常(oom)

開發過程中經常遇到的:

1.單例造成的記憶體洩露

錯誤寫法:

Single single=Single.getInstance(this);

當調用getInstance時,如果傳入的context是Activity的context,隻有這個單例沒有被釋放,那麼這個activity就會一直持有這個引用,直到程序退出才會釋放。

public class Single {

    private static Single instance;

    private Context context;

    private Single(Context context) {
        this.context = context;
    }

    public static Single getInstance(Context context) {
        if (instance == null) {
            synchronized (Single.class) {
                if (instance == null)
                    instance = new Single(context);
            }
        }
        return instance;
    }
}
           

改進後寫法:

Single single=Single.getInstance(getApplicationContext());

盡量使用Application的Context就不要使用Activity的Content,Application的生命周期伴随着整個程序的周期。

2.資源未關閉造成的記憶體洩漏

錯誤寫法:

對于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者登出,否則這些資源将不會被回收,造成記憶體洩漏。

改進後寫法:

在Activity銷毀時及時關閉或者登出。

3.非靜态内部類建立靜态執行個體造成的記憶體洩漏

錯誤寫法:

private static NoStaticMethod method;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (method!=null)
            method=new NoStaticMethod();
    }

    class NoStaticMethod{

    }
           

改進後寫法:

将非靜态内部類修改為靜态内部類。(靜态内部類不會隐式持有外部類)

4.Handelr造成的記憶體洩露

錯誤寫法:

mHandler是Handler的非靜态匿名内部類的執行個體,是以它持有外部類Activity的引用,我們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那麼當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler執行個體的引用,mHandler又持有Activity的引用,是以導緻該Activity的記憶體資源無法及時回收,引發記憶體洩漏。

private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
           

改進後寫法:

建立一個靜态Handler内部類,然後對Handler持有的對象使用弱引用,這樣在回收時也可以回收Handler持有的對象,這樣雖然避免了Activity洩漏,不過Looper線程的消息隊列中還是可能會有待處理的消息,是以我們在Activity的Destroy時或者Stop時應該移除消息隊列中的消息。

public static class MyHandler extends Handler {
        private WeakReference<MainActivity> weakReference;

        public MyHandler(MainActivity activity) {
            weakReference = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = weakReference.get();
            if (activity != null) {

            }
        }
    }
           
@Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }
           

5.線程造成的記憶體洩漏

錯誤寫法:

異步任務和Runnable都是一個匿名内部類,是以它們對目前Activity都有一個隐式引用。如果Activity在銷毀之前,任務還未完成, 那麼将導緻Activity的記憶體資源無法回收,造成記憶體洩漏。

new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    Log.d(TAG, "--------------");
                }
            }
        });

        new AsyncTask<Void,Void,Void>(){

            @Override
            protected Void doInBackground(Void... params) {
                return null;
            }
        }.execute();
           

改進後寫法:

使用 靜态内部類,避免了Activity的記憶體資源洩漏,當然在Activity銷毀時候也應該取消相應的任務AsyncTask::cancel(),避免任務在背景執行浪費資源。

private static class MyThread extends Thread {
        private WeakReference<MainActivity> mWeak;

        public MyThread(MainActivity activity) {
            mWeak = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void run() {
            super.run();
            while (true) {
                if (mWeak == null) return;
                if (mWeak.get() != null) {
                    Log.d(TAG, "--------------");
                }

            }
        }
    }
           

6.注冊了系統的服務,但onDestory未登出

AS檢視記憶體洩露

1.我們故意寫一個記憶體洩露的代碼,即:

Single.getInstance(getApplicationContext());

運作程式并退出(finish)

Android性能優化--記憶體洩露
Android性能優化--記憶體洩露

進行分析

Android性能優化--記憶體洩露
Android性能優化--記憶體洩露

我們可以看出是Sinle類出現了問題,原因我們也知道就是因為單例的問題,現在我們改一下代碼:

Single.getInstance(getApplicationContext());

再看:

Android性能優化--記憶體洩露

果然沒有問題了

小結

以上這些隻是一些常見的造成記憶體洩露的原因,在實踐的路上我們還任重而道遠,我們可以通過AndroidStudio自帶的工具來檢查是否存在記憶體洩露,也可以通過添加第三方庫來檢查 。