Android在横竖屏切换时到底发生了什么?

在之前的一篇文章我们深入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 生命周期, 你的预测结果可能是下面的答案:

onCreate()
Requesting orientation change
onResume()
onPause()
onDestroy()
onCreate()
onResume()

 

Android的生命周期正常运转,Activity被created,resumed,然后这个时候orientation change 发生了,Activity被 paused, destroyed,接着一个新的Activity被created 和 resumed。

Orientation changes 和Android主线程

此处有一个重要的细节:一个orientation change 导致Activity 被重新创建是通过向Android主线程的消息队列发送了一个简单的消息。

请看下面通过反射来读取主线程消息队列里面内容的spy代码:

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();
    }
  }
}

最后的输出是:

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 代表的消息是:

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 会往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) {
      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()");
  }
}

输出结果为:

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()方法最后时,消息队列里面包含四个消息。第一个就是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主线程。

你是不是想知道阻塞了主线程到底意味着什么?哈哈,这是下一个主题了。

 

本文翻译自:Square技术博客

Android 主线程之旅——PSVM(public static void main)

本文翻译自Square官方技术博客

当碰到与Android主线程交互相关的Bug时,我决定好好去看看Android的主线程究竟是怎么一回事。这篇文章就是描述我的Android主线程之旅的第一部分。

PSVM

public class BigBang {
  public static void main(String... args) {
    // The Java universe starts here.
  }
}

 

众所周知,所有的Java程序的入口都是 public static void main() 方法。这对所有的Java 桌面程序、J2EE以及Android程序都是成立的。

当Android启动时,它会开启一个叫做 ZygoteInit 的Linux进程。这个进程是一个Dalvik 虚拟机,它会在一个线程上面加载Android SDK里面大部分的常用类,然后等待。

当Android启动一个新的Android程序时,Android系统会 fork 这个 ZygoteInit 进程。接着子进程里面的线程会停止等待,然后调用ActivityThread.main()方法。

1411804069283-zygote

Wikipedia上面的一个Zygote。根据Wikipedia的定义,一个 Zygote 就是一个受精卵细胞。

Loopers

在继续深入之前,我们需要来看一看Looper这个类。

使用looper可以连续地为一个线程处理它的消息。

每一个looper都有一个消息队列(一个 MessageQueue)。

每一个looper都有一个处理消息队列里面所有消息的loop()方法,这个方法会在消息队列为空时阻塞。

Looper.loop() 方法里面的代码类似这样:

void loop() {
  while(true) {
    Message message = queue.next(); // 消息队列为空时阻塞
    dispatchMessage(message);
    message.recycle();
  }
}

 

每一个looper都会和一个线程绑定。要创建一个新的looper并将它同当前的线程绑定起来,你必须要调用Looper.prepare()方法。这些looper都被存储在Looper 类里面的静态ThreadLocal变量里面。你可以通过调用Looper.myLooper()方法来获取与当前线程相关联的Looper。

当然实际情况不要这么复杂,其实HandlerThread类已经帮你做了所有事情:

HandlerThread thread = new HandlerThread("SquareHandlerThread");
thread.start(); // starts the thread.
Looper looper = thread.getLooper();

 

HandlerThread的内部代码类似于这样:

class HandlerThread extends Thread {
  Looper looper;
  public void run() {
    Looper.prepare(); // 创建一个Looper对象并将它保存到一个ThreadLocal 对象里面。
    looper = Looper.myLooper(); // 从ThreadLocal 里面获取Looper以便后来的使用。
    Looper.loop(); // Loop forever.
  }
}

 

Handlers

handler 天生就是 looper 的好伙伴。

一个 handler 有两个作用:

  • 从任意线程发送消息给一个looper的消息队列;
  • 处理相关联的looper发过来的消息。
// Each handler is associated to one looper.
Handler handler = new Handler(looper) {
  public void handleMessage(Message message) {
    // 处理给定looper相关联的线程上面的消息
    if (message.what == DO_SOMETHING) {
      // do something
    }
  }
};
//创建一个与这个handler相关联的message
Message message = handler.obtainMessage(DO_SOMETHING);

// 将message 添加到looper 的消息队列queue.
// 这个方法能在任意线程上调用
handler.sendMessage(message);

 

你可以为一个looper关联多个handler。looper会把message发送到message.target(它就是一个handler)这里。

一个常用简单的用法就是使用handler来发送一个Runnable对象:

//创建包含一个runnable引用的message,然后将这个message添加到这个looper的消息队列
handler.post(new Runnable() {
  public void run() {
    // 这会在与handler相关联的looper对应的线程上运行
  }
});

 

一个handler也可以在没有设置looper的情况下被创建。:

// 千万不要这样做
Handler handler = new Handler();

handler的无参构造函数会调用方法来获取与当前线程相关联的looper。这个时候你要注意,可能当前线程不是你的handler想关联的线程。

大部分时间,你只需要创建一个在主线程上面发送消息的handler就行了:

Handler handler = new Handler(Looper.getMainLooper());

Back to PSVM

让我们再来看看 ActivityThread.main()这个方法。下面就是这个方法的一些内部实现:

public class ActivityThread {
  public static void main(String... args) {
    Looper.prepare();

    // 你可以在任意时刻调用Looper.getMainLooper()方法来获取主线程的looper
    Looper.setMainLooper(Looper.myLooper());

    // 发送第一波消息给主线程的 looper.
    // { ... }

    Looper.loop();
  }
}

现在你知道为什么这个线程被称为主线程了吧:) .

注意: 主线程最先做的几件事情之一就是创建Application对象,然后调用Application.onCreate()方法。

在下一篇文章中,我们将会分析Android生命周期和主线程之间的关系以及bug是怎么出现的。