前言
前一章我们讲了更新UI的时机,即Android是在哪一刻才更新UI的,了解Android更新UI的源码流程有助于我们了解其本质,在开发过程中出错也能更快的定位。当然看源码也要不求甚解,不要一头砸进去,毫无头绪,只需顺着一条主线,适可而止。像UI怎么显示到屏幕上的,实际App进程是把显示操作发给System_Server进程的WindowManagerService线程,让它去显示,中间通过Binder线程实现进程间交互。
Android更新UI的几种方式
我们知道Android中有以下几种更新UI的方式,我们探讨的都是在非UI线程更新,下同。
1、Handler sendMessage() or post();
2、Activity runOnUiThread();
3、View post()
4、AsyncTask
5、View其他相关用法
Handler
handler是最常用的,也是Android中最基本的方法,Android中所有的异步更新UI的本质都是用Handler。那么我们看一下sendMessage和post有什么区别
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(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);
}
发现最后调用的是sendMessageAtTime,这个方法里面吧Message加入到MessageQueue里面
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
其实就是把Runnable包装成一个Message,即把Message的callback设置成Runnable,之后就会在主线程回调Runnable的run方法。就这么简单实现了UI的更新。
runOnUiThread()
runOnUiThread()是Activity的方法
// we must have a handler before the FragmentController is constructed
final Handler mHandler = new Handler();
/**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
*
* @param action the action to run on the UI thread
*/
@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
如果是在主线程调用该方法,则跟没调用一样的,都是立即更新UI,调用Runnable的run方法;
如果实在子线程调用该方法,则通过handler.post()把它放进消息队列。所以这个方法的本质很简单,主线程立即更新,子线程通过handler放进MessageQueue
注意mHandler是Activity里面的handler,mUiThread实在Activity的attach方法中赋值的,这个方法是由ActivityThread经过一系列方法调用的,所以这个是主线程。
View post()
Android还提供这个View.post()方法来供我们更新UI,这个方法在View.java里面
/**
* Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
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;
}
从注释可以知道,当attachInfo为空时,即这时候UI还没创建好,是吧Runnable添加到队列中;当attachInfo不为空时,它是把Runnable投递到主线程的消息队列,这里用的handler是mAttachInfo的handler,我们看一下它是在哪,什么时候创建的。
AttachInfo mAttachInfo;
/**
* Queue of pending runnables. Used to postpone calls to post() until this
* view is attached and has a handler.
*/
private HandlerActionQueue mRunQueue;
/**
* @param info the {@link android.view.View.AttachInfo} to associated with
* this view
*/
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
.....
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
dispatchAttachedToWindow()是在ViewRootImpl中调用的
ViewRootImpl.java
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
.....
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
.....
}
mView是在setView()中赋值的
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
}
}
}
而setView是WindowManagerGlobal中调用的,关于WMS等的源码分析我们之后再讲,这里只需要重点关注View的mRunQueue,也就是在Activity启动的流程中,会把post的Runnable放到mRunQueue这个队列里,当view显示到屏幕上,会调用ViewRootImpl的performTraversals(),然后调用View的dispatchAttachedToWindow,然后执行mRunQueue里面的任务。
总结:当view没创建好时,先把任务添加到队列中,不会执行;只有当view创建完毕,显示到屏幕上时,才回去调用任务队列里面的任务,所以我们在onCreate(),onStart()等生命周期里面post的任务不会立即执行,有兴趣的童鞋可以去试一下。
接下来我们重点关注mRunQueue这个HandlerActionQueue
/**
* Class used to enqueue pending work from Views when no Handler is attached.
*
* @hide Exposed for test framework only.
*/
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
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;
}
}
public int size() {
return mCount;
}
public Runnable getRunnable(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].action;
}
public long getDelay(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].delay;
}
private static class HandlerAction {
final Runnable action;
final long delay;
public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}
public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}
}
它里面有个内部类HandlerAction,封装了Runnable,HandlerAction[]保存了任务,初始容量大小为4。可以看到post()方法就是把Runnable放进handlerAction数组里面,并没有执行,真正的执行在dispatchAttachedToWindow方法中
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = 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;
}
}
执行就是遍历HandlerAction数组,依次执行,这里的handler是AttachInfo的handler,它是ViewRootImpl中的ViewRootHandler,它也是继承Handler的。执行完毕后清空HandlerAction数组。
view post 时序图
AsyncTask
AsyncTask的原理其实也是用Handler更新UI的,它内部有一个InternalHandler sHandler;
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult> result = (AsyncTaskResult>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
注意它初始化Handler用的是主线程的Looper,sHandler = new InternalHandler(Looper.getMainLooper());关于AsyncTask的工作原理之后会讲到。
View其他相关用法
ViewTreeObserver
在开发中,通常有这样一种需求,我们需要获取View的宽高等信息,要获取这些信息必须等View完成measure后才能获取,可以用上面我们将的View的post获取,也可以用ViewTreeObserver设置监听来获取
final ViewTreeObserver observer = surfaceView.getViewTreeObserver();
ViewTreeObserver.OnGlobalLayoutListener layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (observer.isAlive()) {
observer.removeGlobalOnLayoutListener(this);
//获取宽高
}
}
};
observer.addOnGlobalLayoutListener(layoutListener);
它会在View的layout发生变化时调用,注意可能会多次调用,用过一次后最好移除监听,否则回调会多次执行,拿它是什么时候回调的呢,我们要再一次进入ViewRootImpl的performTravels()方法
ViewTreeObserver.java
public final void dispatchOnGlobalLayout() {
final CopyOnWriteArray listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
access.get(i).onGlobalLayout();
}
} finally {
listeners.end();
}
}
}
ViewRootImpl.java
private void performTraversals() {
.....
performMeasure();
.....
performLayout();
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
.....
performDraw();
.....
}
当View完成测量,布局后,如果设置了监听,则回调。所以这也能正确的获取宽高。
MessageQueue.IdleHandler
在上一节中,我们提到过MessageQueue中有个有趣的接口叫IdleHandler
private final ArrayList mIdleHandlers = new ArrayList();
private IdleHandler[] mPendingIdleHandlers;
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
Message next() {
.....
int pendingIdleHandlerCount = -1; // -1 only during first iteration
for (;;) {
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
.....
}
通过官方解释我们可以了解这个接口的作用:当消息队列中没有消息时,会回调该接口,前提是你设置了监听。它的返回值:返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。
那么这个接口到底有什么用呢,其实也可以用来获取View的宽高,在View初始化,Activity启动的时候,MessageQueue的有许多的消息,我们可以从ViewRootImpl的ViewRootHandler里面可以看出。当MessageQueue为空时,表示View真正的完成了measue,layout,draw等操作,这是我们就可以去执行一些获取view属性的操作或者做一些耗时操作,这时就不会影响UI的显示了,特别是进入App主页,经常需要向服务器请求大量的数据来更新UI,如果子线程过多,又设置了过高的优先级,就会抢占调度UI线程,导致卡顿,所以这个接口就很有用了。那么怎么具体使用呢?
//MessageQueue queue = Looper.getMainLooper().getQueue();
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.d("Activity","idleHandler");
return false;
}
});