先來看一段使用Thread的代碼侧漓,簡單而常見
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 開啟線程缺前,啟動循環(huán)任務
new Thread() {
@Override
public void run() {
while(true) {
SystemClock.sleep(1000);
}
}
}.start();
}
}
Ok蛀醉,想一下,如果用戶旋轉了屏幕衅码,這時會發(fā)生什么拯刁?
正常情況下,系統(tǒng)新創(chuàng)建一個橫屏的Activity實例逝段,銷毀舊的Activity實例并釋放相關資源垛玻。
但在這里割捅,舊的Activity會長久的存在,直到發(fā)生OOM夭谤。為什么棺牧?
首先,它內(nèi)部匿名的Thread實例會長久運行朗儒,不會被系統(tǒng)GC回收颊乘。Threads在Java中是GC roots,意味著 Dalvik Virtual Machine (DVM) 會為所有活躍的threads在運行時系統(tǒng)中保持一個硬引用醉锄,這會導致threads一直處于運行狀態(tài)乏悄,垃圾收集器將永遠不可能回收它。
其次恳不,非靜態(tài)內(nèi)部類會持有外部類的引用檩小。Thread會長久地持有Activity的引用,使得系統(tǒng)無法回收Activity和它所關聯(lián)的資源和視圖烟勋。
針對后者规求,MainActivity的內(nèi)存泄漏問題,只需把非靜態(tài)的Thread匿名類定義成靜態(tài)的內(nèi)部類就行了卵惦。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleTwo();
}
private void exampleTwo() {
new MyThread().start();
}
private static class MyThread extends Thread {
@Override
public void run() {
while(true) {
SystemClock.sleep(1000);
}
}
}
}
現(xiàn)在新創(chuàng)建的Thread不會持有MainActivity的一個隱式引用阻肿,當手機屏幕旋轉時不會阻止垃圾回收器對舊MainActivity的回收工作。
而在第一個問題中沮尿,一旦有一個新的Activity創(chuàng)建丛塌,那么就有一個Thread永遠得不到清理回收,發(fā)生內(nèi)存泄漏畜疾。
出于這個原因赴邻,我們應當為thread添加一個結束的邏輯,如下代碼所示
public class MainActivity extends Activity {
private MyThread mThread;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleThree();
}
privatevoid exampleThree() {
mThread = new MyThread();
mThread.start();
}
/**
* Static inner classes don't hold implicit references to their
* enclosing class, so the Activity instance won't be leaked across
* configuration changes.
*/
private static class MyThread extends Thread {
private boolean mRunning = false;
@Override
public void run() {
mRunning = true;
while(mRunning) {
SystemClock.sleep(1000);
}
}
public void close() {
mRunning = false;
}
}
@Override
protectedvoid onDestroy() {
super.onDestroy();
mThread.close();
}
}
在上述的代碼中啡捶,當Activity結束銷毀時在onDestroy()方法中結束了新創(chuàng)建的線程姥敛,保證了thread不會發(fā)生泄漏。但是如果你想在手機配置發(fā)生改變時届慈,保持原有的線程而不重新創(chuàng)建的話徒溪,你可以考慮使用fragment來保留原有的線程,以備下一次使用金顿。
結論
-
使用靜態(tài)內(nèi)部類/匿名類,不要使用非靜態(tài)內(nèi)部類/匿名類鲤桥。非靜態(tài)內(nèi)部類/匿名類會隱式的持有外部類的引用揍拆,外部類就有可能發(fā)生泄漏。而靜態(tài)內(nèi)部類/匿名類不會隱式的持有外部類引用茶凳,外部類會以正常的方式回收嫂拴,如果你想在靜態(tài)內(nèi)部類/匿名類中使用外部類的屬性或方法時播揪,可以顯式的持有一個弱引用。
</br> -
不要以為Java永遠會幫你清理回收正在運行的threads筒狠。在上面的代碼中猪狈,我們很容易誤以為當Activity結束銷毀時會幫我們把正在運行的thread也結束回收掉,但事情永遠不是這樣的辩恼!Java threads會一直存在雇庙,只有當線程運行完成或被殺死掉,線程才會被回收灶伊。所以我們應該養(yǎng)成為thread設置退出邏輯條件的習慣疆前。
</br> - 適當?shù)目紤]下是否應該使用線程。Android應用框架設計了許多的類來簡化執(zhí)行后臺任務聘萨,我們可以使用與Activity生命周期相關聯(lián)的Loaders來執(zhí)行簡短的后臺查詢?nèi)蝿罩窠贰H绻粋€線程不依賴與Activity,我們還可以使用Service來執(zhí)行后臺任務米辐,然后用BroadcastReceiver來向Activity報告結果胸完。另外需要注意的是本文討論的thread同樣使用于AsyncTasks,AsyncTask同樣也是由線程來實現(xiàn)翘贮,只不過使用了Java5.0新增并發(fā)包中的功能赊窥,但同時需要注意的是根據(jù)官方文檔所說,AsyncTask適用于執(zhí)行一些簡短的后臺任務