runOnUiThread 、Handler.post、View

作者: Burjal | 来源:发表于2017-12-12 14:53 被阅读418次

    本文源码基于 Android API 26 Platform

    一、 示例

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

    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 快?
    • 在正常情况下,runOnUiThreadHandler.postView.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 不为空,则通过 AttachInfoHandler 来执行 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];
                    }
    
                    j++;
                }
    
                // 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 的时候才会立即执行 。通过执行 ViewRootImplViewRootHandler 执行。

    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

    相关文章

      网友评论

        本文标题:runOnUiThread 、Handler.post、View

        本文链接:https://www.haomeiwen.com/subject/vucnixtx.html