Android的消息机制--Handler

Android的消息机制主要是指Handler的运行机制,及Handler所附带MassageQueue和Looper的工作过程。Handler的主要任务是将一个任务切换到指定的线程中去执行。且这种功能大部分是用于从子线程切到Android UI线程中去更新UI。
这里延伸一点,Android系统为什么不允许在子线程中访问UI呢?这是因为Android的UI控件是线程不安全的,如果多线程并发访问会导致UI控件处于不可预期的状态。系统不对IU控件的访问添加锁机制,是因为加上锁机制会让UI访问的逻辑变得复杂,并且锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。最简单且高效的方法就是采用单线程模型来处理UI操作。

handler的工作原理

Handler创建时会采用当前现成的Looper来构建内部的消息循环系统,如果当前线程没有Looper,就会报如下错误:

new Thread(){
   @Override
    public void run() {
        super.run();
        Log.e("cyy","handler run");
        Handler handler1 = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };

    }
}.start();

在这里插入图片描述
报错的原因:就是在我们new的这个Thread中,没有创建Looper,就直接创建Handler了。
解决办法:在当前线程创建Looper即可(稍后给出实例代码)。
这里我们插播说下ThreadLocal,这个类与Handler相关。

ThreadLocal介绍

定义:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储以后,只有在指定的线程中获取到存储的数据,其他线程是无法获取到这个数据的。
举个例子:创建3个线程,并在各个线程中各向同一个ThreadLocal中存储一个数据,然后再取出。

threadLocal = new ThreadLocal<>();
new Thread("ThreadAAA"){
    @Override
    public void run() {
        super.run();
        threadLocal.set("AAA");
        Log.e("cyy","ThreadAAA:"+threadLocal.get());
    }
}.start();

new Thread("ThreadBBB"){
    @Override
    public void run() {
        super.run();
        threadLocal.set("BBB");
        Log.e("cyy","ThreadBBB:"+threadLocal.get());
    }
}.start();

new Thread("ThreadCCC"){
    @Override
    public void run() {
        super.run();
        threadLocal.set("CCC");
        Log.e("cyy","ThreadCCC:"+threadLocal.get());
    }
}.start();

运行结果如下
在这里插入图片描述
在不同线程中获取的数据就是 当前线程设置的数据,互不干扰。

回到我们Handler的消息队列的讲解中

Handler的消息机制是由Handler、Looper、MessageQueue组成的。
MessageQueue主要包含两个操作,插入和读取
插入是enqueueMessage;读取是next (读取并移除)
尽管MessageQueue叫消息队列,但是它的内部实现并不是队列,而是通过单链表的结构来维护消息队列。插入和读取消息就是通过操作链表来实现的。

Looper介绍

我们先来看下Looper的构造方法

private Looper(boolean quitAllowed) {
	mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

从Looper的构造方法中可知,Looper在构造的时候会去创建MessageQueue。

我们从本篇最开始的例子可以看到,在没有Looper的Thread中使用Handler会报此线程没有Looper的错,我们现在就来创建并开启Looper。
开启Looper通过Looper的prepare方法

//Looper的prepare源码
public static void prepare() {
	prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));// new Looper 并添加到ThreadLocal中
}

这里用到了ThreadLocal,将创建的Looper对象放到ThreadLocal中。
然后通过Looper.loop来开启循环。loop方法比较长,但是其主要的思路就是从MessageQueue的消息链表中拿出一个msg,并交与Handler处理。

public static @Nullable Looper myLooper() {
	return sThreadLocal.get();
}

修改后的Handler使用如下:

new Thread(){
  @Override
   public void run() {
       super.run();
       Looper.prepare();//创建Looper
       Handler handler1 = new Handler(){
           @Override
           public void handleMessage(Message msg) {
               super.handleMessage(msg);
           }
       };
       Looper.loop();//开启Looper循环
   }
}.start();

在次运行,就不会报错啦。
Looper运行起来了,那怎么退出Looper循环呢?
Looper提供了quit和quitSafely来退出。二者区别如下:
quit:直接退出Looper
quitSafely:设置一个退出标志,然后把消息队列中已有的消息处理完毕后才安全退出。

在子线程中,如果手动创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环。否则这个子线程就会一直处于等待状态,而如果退出Looper以后,这个线程就会立刻终止。因此建议不需要的时候终止Looper。

我们再来改进下上面带Handler的子Thread。
给Handle加上两个消息处理类型,然后在通过两个按钮的click事件去发送消息。

new Thread("zi Thread"){
   @Override
    public void run() {
        super.run();
        Looper.prepare();
        handler1 = new Handler(){
           @Override
           public void handleMessage(Message msg) {
               super.handleMessage(msg);

               int what = msg.what;
               if(what == 1){
                   Log.e("cyy","what = 1:"+Thread.currentThread().getName());
               }else if( what == 2){
                   Log.e("cyy","what == 2  停止线程了");
                   Looper.myLooper().quit();
               }
           }
       };
       Looper.loop();
    }
}.start();

在这里插入图片描述
我们多次点击按钮 “发送Message 1”,可以看到what = 1的log正常打出,然后在点击“发送Message 2”,what == 2的log正常打印了,此时再去点击 “发送Message 1”,发现没有任何log打出了。这个子线程已经正常退出了。

到这里Handler的机制就讲完了,最后在啰嗦一句,不知道大家看完之后有没有疑问呢?Handler是我们在开发中经常使用的类,在不知道上述知识之前,我们都在正常使用Handler,并没有操作Looper相关的内容呢?
那是因为你是在主线程中使用的handler,主线程会在启动的时候自动创建并开启Looper循环,因此在主线程中使用Handler,就不需要关注Looper是否创建并开启循环了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值