Handler消息传递机制(一)理解到底为什么?

1.为什么UI更新要在主线程中,主线程有什么原则?

我们都知道,要在非UI线程更新UI需要使用到Handler或者AsyncTask(内部也是Handler实现),那么为什么就不能直接在子线程中更新UI,而要在UI线程中更新。这就扯到一个单线程和多线程问题,要知道UI线程中只有一个MainThread主线程,也就是单线程,而非UI线程的话就可能有多线程,关于一个线程安全问题。在多线程情况下如果去更新UI,所有线程同时在对一个UI进行访问,当前UI的状态就会无法获取到,导致线程安全问题,可以使用synchronized锁定当前线程,防止其他线程访问。这里也看到其他人的一些说法,就是跟显卡芯片有关,想想还是有点道理。所有的并发显示UI都是要排队的,只是给你的假象是在同时更新,如果芯片很差,就会出现绘制延迟和混乱,如果显卡芯片性能很高很高,在子线程更新UI也没什么问题,而目前在Google 新推出的Sky语言号称能达到120fps ,并且不会阻塞UI。要知道现在的Android手机很多连60fps都达不到。
另外当所有更新UI操作放到主线程时,如果存在一些比较耗时的工作比如访问网络或者数据库查询,都会堵塞UI线程,导致事件停止分发,也就是耗时操作要放在子线程。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果UI线程blocked的时间太长(大约超过5秒),用户就会看到ANR(application not responding)的对话框。所以Android的单线程模型有两条原则

①不要阻塞UI线程,耗时操作放在子线程

②不要再UI线程之外访问UI组件

2.UI中子线程更新UI的3种方法

  • runOnUiThread(Runnable runnable)
     /**
     * 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
     */
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

源码中这样解释。先判断当前线程是否是主线程,如果是主线程,则直接执行Runable接口的run()方法,如果不是主线程,则通过Handler,post(Runnable runnable)到主线程中去。这里需要说明的是,runOnUiThread方法是属于Activity的,也就是说我们能拿到Activity才能使用该方法。比如

new Thread(new Runnable){
    @Override
    public void run(){
        MainActivity.this.runOnUiThread(new Runnable(){

            @Override
            public void run(){
                //done
            }
        });
    }
}).start();

很明显,这里的线程并不是主线程,获取当前线程是通过Looper里面Thread.currentThread()获得,而MainThread主线程里面已经自动创建了Looper,详细会在之后介绍。该方法会返回当前的执行线程。这里明显是执行了mHandler.post(action)方法。我们目前不去研究handler.post方法,因为一会你就知道为什么了。再来看看第二种解决问题的办法

  • view.post(Runnable runnable)
/**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @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);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

这里AttachInfo表示View绑定到父Window的一组信息。当View绑定到父窗口时就获取当前窗口的Handler,再调用post方法。无论我们是选择第一种方法还是第二种方法去解决这个崩溃问题,都是殊途同归的,最后经过层层封装,都走到了handler.post方法中。

  • handler.post(Runnable 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);
    }

可以看到,通过handler.post(Runnable runnable)会把Runnable接口添加到当前的消息队列中,这个Runnable的执行时的线程就是与Handler绑定的线程.而Handler的线程则是通过Looper来获取。也就是说,当我们创建Handler的时候,例如我们经常new的一个Handler

    Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };  
        ...
        //我们new了一个很普通的Handler,我们点进 new Handler()的构造方法一层一层点进
    public Handler() {
        this(null, false);
    }
        ...
    //再点
    public Handler(Callback callback, boolean async) {
        ... //省略一些我们不用到的
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到,在new Handler()中,里面获取了Looper,而Looper里面通过Thread.currentThread()获取到了当前的线程,跟前面一样,详细的会下介绍Looper的时候说。
回到handler.post上,调用的是sendMessageDelayed(getPostMessage(r), 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);
    }

可以看到,方法的调用顺序为post–sendMessageDelayed–sendMessageAtTime–enqueueMessage
详细的下一篇文章再引出来,一次性引出太多也消化不了,反而变乱。本篇文章主要跟大家讲解Handler的使用情况,进而引出一系列问题出来,比如Handler内部操作,Looper又是什么,他们之间是如何协调工作的。

3.小结

* 主线程更新UI操作,考虑到线程安全问题

* 耗时任务要放在子线程,避免阻塞主线程

* 子线程更新Ui的三种方法

1.runOnUiThread(Runnable runable),必须持有activity的引用才可以调用
2.view.post(Runnable runnable) , View通过绑定父窗口,获取父窗口的Handler,再调用Handler.post(Runnable runnable)方法
3.handler.post(Runnable runnable) , 将Runnable打包成Message,放到Handler的消息队列中去。Handler通过Looper绑定当前线程

4.个人体会

Handler相信网上肯定有很多的资料解释,但那些资料也无一没有脱离源码,所以要解决问题,还是要多看源码,自己多动手做,坚持才是硬道理。下篇文章将会通过源码详细介绍Handler,MessageQueue,Looper

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值