Android 子线程更新UI常用方法及源码分析
前言
本文不深入介绍Handler机制原理,只是简单地介绍使用方式,重点介绍其他两种方法利用 Handler 机制实现的原理
正文
在 Android 中只能在主线程中更新UI,如果在子线程中更新UI就会出现经典报错:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
日常开发中我们经常有访问网络异步获取数据然后根据数据更新 UI 的操作,这时我们就需要在子线程中切换到主线程更新 UI ,常用的方法有三种:
1.Handler
在主线程中定义Handler
接收到信息并做具体操作
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
mTextView.setText((String) msg.obj);
break;
}
}
};
在子线程中使用主线程的Handler
发送消息(Message),也可以设置延迟时间延迟发送消息
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.obj = "Update UI by handler " + Thread.currentThread().getName();
message.what = 1;
mHandler.sendMessage(message);
}
}).start();
2.View.post(runnable)
new Thread(new Runnable() {
@Override
public void run() {
mTextView.post(new Runnable() {
@Override
public void run() {
mTextView.setText("Update UI");
}
});
}
}).start();
看看 view.post() 源码:
public boolean post(Runnable action) {
// 获取 view 依附在 window 后的信息
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 从信息中获取主线程的 handler 并让其执行我们的 runnable
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 的注释介绍 mHandler 可以用来操作UI,所以它是主线程的 handler
注意最后调用的是 handler.post(),该方法的具体介绍放在最后
/**
* A set of information given to a view when it is attached to its parent
* window.
*/
final static class AttachInfo {
/**
* A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
* handler can be used to pump events in the UI events queue.
*/
final Handler mHandler;
...
}
所以 View.post
其实是利用 handler
来实现的,所以适当的使用它可以减少代码量。
那么从代码上看如果View
还没有依附在window
上的话会执行getRunQueue().post(action)
,先看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;
}
返回该 View
的HandlerActionQueue
对象,再看看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 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;
}
}
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);
}
}
}
根据描述HandlerActionQueue
是当没有Handler
的时候用来保存runnable
的队列。
通过阅读代码我们可以知道其内部维护了一个HandlerAction
数组作为队列,且提供了executeActions(Handler)
方法传入 handler
然后使用handler
处理暂存的HandlerAction
队列,那么executeActions()
什么时候执行呢,在View
中搜索关键字可以找到executeActions()
在View
的dispatchAttachedToWindow()
里被调用:
/**
* @param info the {@link android.view.View.AttachInfo} to associated with
* this view
*/
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
...
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
...
}
具体流程:
该方法会在 View 依附在 window 的时候被调用,所以当我们回看 view.post()
代码的时候我们就知道作者的思路:
- 实现原理是使用 Handler 处理 View.post 传入的 runnable
- 但是 View 依附在 window 之前是没有 handler 的,所以就把传入的 runnable 暂时存放在 View 的 HandlerActionQueue ,然后依附在 window 后再用 handler 处理 HandlerActionQueue 里面的 runnable
- 如果 View 已经依附在 window 上的时候直接可以用 handler 处理传入的 runnable
3.Activity.runOnUiThread(Runnable)
Activity 的 runOnUiThread() 顾名思义,传入的 runnable 实在主线程中运行的
new Thread(new Runnable() {
@Override
public void run() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView3.setText("Update UI by runOnUiThread " + Thread.currentThread().getName());
}
});
}
}).start();
查看runOnUiThread()
源码
@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) { // 当前线程不是主线程
// mHandler: 当前 activity 创建的 handler
// 调用 handler.post 运行 runnable
mHandler.post(action);
} else {
// 当前是主线程,直接执行 runnable 内容
action.run();
}
}
注意最后调用的是 handler.post(),该方法的具体介绍放在最后
总结:回顾 UI 线程就是指创建UI层次的线程,那么 UI 就是 Activity 创建的,所以 Activity 所在的线程肯定是 UI线程,Activity 创建的 handler 肯定可以操作 UI,回头看 runOnUiThread() 代码一目了然了
Handler.post(runnable)
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r 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.
*/
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;
}
查看代码后我们知道handler.post(runnable)
方法就是把传入的runnable
包装为一个Message
发送出去,注意runnable
变成了message
的callback
,为什么这样做呢?这里涉及到Handler
机制的具体实现,在这里不做详细介绍,我们只要知道Handler
接收信息的时候会回调dispatchMessage
分发消息做处理,当Message
的callback
不为 null 的时候调用handleCallback()
,即直接运行Message
的callback
流程:调用Handler.post()
的runnable
被包装为Message
,然后被发送后再被handler
接收时会立即执行runnable
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
...
private static void handleCallback(Message message) {
message.callback.run();
}