一、 示例

首先,看如下代码,请判断输出结果:

public class MainThreadTestActivity extends AppCompatActivity {
  private static final String TAG = MainThreadTestActivity.class.getSimpleName();
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_thread_test);
    View view = new View(this);
    view.post(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[view.post] >>>> 1 ");
    new Handler(Looper.getMainLooper()).post(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[handler.post] >>>> 2");
    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[runOnUiThread] >>>>> 3");
    new Thread(new Runnable() {
      @Override
      public void run() {
        runOnUiThread(new Runnable() {
          @Override
          public void run() {
            Log.i(TAG, "[runOnUiThread from thread] >>>> 4");
    }).start();

首先预测下,输出结果会是怎样的呢?

下面给出运行结果:

...I/MainThreadTestActivity: [runOnUiThread] >>>>> 3
...I/MainThreadTestActivity: [handler.post] >>>> 2
...I/MainThreadTestActivity: [runOnUiThread from thread] >>>> 4

那么,问题来了:

  • 第一步的 View.post 为什么没有执行?
  • 为什么 runOnUiThread 会执行的比 Handler.post 快?
  • 在正常情况下, runOnUiThread Handler.post View.post 这三者的执行顺序又会是怎样的呢?
  • 下面我们分别进行解析。

    二、 解析

    2.1 View.post

    2.1.1 View.post 不执行问题

    首先,我们来看 View.post 源码:

    //View.java
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    

    即:当执行 View.post 方法时,如果 AttachInfo 不为空,则通过 AttachInfo Handler 来执行 Runnable ;否则,将这个 Runnable 抛到 View 的执行队列 HandlerActionQueue 中。

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            mAttachInfo = info;
            //...
    

    也就是只有当 View attch 到 Window 后,才会给 AttachInfo 赋值。所以,在 示例 里的代码会直接走入 getRunQueue().post(action) 。我们继续顺着源码看下去。

    View 里,通过 HandlerActionQueue 封装了可执行请求队列。官方给它的注释是 Class used to enqueue pending work from Views when no Handler is attached. ,也就是view还没有 Handler 来执行后续任务(没有attach 到 Window 上),将所有请求入队。

    // HandlerActionQueue.java
    public class HandlerActionQueue {
        public void removeCallbacks(Runnable action) {
            synchronized (this) {
                final int count = mCount;
                int j = 0;
                final HandlerAction[] actions = mActions;
                for (int i = 0; i < count; i++) {
                    if (actions[i].matches(action)) {
                        // Remove this action by overwriting it within
                        // this loop or nulling it out later.
                        continue;
                    if (j != i) {
                        // At least one previous entry was removed, so
                        // this one needs to move to the "new" list.
                        actions[j] = actions[i];
                // The "new" list only has j entries.
                mCount = j;
                // Null out any remaining entries.
                for (; j < count; j++) {
                    actions[j] = null;
        public void executeActions(Handler handler) {
            synchronized (this) {
                final HandlerAction[] actions = mActions;
                for (int i = 0, count = mCount; i < count; i++) {
                    final HandlerAction handlerAction = actions[i];
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                mActions = null;
                mCount = 0;
    

    而在 View 中,

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            // Transfer all pending runnables.
            if (mRunQueue != null) {
                mRunQueue.executeActions(info.mHandler);
                mRunQueue = null;
    

    综上我们就可以分析出: 由于 View view = new View(this); 没有将view attch到window上,所以执行的 View.post 方法将可执行请求都缓存到请求队列里。

    所以, 示例 中的代码可改为:

    View view = new View(this);
        rootView.addView(view);
        view.post(new Runnable() {
    

    输出结果为:

    ...I/MainThreadTestActivity: [runOnUiThread] >>>>> 3
    ...I/MainThreadTestActivity: [handler.post] >>>> 2
    ...I/MainThreadTestActivity: [runOnUiThread from thread] >>>> 4
    ...I/MainThreadTestActivity: [view.post] >>>> 1 
    

    成功执行 View.post 方法,修改正确。

    View.post() 只有在 View attachedToWindow 的时候才会立即执行

    2.1.2 View.post 源码解析

    通过 2.1.1 节我们知道, View.post() 只有在 View attachedToWindow 的时候才会立即执行 。通过执行 ViewRootImpl ViewRootHandler 执行。

    2.2 runOnUiThread

    //Activity.java
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
    
  • 如果当前线程是 主线程 , 请求会立即执行
  • 如果当前线程不是 主线程 , 请求会发到 UI 线程的时间队列。
  • 即,在非主线程调用该方法,存在线程切换的开销

    2.3 Handler.post

    需要先把请求加入到 Handler 队列,然后执行。

    三、执行顺序分析

    综上,当在主线程分别调用 View.postHandler.postrunOnUiThread , new Thread() - [runOnUiThread] 四个方法执行顺序从快到慢为:

    runOnUiThread - Handler.post - new Thread() - [runOnUiThread] - View.post

    (符合前面验证结果)

  • runOnUiThread 因为当前执行在 UI线程,无需线程切换,直接执行
  • Handler.post 需要将请求加入 UI线程 Handler , 多了 入队出队 时间
  • new Thread() - [runOnUiThread] 开启新线程,在启动完成后将请求加入 UI线程 Handler, 多了 线程切换入队出队 时间
  • View.post 需要在view attach 到 Window 后,通过 ViewRootImplViewRootHandler 执行请求。线程切换时间远小于UI渲染时间,所以执行最慢
  • 扩展: 当 示例 中代码在 非UI线程 运行时,runOnUiThreadHandler.post 开销几乎一样,所以执行结果也是顺序完成。 故当在 非UI线程 分别调用 View.postHandler.postrunOnUiThread , new Thread() - [runOnUiThread] 四个方法执行顺序从快到慢为:

    Handler.post - runOnUiThread - new Thread() - [runOnUiThread] - View.post

  • 感谢 公众号巴巴巴掌 提出的发散问题 查看公众号原文
  • Burjal本尊 Android民工
    粉丝