在上一篇 Android記憶體洩漏的八種可能(上)中,我們討論了八種容易發生記憶體洩漏的程式碼。其中,尤其嚴重的是洩漏Activity
物件,因為它佔用了大量系統記憶體。不管記憶體洩漏的程式碼表現形式如何,其核心問題在於:
在Activity生命週期之外仍持有其引用。
幸運的是,一旦洩漏發生且被定位到了,修復方法是相當簡單的。
Static Actitivities
這種洩漏
1 2 3 4 5 |
private static MainActivity activity; void setStaticActivity() { activity = this; } |
構造靜態變數持有Activity
物件很容易造成記憶體洩漏,因為靜態變數是全域性存在的,所以當MainActivity
生命週期結束時,引用仍被持有。這種寫法開發者是有理由來使用的,所以我們需要正確的釋放引用讓垃圾回收機制在它被銷燬的同時將其回收。
Android提供了特殊的Set
類 https://developer.android.com/reference/java/lang/ref/package-summary.html#classes 允許開發者控制引用的“強度”。Activity
物件洩漏是由於需要被銷燬時,仍然被強引用著,只要強引用存在就無法被回收。
可以用弱引用代替強引用。
https://developer.android.com/reference/java/lang/ref/WeakReference.html.
弱引用不會阻止物件的記憶體釋放,所以即使有弱引用的存在,該物件也可以被回收。
1 2 3 4 5 |
private static WeakReference<MainActivity> activityReference; void setStaticActivity() { activityReference = new WeakReference<MainActivity>(this); } |
Static Views
靜態變數持有View
private static View view;
1 2 3 |
void setStaticView() { view = findViewById(R.id.sv_button); } |
由於View
持有其宿主Activity
的引用,導致的問題與Activity
一樣嚴重。弱引用是個有效的解決方法,然而還有另一種方法是在生命週期結束時清除引用,Activity#onDestory()
方法就很適合把引用置空。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private static View view; @Override public void onDestroy() { super.onDestroy(); if (view != null) { unsetStaticView(); } } void unsetStaticView() { view = null; } |
Inner Class
這種洩漏
1 2 3 4 5 6 7 |
private static Object inner; void createInnerClass() { class InnerClass { } inner = new InnerClass(); } |
於上述兩種情況相似,開發者必須注意少用非靜態內部類,因為靜態內部類持有外部類的隱式引用,容易導致意料之外的洩漏。然而內部類可以訪問外部類的私有變數,只要我們注意引用的生命週期,就可以避免意外的發生。
避免靜態變數
這樣持有內部類的成員變數是可以的。
1 2 3 4 5 6 7 |
private Object inner; void createInnerClass() { class InnerClass { } inner = new InnerClass(); } |
Anonymous Classes
前面我們看到的都是持有全域性生命週期的靜態成員變數引起的,直接或間接通過鏈式引用Activity
導致的洩漏。這次我們用AsyncTask
1 2 3 4 5 6 7 |
void startAsyncTask() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { while(true); } }.execute(); } |
Handler
1 2 3 4 5 6 7 8 9 10 11 |
void createHandler() { new Handler() { @Override public void handleMessage(Message message) { super.handleMessage(message); } }.postDelayed(new Runnable() { @Override public void run() { while(true); } }, Long.MAX_VALUE >> 1); } |
Thread
1 2 3 4 5 6 7 8 |
void scheduleTimer() { new Timer().schedule(new TimerTask() { @Override public void run() { while(true); } }, Long.MAX_VALUE >> 1); } |
全部都是因為匿名類導致的。匿名類是特殊的內部類——寫法更為簡潔。當需要一次性的特殊子類時,Java提供的語法糖能讓表示式最少化。這種很贊很偷懶的寫法容易導致洩漏。正如使用內部類一樣,只要不跨越生命週期,內部類是完全沒問題的。但是,這些類是用於產生後臺執行緒的,這些Java執行緒是全域性的,而且持有建立者的引用(即匿名類的引用),而匿名類又持有外部類的引用。執行緒是可能長時間執行的,所以一直持有Activity
的引用導致當銷燬時無法回收。
這次我們不能通過移除靜態成員變數解決,因為執行緒是於應用生命週期相關的。為了避免洩漏,我們必須捨棄簡潔偷懶的寫法,把子類宣告為靜態內部類。
靜態內部類不持有外部類的引用,打破了鏈式引用。
所以對於AsyncTask
1 2 3 4 5 6 7 8 9 |
private static class NimbleTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { while(true); } } void startAsyncTask() { new NimbleTask().execute(); } |
Handler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private static class NimbleHandler extends Handler { @Override public void handleMessage(Message message) { super.handleMessage(message); } } private static class NimbleRunnable implements Runnable { @Override public void run() { while(true); } } void createHandler() { new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE >> 1); } |
1 2 3 4 5 6 7 8 9 |
private static class NimbleTimerTask extends TimerTask { @Override public void run() { while(true); } } void scheduleTimer() { new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1); } |
但是,如果你堅持使用匿名類,只要在生命週期結束時中斷執行緒就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private Thread thread; @Override public void onDestroy() { super.onDestroy(); if (thread != null) { thread.interrupt(); } } void spawnThread() { thread = new Thread() { @Override public void run() { while (!isInterrupted()) { } } } thread.start(); } |
Sensor Manager
這種洩漏
1 2 3 4 5 |
void registerListener() { SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL); sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST); } |
使用Android系統服務不當容易導致洩漏,為了Activity
與服務互動,我們把Activity
作為監聽器,引用鏈在傳遞事件和回撥中形成了。只要Activity
維持註冊監聽狀態,引用就會一直持有,記憶體就不會被釋放。
在Activity結束時登出監聽器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private SensorManager sensorManager; private Sensor sensor; @Override public void onDestroy() { super.onDestroy(); if (sensor != null) { unregisterListener(); } } void unregisterListener() { sensorManager.unregisterListener(this, sensor); } |
總結
Activity
洩漏的案例我們已經都走過一遍了,其他都大同小異。建議日後遇到類似的情況時,就使用相應的解決方法。記憶體洩漏只要發生過一次,通過詳細的檢查,很容易解決並防範於未然。
是時候做最佳實踐者了!