1. 程式人生 > >Android記憶體洩露案例和解析

Android記憶體洩露案例和解析

使用過長物件生命週期

靜態引用

靜態變數儲存在方法區,在類載入的時候被載入,除非類被解除安裝了,或者他會一直存活,直到App程序銷燬,也就是說靜態變數的生命期等於整個程序的生命期。

    private Context mContext;
    @OnClick(R.id.btnStaticTest)
    void testClick() {
        finish();
        Toast.makeText(mContext, "mContext洩漏", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected
void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = this; }

mContext持有了MainActivity的引用,因為mContext生命期長於MainActivity所以造成記憶體洩漏

單例

單例整個程式只儲存一個物件,因為其也要通過靜態變數來實現,所以他的生命期其實和靜態變數一致也是整個程序的存活期。如果在內部持有了一個生命期較短物件的引用,而且沒有手動釋放的話,那麼也會造成記憶體洩漏。

public class SingleInstance {
    private Context mContext;
    private static SingleInstance instance = new SingleInstance();

    public static SingleInstance getInstance() {
        return instance;
    }

    public void setup(Context context) {
        this.mContext = context
    }
}

解決辦法

使用getApplicationContext代替context

例如在用Toast的時候,我們會盡量選用getApplicationContext(),就是利用Application的上下文生命期會貫穿整個App。

 Toast.makeText(getApplicationContext(), "mContext洩漏", Toast.LENGTH_SHORT).show();

有的使用,我們無法拿到getApplicationContext()因為這個方法是在ContextWrapper這個類裡面,Activity繼承了這個類他自然可以拿到。可以通過下面這種方式去獲取:

public class XApplication extends Application {
    private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = this;
        LeakCanary.install(this);
    }
    public static Context getContext() {
        return mContext;
    }
}

使用的時候XApplication.getContext()拿到Context,因為Application和靜態變數生命期一致,所以不會造成記憶體洩漏,如果Application 在程式切換到後臺被後臺殺死,再切換回來重建肯定會執行他的onCreate()方法,再次給mContext賦值,所以不用擔心XApplication.getContext()會報空指標異常。

手動控制釋放

例如在Activity中會用到Activity的上下文mContext,那麼就可以在onDestroy()釋放引用

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SingleInstance.getInstance().setContext(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        SingleInstance.getInstance().setContext(null);
    }

事實上,這種情況還是經常碰到的,例如註冊和反註冊廣播,還有使用EventBus或者otto的時候

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

EventBus.getDefault()的實現

    static volatile EventBus defaultInstance;
    /** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

很明顯看出他是一個懶漢式(Lazy Loading)的單例,通過unregister方法實現我們所說的手動釋放。

使用非靜態內部類

非靜態內部類包括以下三種
- 成員內部類
- 區域性內部類
- 匿名內部類
非靜態內部類會直接持有外部類的引用,如果非靜態內部類不被回收,他的外部類也不會被回收從而造成記憶體洩漏
例如:

成員內部類

    class A {
    }

    private static A a;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        a = new A();
    }

雖然class A為空,但是因為他存在一個靜態例項,這會導致A的類例項不會被GC回收,從而導致外部類MainActivity不會被回收。

匿名內部類

在點選事件的匿名內部類中,通過Handler.postDelayed()執行一個延遲任務,任務執行在Runnable匿名內部類中實現。

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "MainActivity";
    public final static int DO_WORK = 1;


    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == DO_WORK) {
                //do something
            }
            return false;
        }
    });
    @BindView(R.id.btnHandler)
    Button btnHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        btnHandler.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MainActivity.this.finish();
                mHandler.sendEmptyMessage(DO_WORK);
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, "btnHandler", Toast.LENGTH_SHORT).show();
                    }
                }, 15 * 1000);
            }
        });
    }
}

這裡存在2處記憶體溢位:
1. finish()掉這個Activity,任務延遲15s,在延後的這15S過程中,造成了15S的記憶體洩漏。
2. 在public boolean handleMessage(Message msg)收到傳送過來的訊息,執行一些任務

修改方式:有兩種方案:

  1. 手動取消 。 在關閉Activity時(finish/onStop等函式中),取消還在排隊的Message:
    mHandler.removeCallbacksAndMessages(null);

  2. 使用弱引用。 使用WeakReference截斷StrongReference。問題的癥結既然是內部類持有外部類物件的引用,那我不用內部類就行了,直接使用靜態成員類。但mHandler又需要與Activity物件互動,那就來個WeakReference,指向外部Activity物件。

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "MainActivity";
    public final static int DO_WORK = 1;

    private MyHandler mHandler;
    @BindView(R.id.btnHandler)
    Button btnHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        ButterKnife.bind(this);
        btnHandler.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MainActivity.this.finish();
                mHandler.sendEmptyMessage(DO_WORK);
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, "btnHandler", Toast.LENGTH_SHORT).show();
                    }
                }, 15 * 1000);
            }
        });
    }

//方案一
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
        if (mHandler != null) {
            mHandler = null;
        }
    }
//方案二
    private static class MyHandler extends Handler {
        private WeakReference<AppCompatActivity> wr;

        public MyHandler(AppCompatActivity aty) {
            wr = new WeakReference<>(aty);
        }


        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (wr.get() != null) {
                if (msg.what == DO_WORK) {
                //dosomething
                }
            }
        }
    }


}

耗時操作

直接開啟子執行緒,匿名內部類

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "MainActivity";
    private static int count = 0;

    @BindView(R.id.btnHandler)
    Button btnHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count++;
                    Log.d(TAG, "run: " + count);
                }
            }
        }).start();
    }
}

根據我們剛才分析的,使用弱引用來解除,Thread對Activity持有的引用

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "MainActivity";
    private static int count = 0;

    @BindView(R.id.btnHandler)
    Button btnHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        new MyThread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count++;
                    Log.d(TAG, "run: " + count);
                }
            }
        }).start();
    }

    private class MyThread extends Thread {
        private WeakReference<Runnable> wr;

        public MyThread(Runnable runnable) {
            super(runnable);
            wr = new WeakReference<Runnable>(runnable);
        }

        @Override
        public synchronized void start() {
            if (wr.get() == null) {
                return;
            }
            super.start();
        }
    }
}

但是執行後發現,這樣做沒有作用。因為我們開始建立的Thread的時候有兩個匿名內部類,修改後Runnable還是持有Activity的引用。
這裡我們可以利用,執行緒池的特性在Activity停止的時候取消任務

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "MainActivity";
    private static int count = 0;

    @BindView(R.id.btnHandler)
    Button btnHandler;
    private Future future;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        Callable<Boolean> callable = new Callable() {
            @Override
            public Object call() throws Exception {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        return false;
                    }
                    count++;
                    Log.d(TAG, "run: " + count);

                }
            }
        };
        ExecutorService es = Executors.newSingleThreadExecutor();
        future = es.submit(callable);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        future.cancel(true);
    }
}

注意:不要使用Runnable取代Callable,這並不能中斷任務執行,下面是錯誤的範例

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                {
                    while (true) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count++;
                        Log.d(TAG, "run: " + count);
                    }
                }
            }
        };
        ExecutorService es = Executors.newSingleThreadExecutor();
        future = es.submit(runnable);

這樣寫 並不能取消執行