在前一部分里面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!