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主线程。
你是不是想知道阻塞了主线程到底意味着什么?哈哈,这是下一个主题了。