Android 消息机制

什么是Android的消息机制结构

Android是参考Windows的消息循环机制来实现自身的消息循环。

Android通过Looper、MessageQueue、Handler来实现消息循环机制。Android的消息机制主要是指Handler的运行机制。Handler的运行需要底层MessageQueue和Looper的支撑。

Android消息循环是针对线程的,Handler用于同一个进程的线程间通信。创建的工作线程默认是没有消息队列和消息循环的。

消息处理机制本质:一个线程开启循环模式持续监听并依次处理其他线程给它发的消息

简单的说:一个线程开启一个无限循环模式,不断遍历自己的消息列表,如果有消息就挨个拿出来做处理,如果列表没消息,自己就堵塞(相当于wait,让出cpu资源给其他线程),其他线程如果想让该线程做什么事,就往该线程的消息队列插入消息,该线程会不断从队列里拿出消息做处理。

为什么需要Android的消息机制

Android规定访问UI只能在主线程中进行。若在子线程中访问UI就会抛出异常。

**为什么不允许在非主线程访问UI呢?**这是因为Android的UI控件不是线程安全的。并且UI访问没有锁机制,并发访问会导致控件处于不可预期的状态。

tips:什么是线程安全?
线程安全指的是内存的安全,在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。
所以线程安全指的是,在堆内存中的数据由于可以被任何线程访问到,在没有限制的情况下存在被意外修改的风险。即堆内存空间在没有保护机制的情况下,对多线程来说是不安全的地方,因为你放进去的数据,可能被别的线程“破坏”。

那为什么不对UI访问加上锁机制呢?
(1)这显然会让UI访问的逻辑变得极其复杂;
(2)锁机制自然会降低效率;
(3)锁机制还会阻塞某些进程的执行。
但是Android又不建议在主线程进行耗时操作,因为这可能会引起ANR。那么需要经过时间处理的逻辑才能影响UI结果的情况该如何处理呢?Android的消息机制应运而生。

MessageQueue

MessageQueue采用以单链表为数据存储结构的消息列表。对外提供插入(enqueueMessage)和读取(next)工作。读取本身附带删除操作。单链表在插入和删除上比较有优势。

插入操作根据消息的延迟时间来进行调整插入位置;

读取操作是一个无限循环,如果消息队列中没有消息就会阻塞,当有新消息到来时,就返回这条消息并将其从单链表中删除。

MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。

Looper

我们知道一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么做为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用Looper.prepare()…Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),Looper.loop()方法里面有一段死循环的代码,所以主线程会进入while(true){…}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在while(true){…}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。

Looper的作用

在Looper的构造方法中会创建一个消息队列并获得当前线程的对象。每一个线程都会维护自己的Looper,具体可以参考Android开发——ThreadLocal功能介绍。Handler在创建时会采用当前线程的Looper来构造消息循环系统。

loop()方法是一个死循环,会调用消息队列的next方法,next是一个阻塞操作,前面也讲过了。只有当next返回了新消息,Looper才会处理这条消息。这样Handler发送的消息最终又交给handler的dispatchMessage方法来处理,我们编写的UI更新操作就在Looper对象的代码中执行了,这个Looper运行在我们创建Handler时的线程中的,也就是主线程,那这样一来就成功的把代码执行从子线程切换到了主线程中。

跳出looper()死循环的唯一条件是MessageQueue的读取方法next()返回了null。当Looper的quit()方法被调用时,消息队列就会被标记为退出状态,它的next()也就返回了null。所以Looper必须退出,否则loop()方法会一直执行下去。

如何为一个线程创建Looper

(1)UI线程,主线程被创建时就会初始化Looper,因此在主线程默认可以使用Handler。

(2)子线程默认是没有Looper的,Handler创建前必须手动创建Looper,否则会报错。

通过Looper.prepare()即可为当前线程创建一个Looper,并通过Looper.loop()来开启消息循环。如下所示。

new Thread(){
    @Override
    public void run() {
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();
    }
}.start();

(3)还有一种情况是使用HandlerThread,HandlerThread 继承自Thread,它和普通Thread不同的是其内部已经封装了Looper。并对外提供自己这个Looper对象的get方法。具体关于HandlerThread的介绍请查看Android开发——HandlerThread以及IntentService详解。

Handler

Handler主要工作是消息的发送和接收。

简单说Handler用于同一个进程的线程间通信。Looper让主线程无限循环地从自己的MessageQueue拿出消息处理,既然这样我们就知道处理消息肯定是在主线程中处理的,那么怎样在其他的线程往主线程的队列里放入消息呢?其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了。

消息的发送可以使用Handler的post的方法(最终还是通过send方法完成)将一个Runnable投递到Looper中去处理。

handler.post(new Runnable(){
@Override
public void run() {
//do something
}});

或者这种用法的变形,用途很广,功能是延迟3秒后从欢迎界面进入主界面。这里并不是开启了一个新的线程。

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        Intent intent = new Intent(SplashActivity.this, MainActivity.class);
        startActivity(intent);
        finish();
    }
}, 3000);

看一下handler.post(Runnable callback)方法的源码,很明显最终还是通过send方法完成的。

public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}

再看一下sendMessageDelayed的源码:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
      delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

这里面有个关键就是方法getPostMessage®这个方法,将Runnable转成一个Message,他内部到底干了什么呢?看一下他的源码:

private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

这里面就是将Runnable转化成一个Message,其他看他的代码很简单,就是先获取一个空消息Message.obtain(),然后将Message中私有变量callback的值设置成Runnable。

send方法被调用时,会调用消息队列的enqueueMessage方法将这个消息放入消息队列中,然后next方法会返回这条消息给Looper,Looper发现新消息到来就把消息交给Handler处理。Handler的dispatchMessage方法被调用,过程如下。

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

(1)首先检查Message的callback,不为null就意味着callback是一个Runnable对象(实际上就是Handler的post方法所传递的Runnable参数)。然后就会通过handleCallback来处理消息。handleCallback逻辑很简单,直接就是msg.callback.run(),执行我们在Runnable方法里重写的run方法。

(2)其次检查mCallback不为空,调用mCallback的handleMessage方法来处理消息。

这是为了处理以下这种Handler的使用情况。CallBack可以用来创建一个Handler的实例但并不需要派生出Handler的子类。

Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            return false;
        }
    });

Callback是一个接口,在重写它的handlerMessage方法时,处理完消息返回true的话,dispatchMessage也就返回了,就不用再处理消息了。

(3)如果返回了false或者mCallback为空,才会执行Handler的handleMessage(msg)方法来处理消息。

参考:
https://blog.csdn.net/SEU_Calvin/article/details/52120086
https://blog.csdn.net/qq_57958234/article/details/124737319
https://www.jianshu.com/p/02962454adf7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值