Android Dalvik GC 採用 Mark and Sweep 處理機制,mark 會 traverse 整個 object tree,並將所有未被其他物件參考到的物件,標示為 unused,未被使用的物件,就可以被 GC。
APP 執行緒潛藏 memory leak 的風險,只有在 thread 終止時,他們使用到的物件才會被GC。
worker thread
當執行緒在執行時,Thread 物件本身會變成 GC root,他所參考到的物件都是可以到達的。而 Thread 與 Runnable instance 保存了只向其他物件的 references,必須等到 Thread 結束,才能回收那些物件。當 method return 後,方法裡面所建立的物件就能被 GC,除非該 method 將物件回傳給他的呼叫者,讓其他 method 可以使用該物件。
- 內部類別
inner class 是 outer class 的成員,可以存取外部類別的其他成員,因此內部類別隱含地參考到外部類別,被定義為內部類別的執行緒,保存了指向外部類別的 reference,只要執行緒還在執行,外部類別就不會被標示為可以被 GC。
定義為 local class 與 anonymous inner class 的執行緒跟外部類別的關係,與內部類別相同,在執行期間,可以讓外部類別從 GC Root 到達。
以下範例中,只要 SampleThread 還在執行中,在 Outer 類別裡的任何物件,都必須保存在記憶體中,伴隨著內部的 SampleThread 類別裡的物件。
public class Outer {
public void sampleMethd() {
SampleThread sampleThread = new SampleThread();
sampleThread.start();
}
private class SampleThread extends Thread {
public void run() {
Object sampleObject = new Object();
}
}
}
- 靜態內部類別
static inner class 是外部類別 instance 的成員,定義在靜態內部類別的執行緒保存了指向外部物件的類別的 member reference,而不是直接只向外部物件,所以一旦指向外部物件的其他參考消失,就可以被 GC。
public class Outer {
public void sampleMethd() {
SampleThread sampleThread = new SampleThread();
sampleThread.start();
}
private static class SampleThread extends Thread {
public void run() {
Object sampleObject = new Object();
}
}
}
在大多數情況下,我們會想要將 Thread 與 Runnable 分開,如果建立了新的 Runnable 作為內部類別,在執行期間會保存指向外部類別的 reference。
public class Outer {
public void sampleMethd() {
SampleThread sampleThread = new SampleThread(new Runnable () {
@Override
public void run() {
Object sampleObject = new Object();
}
});
sampleThread.start();
}
private static class SampleThread extends Thread {
public SampleThread(Runnable runnable) {
super(runnable);
}
}
}
執行緒溝通
執行緒跟訊息傳遞機制是 memory leak 的潛在根源。對收到訊息物件的 thread 來說,他必須使用 MessageQueue 來存放待處理的訊息,使用 Looper 派送訊息,使用 Handler 處理訊息,這些物件都會被 worker thread 參考。然而 Handler 是 memory leak 發生的主因,因為他是透過一系列物件,被 consumer thread 參考到的, Handler 跟他所參考到的物件,必須等到 thread 終止時,才能被 GC。
- 傳送資訊訊息
資料物件能以多種方式被傳遞,選擇哪一種實作方式,會決定 memory leak的規模。
public class Outer {
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
}
}
public void doSend() {
Message message = mHandler.obtainMessage();
message.obj = new SampleObject();
mHandler.sendMessageDelayed(message, 60*10000);
}
}
- 發布任務訊息
Handler 與 Runnable 都會參考到 Outer,增加了 memory leak 的風險
public class Outer {
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
}
}
public void doPost() {
mHandler.post(new Runnable() {
public void run() {
}
});
}
}
如何避免 memory leak
使用靜態內部類別 不使用具有外部物件參考的巢狀類別,改用靜態內部類別。因為他們只會參考到痊癒的 class object,而不是 instance object。
使用 WeakReference
WeakReference 可避免 reference counting。
public class Outer {
private int mField;
pirvate static class SampleThread extends Thread {
private final WeakReference<Outer> mOuter;
SampleThread(Outer outer) {
mOuter = new WeakReference<Outer>(outer);
}
public void run() {
if(mOuter.get() !=null ) {
mOuter.get().mField=1;
}
}
}
}
停止 Work Thread
保留 Work Thread
以 retain 的方式,保留舊物件給新的 Activity 使用。
- 清除 Message Queue
removeCallbacks(Runnable r)
removeCallbacks(Runnable r, Object token)
removeCallbacksAndMessages(Object token)
removeMessages(int what)
removeMessages(int what, Object object)
Android 管理執行緒的生命週期
每個 APP 必須管理他們建立的執行緒,避免發生 memory leak。
定義與啟動
定義與啟動 Thread 的方式會影響記憶體洩漏的風險與規模。以下是幾種建立執行緒的方式:
匿名內部類別:容易實作,但保有只向外部類別的參考,可能會產生記憶體洩漏
public class AnyObject { @UiThread public void anyMethod() { new Thread { public void run() { doLongRunningTask(); } }.start(); } }
公用執行緒:將執行緒定義為獨立的類別
class MyThread extends Thread { public void run() { doLongRunningTask(); } } public class AnyObject { private MyThread myThread; @UiThread public void anyMethod() { myThread = new MyThread(); myThread.start(); } }
靜態內部類別執行緒:在類別物件上定義執行緒,而不是 instance
public class AnyObject { static class MyThread extends Thread { public void run() { doLongRunningTask(); } }; private MyThread myThread; @UiThread public void anyMethod() { myThread = new MyThread(); myThread.start(); } }
保留
因為執行緒的生命週期可能會超過啟動它的元件,或是 Activity 因為設備旋轉而重新產生了新的 Activity,這時候的背景執行緒只有舊的 Activity 才知道,也需要重新啟動一次執行緒。因此我們需要一個保留工作執行緒的方法。
public Object onRetainNonConfigurationInstance() 在組態改變前由平台呼叫,可以回傳想要保留並傳遞給新 Activity 的物件
public Object getLastNonConfigurationInstance() 組態改變後,可在 onCreate 或 onStart 中被呼叫,取回先前紀錄的物件
在 Activity 中保留 thread 的範例:
public class ThreadRetainActivity extends Activity { // worker thread 宣告為靜態內部類別 private static class MyThread extends Thread { private ThreadRetainActivity mActivity; public MyThread(ThreadRetainActivity activity) { mActivity = activity; } // attach 用來記錄 Activity 的 reference private void attach(ThreadRetainActivity activity) { mActivity = activity; } @Override public void run() { final String text = getTextFromNetwork(); mActivity.setText(text); } // Long operation private String getTextFromNetwork() { // Simulate network operation SystemClock.sleep(5000); return "Text from network"; } } private static MyThread t; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_retain_thread); textView = (TextView) findViewById(R.id.text_retain); // 查詢有沒有先前紀錄的物件 Object retainedObject = getLastNonConfigurationInstance(); if (retainedObject != null) { t = (MyThread) retainedObject; // 修改 worker thread 裡面記錄的 activity reference t.attach(this); } } @Override public Object onRetainNonConfigurationInstance() { // 在 worker thread 還在處理中時,回傳 t if (t != null && t.isAlive()) { return t; } return null; } public void onStartThread(View v) { // 啟動 thread t = new MyThread(this); t.start(); } private void setText(final String text) { runOnUiThread(new Runnable() { @Override public void run() { textView.setText(text); } }); } }
在 Fragement 保留 thread 的方法:Fragment 通常用來處理部分 UI,可以將保留 thread instance 的責任轉交給 Fragment。
public class ThreadRetainWithFragmentActivity extends Activity { private static final String TAG = "ThreadRetainActivity"; private static final String KEY_TEXT = "key_text"; private ThreadFragment mThreadFragment; private TextView mTextView; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_retain_thread); mTextView = (TextView) findViewById(R.id.text_retain); // Activity 第一次啟動,要產生 Fragment FragmentManager manager = getFragmentManager(); mThreadFragment = (ThreadFragment) manager.findFragmentByTag("threadfragment"); if (mThreadFragment == null) { FragmentTransaction transaction = manager.beginTransaction(); mThreadFragment = new ThreadFragment(); transaction.add(mThreadFragment, "threadfragment"); transaction.commit(); } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mTextView.setText(savedInstanceState.getString(KEY_TEXT)); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(KEY_TEXT, (String)mTextView.getText()); } // Method called to start a worker thread public void onStartThread(View v) { // 啟動 worker thread mThreadFragment.execute(); } public void setText(final String text) { // 讓 fragment 回頭更新 UI runOnUiThread(new Runnable() { @Override public void run() { mTextView.setText(text); } }); } } public class ThreadFragment extends Fragment { // 保留上層的 Activity reference private ThreadRetainWithFragmentActivity mActivity; private MyThread t; I private class MyThread extends Thread { @Override public void run() { final String text = getTextFromNetwork(); mActivity.setText(text); } // Long operation private String getTextFromNetwork() { // Simulate network operation SystemClock.sleep(5000); return "Text from network"; } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 在狀態改變時,保留狀態 setRetainInstance(true); } @Override public void onAttach(Activity activity) { super.onAttach(activity); mActivity = (ThreadRetainWithFragmentActivity) activity; } @Override public void onDetach() { super.onDetach(); mActivity = null; } public void execute() { // 啟動 worker thread t = new MyThread(); t.start(); } }
例外
如果執行緒因例外而結束,可以用 UncaughtExceptionHandler 捕捉此狀況。
執行緒全域處理器
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler);
執行緒區域處理器:比全域處理器還早被執行
void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler);
範例程式:
Thread t = new Thread(new Runnable() { @Override public void run() { throw new RuntimeException("unexpected error"); } }); t.setUncaughtExceptionHandler(new Thread.UncaughtExceptioHandler() { @Override pbulic void uncaughtException(Thread thread, Throwable throwable) { Log.d(TAG, throwable.toString()); } });
APP 裡面有一個全域的 UncaughtExceptionHandler,他會以 kill process 的方式,處理所有例外。可以針對所有 thread,以全域的方式複寫,將例外重導到此 handler。
Thread.setDefaultUncaughtEcceptionHandler(new ErrorReportExceptionHandler()); public class ErrorReportExceptionHandler implements Thread.UncaightExceptionHandler { private final Thread.UncaughtExceptionHandler defaultHandler; public ErrorReportExceptionHandler() { this.defaultHandler=Thread.getDefaultUncaughtExceptionHandler(); } @Override public void uncaughtException(Thread thread, Throwable throwable) { reportErrorToFile(throwable); defaultHandler.uncaughtException(thread, throwable); } }
沒有留言:
張貼留言