Android消息机制再探究

本文深入探讨了Android消息机制的核心组件及其工作原理,包括Handler、Looper、Message和MessageQueue等关键概念,解释了它们如何协作实现线程间的通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android消息机制的在探究

之前写过一篇关于Android的内部消息机制的文章,现在回头在看看发现还是有很多不足之处的,加之在使用过程中对这方面的内容又有了进一步的了解,所以在这里对消息机制在做一次深入的分析。
在分析之前,我们需要弄清楚几个问题:
1. 消息机制在能够干什么
2. 消息机制在Android中的地位
3. 探究其实现原理

一,Android消息机制的作用
大家也都经常使用Handler做UI更新操作,所以大部分都是认为Handler就是用来做UI更新用的,因为子线程是不能更新UI的,需要使用Handler机制也做UI的更新操作,这是大家的共识。没错,Handler的确是ui更新比较好的方式。 其实,我个人觉得android这套消息机制,是线程之间的通信机制,只是用的比较多的是ui更新操作而已。在理说的ui线程,也就是主线程,这个线程是用来循环接受子线程的消息,并交给发送该消息的handler去处理。 也就是说只有”主线程”才能接受handler发送的消息,其实确切的说应该是Looper线程才能接受handler发送的消息,显然ui线程也是Looper线程,至于为什么是Looper线程,这个稍后分析,而Looper线程不仅仅是UI线程,任何一个子线程都可以成为Looper线程。 好了,现在我们已经知道Android这套消息机制可以用来说多线程之间的通信,和数据传递的功能,而且很强大。

二,Android消息机制的地位
地位要从功能说起,上面已经看到消息机制很强大,也很好用。可以说消息机制在Android中的地位是举足轻重的,而且设计思想也很优秀,值得深究。framework源码中用到Handler的地方非常多,比如异步数据库查询类AsyncQueryHandler,AsyncTask等这些都是对handler机制的封装。以及源码中两个Handler之间的通信使用是特别频繁,如果不理解这些内部原理,看上去是眼花缭乱。 对于这么重要的消息机制,要想好好的使用必然是要探究一下其原理的。

三,消息机制原理分析
Handler这一套机制主要设计的类如下:
Handler
Looper
Message
MessageQueue
ThreadLocal
对于上面设计到的核心类进行一一的分析

  1. ThreadLocal

    这是一个线程内部的存储类,可以在执行的线程中存储数据,所以也只能在指定的线程中获取数据。这样说起来比较抽象,比如对于一个boolean类型的变量,多个线程都需要存储和获取这个数据,并且要求这些个线程之间对与该变量的存储和获取是互不干扰的,即在每个线程中都存在这个变量的存储副本(注意这个时候线程之间对这个变量的访问不是互斥的,因为每个线程都是有副本的,所以不需要互斥访问)。在这样的情况下,我们可以使用TheadLocal也实现。这里Looper使用了这个机制,那么Looper为什么需要使用这个机制呢? 也就是说,每个线程中都有一个独立的Looper副本,而每个Looper中都关联一个消息队列,再结合之前说的每个线程都可以成为Looper线程,我们可以就可以理解这么做的原因,那是因为这样可以使线程内的消息队列在线程之间是相互独立的,每个Looper线程值处理其对应的handler发送的消息,这样就很容易的做到消息机制的有条不紊的进行。而且多个线程之间也不会出现消息处理错乱问题。 对与理解ThreadLocal的运行原理,可以执行这样一个例子:

private ThreadLocal<Boolean> mBoolean = new ThreadLocal();
mBoolean.set(true);
System.out.println(Thread.currentThread() + " " + mBoolean.get());

new Thread(new Runnable {
    public void run() {
        mBoolean.set(false);
        System.out.println(Thread.currentThread() + " " + mBoolean.get());
    }
}, "thread1").start()

new Thread(new Runnable {
    public void run() {
        System.out.println(Thread.currentThread() + " " + mBoolean.get();
    }
}, "thread2").start()

上面的例子中执行结果是main true, thread1 false, thread2 null
上面的log出现不分先后顺序,这里例子可以看出mBoolean在三个线程中都有备份,互不影响。 ThreadLocal就说到这。

2.消息Message和MessageQueue
对于Message就是我们需要发送的消息的载体,即是当我们使用sendMessage发送的具体内容的携带者,arg1, arg2, what,obj等变量来携带需要传递到Looper线程的内容。
对于MeaageQueue从字面意思就可以理解这是一个消息队列,用来存储消息和获取消息的地方。虽然是叫做消息队列,其内部并维护的并不是队列,实际上是通过一个单链表也维护消息列表的。 MessageQueue中有两个主要的方法enqueueMessage()和next(). enqueueMessage()源码如下

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        /*这里msg必须有target,止于handler和msg是何时关联   的,后面分许*/
    }
    ...
    synchronized(this) {
        /**
            这里就是单链表的插入操作,不做具体分析
        */
    }
}

下面看next()方法的源码

Message next() {
    /** 取出消息,并出链表中移除*/
    ...
    for (;;) {
        /** 这里的for循环是在等待消息,没有消息就阻塞。消息一旦出现,next就会返回这条消息,并从链表中将其移除*/
    }
}

对于Message和MessageQueue, 前者是线程间传递内容的载体,后者是前者队列的维护。

  1. Looper循环者
    Looper是循环着,即是不停的查看MessageQueue中是否有新的消息,如果有就立即处理,否则一直阻塞。首先看其构造方法
private Looper (boolean queitAllowed) {
    //Looper内部有一个消息队列的引用
    mQueue = new MessageQueue(quitAllowed);
    //关联当前的thread,即looper所在的thread
    mThread = Thread.currentThread();
}

我们知道Handler的工作必须要有Looper参与,想要让一个线程成为looper线程,其实也很简单

public class LooperThread extends Thread() {
    public void run() {
    //将当前线程初始化为looper线程
        Looper.prepare();
        ...
        //开始循环处理消息队列
        Looper.loop();
    }
}

通过上面的两行核心代码,我们就可以将一个普通线程变为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");
    }
    //存储当前线程的looper对象,且值创建一个
    sThreadLocal.set(new Looper(quitAllowed));
}

当Looper.loop()调用之后,Looper才真正的开始工作,不断的从消息队列中取出消息并执行

public static void loop() {
    final me = myLooper(); //获取当前线程的Looper对象
    if (me == null) {

    }
    //当前looper的MessageQueue
    final MessageQueue queue = me.mQueue;
    ...
    //这里开始进入循环,取消息,处理
    for (;;) {
        Message msg = queue.next();

        /**交给Message关联的handler去处理,这里将消息处理的过程有切换到Looper线程*/
        msg.target.dispathMessage(msg);
        ...
    }   
}

实现Looper线程的过程分析完了,从这里我们起码可以看出三个结论
1. 每个线程只能有一个Looper对象,因为这是一个ThreadLocal的变量
2. 调用Looper.prepare()方法可以使一个线程变为looper线程
3. Looper中有一个MessageQueue的实例,loop()方法,进行循环取消息。
分析到这里,MessageQueue,Looper, 当前的线程都关联起来了,下面就是消息的发送和处理,以及Handler和Msg的关联,Handler的消息队列和Looper内部的消息队列相互对应起来

4.异步处理Handler
Handler的主要工作是在子线程中发送消息和在Looper线程中接受消息并处理之。首先看Handler的构造方法

public Handler(boolean async) {
    this(null, async);
}

public Handler(CallBack callback, boolean async) {
    ...
    //Handler持有Looper的引用
    mLooper = Looper.myLooper();
    /**Handler的queue和Looper.mQueue关联,这样就是Handler消息入队和Looper出队的是同一个队列*/
    mQueue = mLooper.mQueue;
    ...
}

/** 这个构造方法可以用来为Handler指定Looper线程*/
public Handler(Looper looper, Callback, boolean) {
    mLooper = looper;
    mQueue = looper.mQueue;
    ...
}

从上面的构造函数可以看出,Handler持有Looper的引用,也持有MessageQueue的引用,而且Handler和Looper持有的m
Queue是同一个消息队列的引用,这样才能实现消息的发送和接受处理。接下来看Handler发送消息的过程

public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg. 0);
}

public final boolean sendMessageDelayed(Message, long) {
    ...
    return sendMessageAtTime(...);
}

 public boolean sendMessageAtTime(Message msg, long uptimeMillis)  {
    ...
    return enqueueMessage(...);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    /**这里指定msg的执行这为当前的handler对象,handler和msg就此也关联起来了*/
    msg.target = this;
    ...
}

上面就是Handler发送消息的过程,在发送消息的同时执行该消息的处理着,也就是发送该消息的Handler对象,该Handler对象位于Looper线程中。
注:一个Looper线程只能有一个Looper对象,这个是ThreadLocal决定的,而一个线程可以有多个Handler对象,当然这多个Handler对象是共同使用一个消息队列的,看源码可知。所以在使用时可以根据任务的不同分为多个Handler进行处理。
在Handler中还存在一个Callback接口,这个有什么作用呢?

/** 当你不想继承Handler创建一个类时,可以使用这个接口*/
public interface Callback {
    public booelan handleMessage(Message msg);
}

所以Handler中也有相应的构造方法

public Handler(Callback callback) {
    this(callback, false);
}

一上就是对于Handler机制的分析,这里没有说如何使用Handler进行UI更新和子线程之间进行数据通信,,仅仅是从源码实现的角度进行分析。 下面看两个特殊的Looper线程

5.HandlerThread
这是Android系统为我们实现的一个Looper线程,继承Thread

public class HandlerThread extends Thread {
    ...
    public void run() {
        ...
        //循环初始化
        Looper.prepare();
        ...
        //进入消息循环
        Looper.loop();
    }
}

HandlerThread的实现,和之前分析Looper时说过的让一个子线程成为一个looper线程的步骤是一样的。所以,我们在使用时不需要自己去创建一个Looper线程,系统已经为我们实现好了,只要继承HandlerThread即可。 而在使用时只是需要将Handler的Looper关联对该looper线程即可。

MyHandlerThread ht = new MyHandlerThread();
Looper looper = ht.getLooper();
Handler handler = new Handler(looper);

上面可以看出HandlerThread使用起来很方便,这里不在分析。

注:
在使用Handler的过程中,最容易出现的就是内存泄漏的问题。那么为什么会出现内存泄漏呢?
1) 在回头看一下Handler,Looper,Mesage它们的关系就会明了。Handler持有Looper和MessageQueue的引用,Message持有Handler的引用,MessageQueue也持有Message的引用,Looper持有MessageQueue的引用。如果某一个引用一直占用不能释放,就不能被回收,导致内存泄漏。
2) 继承Handler,并作为内部类时,Handler持有Activity的引用,如果子线程中的任务比较耗时,且在子线程完成之前,activity就退出,则Handler持有的activity,activity虽然已经销毁但内存还是得不到释放,可以使用静态内部类加弱引用的方法解决之(静态内部类是不持有外部类引用的)。比如:

static class MyHandler extender Handler {
    WeakReferene<Activity> mActivityRef;
    public MyHandler(Activity activity) {
        mActivityRef = new WeakReference<>(activity);
    }
    public void handleMessage(Message msg) {
        if (mActivityRef.get() != null) {
            //处理消息
        }
    }
}

3) 对于Message对象的获取

/** 可以通过这种方式获取*/
Message msg = new Message();

/** 推荐使用这样的方法来新建一个msg*/
Message msg2 = Message.obtain()

好了,关于Android的消息机制,其实我们打交道最多的就是上层的Handler接口,对于Looper,MessageQueue怎么实现的关心比较少,但是这样的线程间通信的思想是必须要理解滴。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值