轉載於:http://android.jobbole.com/84810/
在上一篇 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
泄漏的案例我們已經都走過一遍了,其他都大同小異。建議日後遇到類似的情況時,就使用相應的解決方法。內存泄漏只要發生過一次,通過詳細的檢查,很容易解決並防範於未然。
是時候做最佳實踐者了!