美文网首页AndroidAndroid开发Android开发
Android 子线程更新UI常用方法及源码分析

Android 子线程更新UI常用方法及源码分析

作者: zYoung_Tang | 来源:发表于2018-04-09 15:54 被阅读59次

    前言

    本文不深入介绍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();
    }
    

    相关文章

      网友评论

        本文标题:Android 子线程更新UI常用方法及源码分析

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