Android在橫豎屏切換時到底發生了什么?
原文出處: Square技術博客 譯文出處:Android Cool Posts
在之前的一篇文章我們深入loopers和handler進行分析,看它們是如何同Android主線程相關聯的。
今天,我們將繼續深入Android主線程同Android組件生命周期的交互。
Activity同orientation changes之間的關系
首先來看看Activity的生命周期和它處理configuration changes 的神奇之處。
這篇文章主要來自于一段類似下面的代碼:
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輸出嗎?
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 生命周期, 你的預測結果可能是下面的答案:
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的生命周期正常運轉,Activity被created,resumed,然后這個時候orientation change 發生了,Activity被 paused, destroyed,接著一個新的Activity被created 和 resumed。
Orientation changes 和Android主線程
此處有一個重要的細節:一個orientation change 導致Activity 被重新創建是通過向Android主線程的消息隊列發送了一個簡單的消息。
請看下面通過反射來讀取主線程消息隊列里面內容的spy代碼:
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()"); } }
從上面可以看出,一個消息隊列僅僅只是一個鏈表,每一個消息都有一個指向下一個消息的引用。
我們可以通過上面的代碼來記錄orientation change發生之后消息隊列里面的內容:
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(); } } }
最后的輸出是:
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(); } } }
我們可以通過ActivityThread 類的源碼知道數字 118 和 126 代表的消息是:
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(); } } }
一個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發生之前和之后:
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(); } } }
輸出結果為:
onCreate<span class="o">()</span> Requesting orientation change Begin dumping queue <span class="o">{</span> <span class="nv">what</span><span class="o">=</span>0 <span class="nv">when</span><span class="o">=</span>-129ms <span class="o">}</span> <span class="o">{</span> <span class="nv">what</span><span class="o">=</span>118 <span class="nv">when</span><span class="o">=</span>-96ms <span class="nv">obj</span><span class="o">={</span>1.0 208mcc15mnc en_US ldltr sw360dp w598dp h335dp 320dpi nrml land finger -keyb/v/h -nav/h s.46?spn<span class="o">}</span> <span class="o">}</span> <span class="o">{</span> <span class="nv">what</span><span class="o">=</span>126 <span class="nv">when</span><span class="o">=</span>-69ms <span class="nv">obj</span><span class="o">=</span>ActivityRecord<span class="o">{</span>41fd6b68 <span class="nv">token</span><span class="o">=</span>android.os.BinderProxy@41fd0ae0 no component name<span class="o">}</span> <span class="o">}</span> <span class="o">{</span> <span class="nv">what</span><span class="o">=</span>0 <span class="nv">when</span><span class="o">=</span>-6ms <span class="o">}</span> End dumping queue onResume<span class="o">()</span> Posted before requesting orientation change onPause<span class="o">()</span> onDestroy<span class="o">()</span> onCreate<span class="o">()</span> onResume<span class="o">()</span> 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()方法嗎?
原因是這樣的:
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提供了一個簡單的方式。
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主線程。
你是不是想知道阻塞了主線程到底意味著什么?哈哈,這是下一個主題了。