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

返回该 ViewHandlerActionQueue对象,再看看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()ViewdispatchAttachedToWindow()里被调用:

/**
* @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变成了messagecallback,为什么这样做呢?这里涉及到Handler机制的具体实现,在这里不做详细介绍,我们只要知道Handler接收信息的时候会回调dispatchMessage分发消息做处理,当Messagecallback不为 null 的时候调用handleCallback(),即直接运行Messagecallback

流程:调用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();
}
    原文作者:950061da8f79
    原文地址: https://www.jianshu.com/p/e5c8678410ba
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞