在前一部分裏面previous part ,咱們深刻挖掘了 looper 和 handler的處理機制,以及他們是怎麼與Anroid主線程關聯起來的。html
如今,咱們來深刻探討一下主線程與 安卓組件的生命週期之間是怎麼交互的。java
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 } }
設備的方向是可能在任什麼時候候被改變的. 咱們使用Activity#setRequestedOrientation(int) 方法來模擬orientation change 行爲。android
你知道下面的log輸出的是啥麼?git
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()"); } }
If you know the Android lifecycle, you probably predicted this:app
若是你知道Android lifecycle這篇文章,你可能這麼預測:oop
onCreate() Requesting orientation change onResume() onPause() onDestroy() onCreate() onResume()
created, resumed,而後才考慮orientation change行爲的觸發,這時, activity 再執行 paused, destroyed,而後新的activity從新執行,created 和 resumed方法。The Android Lifecycle goes on normally, the activity is created, resumed, and then the orientation change is taken into account and the activity is paused, destroyed, and a new activity is created and resumed. 正常來講應該是這樣的,activity
此處有一個重要的細節須要注意: orientation change行爲會致使activity被從新建立。而這一行爲僅經過post
post一個消息給主線程的方式來實現。
下面咱們能夠經過反射來檢測主循環隊列 中的內容
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 觸發以後的 消息隊列中的記錄:
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(); } } }
Here is the output:
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表明的是什麼:this
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操做時,實際上就是把 CONFIGURATION_CHANGED
和 RELAUNCH_ACTIVITY消息添加到了主循環隊列中。
google
下面咱們來一步步的分析:spa
當activity第一次啓動時,消息隊列是空的。 當前正在執行的消息是LAUNCH_ACTIVITY。
LAUNCH_ACTIVITY消息的處理過程是這樣的:首先建立一個
activity 實例,而後調用onCreate方法和onResume方法。而後LAUNCH_ACTIVITY消息就算完成了,主循環繼續去運行下一條消息。
When a device orientation change is detected, a is posted to the queue.RELAUNCH_ACTIVITY
當orientation change事件被監測到的時候,RELAUNCH_ACTIVITY消息被添加到隊列中來。
消息處理過程是這樣的:
onSaveInstanceState()
, onPause()
, onDestroy()
方法onCreate()
和onResume()方法。
這一過程都包含在一個消息中處理。也就是說,在上述過程當中,任何post調用都會在 方法以後被調用。onResume()
若是你在onCreate中post一個消息, 而此時剛好orientation change事件也被觸發,會出現什麼樣的狀況?
看下面兩個例子,注意orientation change先後的log:
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()"); } }
Here is the output:
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方法執行到最後時,消息隊列中含有4個消息。第一個消息時orientation change以前添加的post消息,緊接着的兩個消息是與orientation change相關聯的兩個消息,而後是orientation change以後添加的post消息。能夠在日誌中看到他們是有序進行的。
也就是說,任何在orientation change以前添加的消息都會在onPause調用以前被調用,任何在orientation change以後添加的消息都會在 onResume調用以後被調用。
實際的意義在於,當你發生一個post消息時,你不能保證activity實例在消息執行時還存在(即使你是在 or ).若是你的消息持有View或者activity的引用,則activty將不會被及時回收。
onCreate()onResume()中調用的post
不要在主線程下啊調用handler.post(). 在大部分狀況下,handler.post()
被用於快速修復一些與時序相關的問題。
推薦的作法是修改你的程序邏輯,而不是亂調handler.post()方法。
請肯定你在作後臺操做時,不持有activity的引用~
那麼請在activityonPause裏面調用
handler.removeCallbacks()來確保消息隊列清空。
使用
handler.postAtFrontOfQueue()
去確保消息老是在 onPause()
以前抵用. 而後你的代碼就會變得很難懂。。。
你有沒注意到咱們使用 handler.post()
而不是直接調用
Activity.runOnUiThread()
?
看下面你就明白了:
public class Activity { public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } } }
Unlike handler.post()
, runOnUiThread()
does not post the runnable if the current thread is already the main thread. Instead, it calls run()
synchronously.
和handler.post()不同
, runOnUiThread()下,若是當前線程就是主線程的時候,它是直接同步調用runnable方法的。
這有一個常見的誤解須要澄清一下: service 並不運行在後臺線程中
全部的,service 生命週期相關方法如onCreate()
, onStartCommand(),都是在主線程中運行的
不論是在service仍是在activity中,長時間的任務都應該交給後臺線程。後臺線程的存活期能夠和應用自己同樣長。
可是,有時候 系統可能會殺掉app進程。
使用service 有助於咱們儘量的延長引用的存活期。
邊注: 當IBinder接收到另外一個進程的調用時,方法將會在後臺線程中調用。
Take the time to read the Service documentation – it’s pretty good.
大部分Android 生命週期相關的方法都在主線程中調用。
必須再囉嗦一句:不要阻塞主線程!!!
不過,你有沒想過阻塞主線程是啥效果?請看下章
Have you ever wondered what blocking the main thread actually means? That’s the subject of the next part!