Android更新UI的方式

Android更新UI的方式

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelay(Runnable,long)

Activity.runOnUiThread(Runnable)

流程介绍:
1.首先入口Handler类中runOnUiThread方法,如果当前线程不是UI线程,就调用mHandler.post(action);

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

2.实际post中会调用sendMessageDelayed(getPostMessage®, 0);方法

   public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

3.那么,sendMessageDelayed中是怎样已知Runnable拿到消息的呢?
看一下Handler类中getPostMessage方法,主要还是将r赋值给m.callback,并将消息返回

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

4.拿到消息以后就可以调用发送延迟消息的方法sendMessageDelayed(@NonNull Message msg, long delayMillis)

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

5.继续调用发送消息的方法sendMessageAtTime,返回时调用enqueueMessage方法将消息加入消息队列

  public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

6.把当前的handler作为msg的target属性。最终会调用queue的enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。

   private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;这里就把handler的对象赋值给了msg
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

View.post(Runnable)

可以理解为在当前操作视图UI线程添加队列,实际上底层还是通过Handler从子线程切换到主线程,来实现UI的更新

先看一下view.post(Runnable)的源码实现

//=================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;
    }

上述源码的执行逻辑,关键点在mAttachInfo是否为null,这会导致两种逻辑:
1)mAttachInfo != null,走代码①的逻辑。
2)mAttachInfo == null,走代码②的逻辑。
当前View尚未attach到Window时,整个View体系还没有加载完,mAttachInfo就会为null,表现在Activity中,就是onResume()方法还没有执行完。反之,mAttachInfo就不会为null。

(1)mAttachInfo != null的情况
对于第一种情况,当看到代码①时,应该会窃喜一下,因为看到了老熟人Handler,这就是Handler.post(Runnable)方法,我们再熟悉不过了。这里的Runnable会在哪个线程执行,取决于该Handler实例化时使用的哪个线程的Looper。

在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

我们看一下Handler再次处理该Message的过程:
首先Looper.loop方法中有一行Message msg = queue.next(); 取到消息,接着,会调用msg.target.dispatchMessage(msg);来回调消息,msg.target就是handler,所以等价于handler.dispatchMessage(msg);拿到消息去分发消息,具体实现看一下分发消息的源码:

   public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

我们看前两行,首先看一下msg.callBack是什么,它就是view.post中传入的runnable,当msg.callback != null的时候会执行 handleCallback(msg);我们看一下 handleCallback(msg)的源码实现:

  private static void handleCallback(Message message) {
        message.callback.run();
    }

message.callback.run();就相当于执行runnable.run()

(2)mAttachInfo == null的情况
post源码中代码②有说明:推迟Runnable,直到我们知道需要它在哪个线程中运行。代码②处,看看getRunQueue()的源码:

   /**
     * Returns the queue of runnable for this view.
     * @return the queue of runnables for this view
     */
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

getRunQueue()是一个单例模式,返回HandlerActionQueue实例mRunQueue。mRunQueue,顾名思义,表示该view的HandlerAction队列,下面会讲到,HandlerAction就是对Runnable的封装,所以实际就是一个Runnable的队列。注释中也提到,它用于推迟post的调用,直到该view被附着到Window并且拥有了一个handler。
代码②处执行的结果就是将post的参数Runnable action添加到View的全局变量mRunQueue中了,这样就将Runnable任务存储下来了。那么这些Runnable在什么时候开始执行呢?我们在View类中搜索一下会发现,mRunQueue的真正使用只有一处:

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

到dispatchAttachedToWindow()方法了,,第一种情况也是到了这个方法就停下来了。
我们看看mRunQueue.executeActions(info.mHandler);这一行
传递的参数也是形参AttachInfo info的mHandler。进入到HandlerActionQueue类的executeActions可以看到,这个方法的作用就是通过传进来的Handler,来post掉mRunQueue中存储的所有Runnable,该方法中的逻辑就不多说了,比较简单。这些Runnable最终在哪个线程运行,就看这个Handler了。

到这里为止,两种情况就殊途同归了,最后落脚点都集中到了dispatchAttachedToWindow方法的AttachInfo参数的mHandler属性了。所以现在的任务就是找到哪里调用了这个方法,mHandler到底是使用的哪个线程的Looper。

mHandler所用Looper所在线程问题,其实就是伴随着启动Activity并绘制整个View的过程,可以得到如下简略流程图:

Android更新UI的方式_第1张图片
通过这里的dispatchAttachedToWindow方法,就将mHandler传递到了View.post()这个流程中,从而实现了从子线程中切换到主线程更新UI的功能。

View.postDelay(Runnable,long)

延时而已,同post

你可能感兴趣的:(Android,ui,android)