Android之Handler消息机制详解

1、什么是Handler

handler是android给我们提供用来更新UI的一套机制,也是一套消息处理,我们可以发送消息,也可以通过它处理消息。

2、android为什么要设计只能通过Handler机制更新UI呢

最根本的目的就是解决多线程并发的问题。
假设如果再一个Activity当中,有多个线程去更新UI,并且都没有加锁机制,那么会产生什么样子的问题?
更新界面混乱
如果对更新UI的操作都进行加锁处理的话又会产生什么样子的问题?
性能下降
处于对于以上目的的问题的考虑,android给我们提供了一套更新UI的机制,我们只需要遵循这样的机制就可以了。而根本不用去关心多线程的问题,所有的更新UI的操作,都是在主线的消息队列当中去轮询处理。

3、hanlder怎么用

先看看如果在子线程中更新UI会抛出什么样的错误

        new Thread(){
            @Override
            public void run() {
                SystemClock.sleep(1000);
                textView.setText("Thread中更新UI");
            }
        }.start();

抛出的异常:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Handler的使用方法有好几种:
post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long),
sendEmptyMessage(int),sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long)

我们介绍其中一种使用方法:
先实例化Handler

private Handler handler= new Handler();

在我们的子线程中更新UI

new Thread(){
            @Override
            public void run() {

                try {
                    Thread.sleep(1000);
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText("Thread中更新");
                        }
                    });

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }.start();

4、Handler的原理

  • Handler封装了消息的发送(主要包括消息发送给谁)
  • Looper
    1.内部包含一个消息队列也就是MessageQueue,所有的Handler发送的消息都走向这个消息队列。
    2.Looper.Looper方法,就是一个死循环,不断从MessageQueue取消息,如果有消息就处理消息,没有消息就阻塞
  • MessageQueue
    就是一个消息队列,可以添加消息,并处理消息
  • Handler也很简单,内部会跟Looper进行关联,也就是说在Handler的内部可以找到Looper,找到了Looper也就找到了MessageQueue,在Handler中发送消息,其实就是向MessageQueue队列中发送消息

总结:handler负责发送消息,Looper负责接收Handler发送的消息,并直接把消息回传给handler自己,MessageQueue就是一个存储消息的容器。
那么他的内部原理是什么呢?我们通过源码再来更深入的了解:
不论是handler.sendMessage(msg);或者是sendEmptyMessage(),post()方法都是调用的sendMessageAtTime(Message msg, long uptimeMillis)方法
那么在这个方法里都有什么呢?

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

send函数其实就是往消息队列里面插入一条消息,说道MessageQueue消息队列就得再来看看Looper了。

for (;;) {
            Message msg = queue.next(); // might block
            //后面还有一大堆的东西,上面这一句和下面这一句最重要
            msg.target.dispatchMessage(msg);
         }

Looper的loop函数又会调用MessageQueue的next函数去获取消息,最后在交给Handler的dispatchMessage函数处理,这样子我们就把Handler, MessageQueue, Looper三者就有了一定的关系。

再来看看Handler的dispatchMessage都做了什么:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这里有两个回调,先看第一个handleCallback

 private static void handleCallback(Message message) {
        message.callback.run();
    }

这个callback是一个Runnable类,到这里我们应该就明白了最开始我们提到的使用Handler处理耗时操作和UI操作的原理了。
那么当msg.callback==null的情况呢,这个mCallback又是什么?
查看源码其实是Handler的构造函数

public Handler(Callback callback, boolean async) {
    ...
}

最后再来看看Callback是是什么:

public interface Callback {    
    public boolean handleMessage(Message msg);
}

发现Callback是一个接口,再回想我们于是我们应该就会想起我们在创建Handler的时候有时候会写这样子的代码:

Handler handler = new Handler(){    
    @Override
    public void handleMessage(Message msg) {        
        super.handleMessage(msg);
    }
};

是的,这个时候dispatchMessage就会来执行Handler的handleMessage(msg)方法来完成主线程UI的更新。

最后再来一段总结:handler.sendMessage函数最终还是转换成了send系列函数往MessageQueue里面插入了一条消息队列,然后主线程已经为我们创建好的Looper对象在loop函数中调用了MessageQueue的next()方法来读取到这条消息,再调用Handler的dispatchMessage函数来处理消息,在dispatchMessage里面会去执行postDelayed中第一个参数runnbale开启的子线程。如果没有开启子线程Runnable则会去执行Handler的handleMessage()方法。

5、四种子线程更新UI的几种方法

  • 1.第一种最基本的方法即在子线程中发送消息sendMessage,类似的sendEmptyMessage(int),sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long)都属于同一类。
       new Thread(){
            @Override
            public void run() {

                try {
                    Thread.sleep(1000);

                    Message msg = handler.obtainMessage();
                    msg.arg1 = 1;
                    handler.sendMessage(msg);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }.start();

然后在handler中处理

    private Handler handler= new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            textView.setText(msg.arg1+"");
        }
    };
  • 2.直接在子线程中调用handler.post()方法:
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText("Thread中更新");
                        }
                    });
  • 3.控件也有post方法
                    textView.post(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText("Thread中更新");
                        }
                    });
  • 4.在子线程使用 runOnUiThread()方法
 runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText("Thread中更新");
                        }
                    });

6、非UI线程真的不能更新UI吗?

其实是可以的,但是强烈不推荐

        new Thread(){
            @Override
            public void run() {

                try {
                    Thread.sleep(10);

                    textView.setText("Thread中更新");


                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }.start();

在我们的线程中,睡眠时间极短或者没有睡眠的时间的时候。
查看源码是因为onCreate()方法先于onResume()方法,所以我们在子线程更新UI的时候,还没走到checkThread()方法。它就并不知道我们在子线程更新了UI,这种情况下时可以更新的。
再次重申,不推荐,只是告诉大家一种特例情况。

关于Handler的视频学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值