在之前的一篇文章我們深入loopers和handler進行分析,看它們是如何同Android主執行緒相關聯的。
今天,我們將繼續深入Android主執行緒同Android元件生命週期的互動。
Activity同orientation changes之間的關係
首先來看看Activity的生命週期和它處理configuration changes 的神奇之處。
這篇文章主要來自於一段類似下面的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class MyActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { public void run() { doSomething(); } }); } void doSomething() { // Uses the activity instance } } |
通過上面的程式碼我們知道, doSomething()
方法會在Activity因為一個configuration change導致 onDestroy()
方法會被呼叫之後執行。之後,你也不能再使用Activity這個例項了。
基於orientation changes的refresher
裝置的 orientation 回來任意時刻發生改變。我們會在一個Activity被建立的時候通過Activity#setRequestedOrientation(int)方法來模擬一個orientation change。
你能預測當這個Activity處於portrait模式時log輸出嗎?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class MyActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("Square", "onCreate()"); if (savedInstanceState == null) { Log.d("Square", "Requesting orientation change"); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } } protected void onResume() { super.onResume(); Log.d("Square", "onResume()"); } protected void onPause() { super.onPause(); Log.d("Square", "onPause()"); } protected void onDestroy() { super.onDestroy(); Log.d("Square", "onDestroy()"); } } |
如果你瞭解 Android 生命週期, 你的預測結果可能是下面的答案:
1 2 3 4 5 6 7 |
onCreate() Requesting orientation change onResume() onPause() onDestroy() onCreate() onResume() |
Android的生命週期正常運轉,Activity被created,resumed,然後這個時候orientation change 發生了,Activity被 paused, destroyed,接著一個新的Activity被created 和 resumed。
Orientation changes 和Android主執行緒
此處有一個重要的細節:一個orientation change 導致Activity 被重新建立是通過向Android主執行緒的訊息佇列傳送了一個簡單的訊息。
請看下面通過反射來讀取主執行緒訊息佇列裡面內容的spy程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public class MainLooperSpy { private final Field messagesField; private final Field nextField; private final MessageQueue mainMessageQueue; public MainLooperSpy() { try { Field queueField = Looper.class.getDeclaredField("mQueue"); queueField.setAccessible(true); messagesField = MessageQueue.class.getDeclaredField("mMessages"); messagesField.setAccessible(true); nextField = Message.class.getDeclaredField("next"); nextField.setAccessible(true); Looper mainLooper = Looper.getMainLooper(); mainMessageQueue = (MessageQueue) queueField.get(mainLooper); } catch (Exception e) { throw new RuntimeException(e); } } public void dumpQueue() { try { Message nextMessage = (Message) messagesField.get(mainMessageQueue); Log.d("MainLooperSpy", "Begin dumping queue"); dumpMessages(nextMessage); Log.d("MainLooperSpy", "End dumping queue"); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } public void dumpMessages(Message message) throws IllegalAccessException { if (message != null) { Log.d("MainLooperSpy", message.toString()); Message next = (Message) nextField.get(message); dumpMessages(next); } } } |
從上面可以看出,一個訊息佇列僅僅只是一個連結串列,每一個訊息都有一個指向下一個訊息的引用。
我們可以通過上面的程式碼來記錄orientation change發生之後訊息佇列裡面的內容:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class MyActivity extends Activity { private final MainLooperSpy mainLooperSpy = new MainLooperSpy(); protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("Square", "onCreate()"); if (savedInstanceState == null) { Log.d("Square", "Requesting orientation change"); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); mainLooperSpy.dumpQueue(); } } } |
最後的輸出是:
1 2 3 4 5 6 |
onCreate() Requesting orientation change Begin dumping queue { what=118 when=-94ms obj={1.0 208mcc15mnc en_US ldltr sw360dp w598dp h335dp 320dpi nrml land finger -keyb/v/h -nav/h s.44?spn} } { what=126 when=-32ms obj=ActivityRecord{41fd2b48 token=android.os.BinderProxy@41fcce50 no component name} } End dumping queue |
我們可以通過ActivityThread 類的原始碼知道數字 118 和 126 代表的訊息是:
1 2 3 4 5 6 |
public final class ActivityThread { private class H extends Handler { public static final int CONFIGURATION_CHANGED = 118; public static final int RELAUNCH_ACTIVITY = 126; } } |
一個orientation change 會往Android主執行緒的訊息佇列裡面新增一個 CONFIGURATION_CHANGED
和一個 RELAUNCH_ACTIVITY
訊息。
讓我們會退一步來看看到底發生了什麼:
當Activity第一次啟動的時候,主執行緒的訊息佇列是空的。當前要執行的訊息就是 LAUNCH_ACTIVITY,即建立一個Activity例項,先後呼叫
onCreate()
方法和onResume()
方法。接下來只有主執行緒的looper才能處理訊息佇列裡面下一個訊息。
當裝置檢測到有一個 orientation change 時,會有一個 RELAUNCH_ACTIVITY
訊息被推送到主執行緒的訊息佇列。
當這個訊息被處理的時候它會:
- 在老的Activity例項上呼叫
onSaveInstanceState()
,onPause()
,onDestroy()
方法, - 建立一個新的Activity例項,
- 在新的Activity例項上呼叫
onCreate()
和onResume()
方法。
上面這些都是一個訊息要處理的。與此同時發生所有訊息都會在新的Activity執行完 onResume()
方法後才被呼叫的。
全面測試
如果你在一個orientation change發生期間在 onCreate()
方法中向主執行緒訊息佇列傳送訊息會怎麼樣?這會有兩種情況,在orientation change發生之前和之後:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public class MyActivity extends Activity { private final MainLooperSpy mainLooperSpy = new MainLooperSpy(); protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("Square", "onCreate()"); if (savedInstanceState == null) { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { public void run() { Log.d("Square", "Posted before requesting orientation change"); } }); Log.d("Square", "Requesting orientation change"); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); handler.post(new Runnable() { public void run() { Log.d("Square", "Posted after requesting orientation change"); } }); mainLooperSpy.dumpQueue(); } } protected void onResume() { super.onResume(); Log.d("Square", "onResume()"); } protected void onPause() { super.onPause(); Log.d("Square", "onPause()"); } protected void onDestroy() { super.onDestroy(); Log.d("Square", "onDestroy()"); } } |
輸出結果為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
onCreate() Requesting orientation change Begin dumping queue { what=0 when=-129ms } { what=118 when=-96ms obj={1.0 208mcc15mnc en_US ldltr sw360dp w598dp h335dp 320dpi nrml land finger -keyb/v/h -nav/h s.46?spn} } { what=126 when=-69ms obj=ActivityRecord{41fd6b68 token=android.os.BinderProxy@41fd0ae0 no component name} } { what=0 when=-6ms } End dumping queue onResume() Posted before requesting orientation change onPause() onDestroy() onCreate() onResume() Posted after requesting orientation change |
有上面的結果我們可得知:在執行到onCreate()方法最後時,訊息佇列裡面包含四個訊息。第一個就是orientation change 發生之前傳送的訊息,接著兩個訊息為orientation change相關的,最後一個訊息就是orientation change發生之後要傳送的訊息。
最後的logs 表明這些訊息都是按順序依次被執行的。
而且,任何在orientation change 之前傳送的訊息都會在Activity離開時呼叫的 onPause() 之前被處理,任何在orientation change 之後傳送的訊息都會在新的Activity呼叫的 onResume() 之後被處理。
這個實驗告訴我們當你傳送一個訊息的時候,你不能保證當時存在的Activity例項在訊息處理完之後還會存在(即使你是在 onCreate()
或者 onResume()方法裡面傳送的
)。如過你在Activity A傳送的訊息B裡面還持有一個view C或者一個 Activity D的引用,那麼這個Activity A在訊息 B被處理之前是不會被垃圾回收的。
那麼我們可以如何解決這樣的問題呢?What could you do?
The real fix解決方案
當你處於主執行緒的時候不要呼叫 handler.post()
方法。大部分情況下,handler.post()
被用來處理順序問題。為了保證你的應用架構穩定請慎用handler.post()
。
如果你真的需要使用handler.post()
保證你的訊息不要持有一個Activity例項的引用?Make sure your message does not hold a reference to an activity, as you would do for a background operation.
如果你真的需要使用handler.post()還要保持Activity引用
在Activity的onPause()方法裡面通過 handler.removeCallbacks() 方法從訊息佇列移除這個訊息。
如果你真的需要讓你的訊息被及時處理
使用 handler.postAtFrontOfQueue()
方法保證訊息在 onPause()
傳送,那麼這個訊息會在 onPause()之前被處理。但是你的程式碼將會變得很難懂。說真的,別這樣幹。
關於runOnUiThread()的一些話
你注意到我們建立一個handler然後使用 handler.post()
方法而不是直接呼叫Activity.runOnUiThread()方法嗎?
原因是這樣的:
1 2 3 4 5 6 7 8 9 |
public class Activity { public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } } } |
不像 handler.post()
, runOnUiThread()
在當前執行緒為主執行緒的情況下是不會傳送這個runnable的,它會同步地呼叫 run()
方法。
Services
有一個誤解一直需要解除:service 不是 執行在一個後臺執行緒。
所有的 service 生命週期方法 (onCreate()
, onStartCommand()
, 等等) 都是執行在主執行緒上面 (就是那個執行你的Activities各種有趣動畫的執行緒)。
無論你是出於一個 service 還是 activity,長時間任務必須要在後臺執行緒進行處理。這個後臺執行緒可以和你的應用生命週期一樣長,即使你的Activities已經銷燬了。
然而,Android可以在任意時刻決定 kill 一個應用程式。一個 service 是一種請求系統讓我們可以存活更久,同時在系統要kill 程式的時候可以讓這個service知道。
注意: 當onBind() 接受到一個其他程式的呼叫並返回一個IBinder
,那麼這個方法會在後臺執行緒裡面執行。
你可以花時間讀讀 Service documentation –相當不錯。
IntentService
IntentService 為在後臺執行緒依次執行一個Intent 佇列裡面所有Intent提供了一個簡單的方式。
1 2 3 4 5 6 7 8 9 |
public class MyService extends IntentService { public MyService() { super("MyService"); } protected void onHandleIntent(Intent intent) { // This is called on a background thread. } } |
在它內部,使用了一個 Looper
在一個專用 HandlerThread
的來處理這些Intents。當這個service 被銷燬的時候,這個Looper會讓你結束當前intent的處理,然後終結這個後臺執行緒。
總結
大部分的Android 生命週期方法都是在主執行緒被呼叫的。你可以把這些回撥函式當成傳送給一個looper 佇列的簡單訊息。
在此文結束之前還是要像大多數的Android開發部落格那樣強調:不要阻塞Android主執行緒。
你是不是想知道阻塞了主執行緒到底意味著什麼?哈哈,這是下一個主題了。