【翻译】configuration changes与handler.post

 

在前一部分里面previous part ,咱们深刻挖掘了 looper 和 handler的处理机制,以及他们是怎么与Anroid主线程关联起来的。html

如今,咱们来深刻探讨一下主线程与 安卓组件的生命周期之间是怎么交互的。java

Activities 屏幕方向变化是一个常见的问题

咱们首先仍是从activity的生命周期,以及activity处理  configuration changes【好比屏幕方向的变化】的机制谈起。
 

 

为何要探讨这一问题

本文的初衷来自于 在   Square Register  中真实发生的崩溃bug。下面是引发问题的相关代码【简化版】:
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
  }
}
 
 
如咱们所见,当触发   configuration change事件时, doSomething()是可能在 activity的 onDestroy()方法以后才被调用的,而这时你已经不能再使用activity实例了。

让咱们再来复习一下orientation changes事件

设备的方向是可能在任什么时候候被改变的. 咱们使用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 changes 和主线程

此处有一个重要的细节须要注意: 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消息被添加到队列中来。
消息处理过程是这样的:
  • 依次调用旧activity的 onSaveInstanceState()onPause()onDestroy() 方法
  • 建立一个新的activity实例,
  • 依次调用新activity实例的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()方法。

  

若是确实不得不调用post

请肯定你在作后台操做时,不持有activity的引用~

 

好吧,若是你确实须要引用activity

那么请在activityonPause里面调用

handler.removeCallbacks()来确保消息队列清空。

 

固然,若是你想被开除的话,你能够这么作:

使用handler.postAtFrontOfQueue() 去确保消息老是在 onPause() 以前抵用. 而后你的代码就会变得很难懂。。。

顺便提一下runOnUiThread()

你有没注意到咱们使用 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!

相关文章
相关标签/搜索