欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android源码解析--深入Handler机制

程序员文章站 2022-03-03 22:02:37
目录1. Looper1.1 prepare()创建Looper1.2 loop()轮询1.3 主线程的Looper1.4 主线程looper死循环为什么不会导致ANR2. Handler2.1 发送消息2.2 分发消息3. MessageQueue★ 3.1 数据结构3.2 插入消息到队列enqueueMessage()3.3 从队列轮询取出消息next()4. Message5. ThreadLocal5.1 ThreadLocal、synchronized的区别5.2 ThreadLocal源码分析6...

对于一个Android程序员来说,Handler可谓是被谈及最多的一个词也不为过。从Android开发刚兴起的时候它就是面试时不可避讳的话题,发展到现在,它的重要地位也未能被替代,很多开源框架的实现原理也都离不开它,虽然现在面试被问的比较少了,这可能是因为它被认为是Android的一个最基础的知识点了,如果连它都没搞懂,您好意思说是一个Android老手吗?不过话说回来,不少老程序员对于它虽然很熟悉,但是真要问起细节还说不死,这篇文章我们就一起回顾一下这个经典知识。废话不多说,作为一个老程序员,基本的东西还是知道的,就不用说怎么使用那些废话了,直接进入正题。

Handler机制主要由Handler、Looper、MessageQueue、Message四个类来实现的,它们各司其职,首先看看Looper类:

1. Looper

Android中最常用的使用方式是在主线程中创建一个Handler对象,子线程中耗时获取数据后发送消息给Handler,Handler收到消息后在主线程刷新页面。但是如果我们要在子线程中创建Handler则需要多两个步骤,否则会报异常:

new Thread(() -> {
    //1:Looper.prepare()
    Looper.prepare();
    subHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    Log.w("handler", "子线程创建Handler收到消息:"+msg.obj);
                    break;
            }
        }
    };
    //2:Looper.loop()
    Looper.loop();
}).start();

为什么子线程中的Handler需要这两步而主线程中创建Handler却不需要?Looper是个什么东西?prepare()loop()方法做了什么事情?

1.1 prepare()创建Looper

#### android.os.Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//将当前线程初始化为循环程序。请确保在调用此方法后调用loop(),并在循环结束时调用quit()
public static void prepare() {
    prepare(true);
}

//quitAllowed:True 消息队列可以退出(子线程创建的Handler可调用Looper.quit()退出消息队列轮询)
//             false 消息队列不能退出(主线程中创建的Handler不可以退出轮询,请看下面的prepareMainLooper()方法,传的就是false)
private static void prepare(boolean quitAllowed) {
	//从sThreadLocal中获取一个Looper对象,如果获取到则抛出一个异常,说明一个线程只能调用一次Looper.prepare()
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //★ 如果是第一次调用prepare(),为当前线程创建一个Looper对象(请看下面Looper的构造方法),放入sThreadLocal中
    sThreadLocal.set(new Looper(quitAllowed));
}
//应用程序的主循环器是由Android环境创建的,所以您不必自己调用这个函数。这个方法在下面将的ActivityThread的main方法中被调用
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
	//★ Looper构造方法中初始化了MessageQueue消息队列对象
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

从源码可以看出,prepare()方法其实就是为当前线程初始化了一个Looper对象(循环器)并放在sThreadLocal中保存,而Looper的构造方法里初始化了类型为MessageQueue(消息队列)的mQueue变量。sThreadLocalThreadLocal类的变量,它的作用是将Looper对象与线程关联起来,并保证当前线程有且仅有一个Looper对象,至于为什么要用ThreadLocal放到最后讲一下。在Looper中还有一个prepareMainLooper()方法,这个方法是系统创建应用程序是由系统调用的,其作用是为主线程创建循环器。

1.2 loop()轮询

 public static void loop() {
 	//获取当前线程的Looper对象=>sThreadLocal.get()
    final Looper me = myLooper();
    //如果没有获取到,抛异常,提示需要先调用Looper.prepare()
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取当前线程Looper对象中的MessageQueue消息队列
    final MessageQueue queue = me.mQueue;
    //...
    //★ for死循环轮询取出消息
    for (;;) {
    	//★ 从队列中取出消息,next()是一个阻塞方法,当队列中没有需要马上处理的消息会阻塞线程,直到获取到一条消息
        Message msg = queue.next(); // might block
        if (msg == null) {
        	//如果没有next()的阻塞,队列中消息为空时,轮询就停止了
            return;
        }
        //...
    	//★ Message的target就是当前消息保存的Handler对象的引用,通过handler.dispatchMessage(msg)将消息分发给Handler
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            //...
        } finally {
            //...
        }
        //...
        //★ 回收可能正在使用的消息,供Message.obtain()复用
        msg.recycleUnchecked();
    }
}

Looper的prepare()方法为当前线程创建一个唯一的Looper对象,Looper对象中维护了一个消息队列,loop()方法则开启一个for循环不断轮询取出队列中的消息,然后调用handler的dispatchMessage(msg)方法将消息发送给目标Handler处理。需要注意的是轮询取消息的动作所在的线程是调用loop()方法的线程,如果实在主线程中创建Handler则轮询也是在主线程,如果在子线程中创建的Handler,轮询动作在子线程,子线程的Handler收到消息后如果要刷新UI,需要使用runOnUiThread(new Runnable(){})切换到主线程。总之记住一条,在哪个线程创建Handler,则在那个线程收到消息。

1.3 主线程的Looper

为什么子线程中的Handler需要这两步而主线程中创建Handler却不需要?这是因为应用程序在启动时(ActivityThread的main方法)已经为主线程创建了Looper循环器,并开启了消息轮询:

#### android.app.ActivityThread 隐藏API @hide

public static void main(String[] args) {
    //...
    //★ 为主线程创建一个消息队列不能退出的Looper
    Looper.prepareMainLooper();

    //...
    ActivityThread thread = new ActivityThread();
    //
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    //...
    //★ 开启主循环器的消息轮询
    Looper.loop();
}

1.4 主线程looper死循环为什么不会导致ANR

ActivityThread的main方法中为主线程创建了Looper,并调用loop()开启了死循环轮询消息,为什么主线程出现死循环没有导致ANR?Looper在没有消息需要处理时是休眠状态(后面讲解消息队列时会涉及到怎样休眠的),这时候主线程是可以响应用户交互的。Android是由事件驱动的,我们在屏幕上所有的点击触摸交互都是一个事件,这些事件都会被放入主线程的消息队列中,等待Looper轮询分发事件,这样才有了组件的声明周期、事件处理等回调,其实都是对loop到的消息的处理。也正是因为Looper的死循环,才没有导致主线程退出(线程run方法执行完就退出了)。什么情况下会导致ANR?我们在处理消息事件时(生命周期方法、handleMessage())出现耗时操作,系统发现looper长时间不能正常轮询也不能阻塞休眠时就会发生ANR。

接下来我们看看怎样往消息队列中插入消息以及Handler是怎样收到消息的。

2. Handler

Handler handler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        //处理消息
        switch (msg.what){
            case 1:
                Log.w("handler", "主线程创建Handler收到消息:"+msg.obj);
                break;
        }
    }
};
//★ 获取message实例的方式有很多,应该使用哪一种请看下面Message部分的讲解
//new Message对象(但是获取消息的首选方法是调用Message.obtain())
Message msg1 = new Message();
//从全局池返回新的消息实例,避免在许多情况下分配新对象。
Message msg2 = Message.obtain();
//obtainMessage()方法其实也是调用Message.obtain()
Message msg3 = handler.obtainMessage();
//对msg进行设置
msg3.what = 1;
msg3.obj = "这是一条来自Handler的消息";
//发送消息
handler.sendMessage(msg3);
//发送runnable
handler.post(new Runnable() {
        @Override
        public void run() {

        }
    });

上面的代码首先获取到一个Message对象,对其进行数据设置,然后handler.sendMessage(msg3)将消息发送出去,最后在Handler的handleMessage()中处理消息。这个过程最重要的两步就是发送消息以及消息是怎么被传到handleMessage()中的。

2.1 发送消息

#### android.os.Handler

//1. Handler构造方法
public Handler(Callback callback, boolean async) {
    //拿到调用new Handler()所属线程的Looper轮询器
    mLooper = Looper.myLooper();
    //如果没有获取到轮询器抛异常,提示需要调用Looper.prepare()初始化轮询器
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //持有轮询器的消息队列,方便Handler王其中插入和分发消息
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    //消息被处理的时间 = 当前时间+延迟的时间,SystemClock.uptimeMillis()是自开机启动到目前的毫秒数
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

//2. sendMessage(msg)、sendEmptyMessage(what)、sendMessageDelayed(msg, delayMillis)等常用发消息的方法最终都是调用sendMessageAtTime()
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
	//拿到Handler中的mQueue,其实就是Looper中维护的消息队列
    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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	//msg的target指向当前Handler对象,方便在轮询器中取出消息后分发给当前handler
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //★ 调用MessageQueue的enqueueMessage()方法将消息放到消息队列中,该方法跟踪请看MessageQueue讲解
    return queue.enqueueMessage(msg, uptimeMillis);
}

//3. 使用post()方法发送的Runnable对象将被包裹成一个Message消息,并将Runnable作为Message的callback回调
public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

Handler发送消息最终都是调用到queue.enqueueMessage(msg, uptimeMillis),也就是调用MessageQueue的方法将消息插入到队列中,详情请看MessageQueue部分讲解

2.2 分发消息

上面讲Looper.loop()方法时我们知道轮询器开启了一个for循环调用queue.next()从消息队列中取出消息,然后调用msg.target.dispatchMessage(msg)分发消息,下面我们就看看Handler的dispatchMessage(msg)方法是怎样将消息分发的:

#### android.os.Handler
//承接Looper.loop()方法中调用的dispatchMessage(msg)
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
    	//post(runnable)发送的消息将调用handleCallback()
        handleCallback(msg);
    } else {
        if (mCallback != null) {
        	/* 
        	 * 创建handler时传递了Callback对象的情况,将调用mCallback.handleMessage(msg)
        	 * 这跟重写Handler的handleMessage(msg)方法差不多,但是它的返回值如果是false表示消息没处理,将继续由handleMessage(msg)处理
        	 * new Handler(new Handler.Callback() {
        			@Override
			        public boolean handleMessage(@NonNull Message msg) {
			            return false;
			        }
			    }
        	 */
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //调用handleMessage(msg),也就是我们重写的那个方法
        handleMessage(msg);
    }
}

//post(runnable)发送的消息将执行Runnable.run()
private static void handleCallback(Message message) {
    message.callback.run();
}

消息分发就是将loop()取出的消息传递给我们处理消息的方法,需要注意的是dispatchMessage()方法中并没有做线程切换,也就是说Handler在哪个线程创建的(Looper就属于这个线程,消息轮询也在该线程)则消息最终会被发送到这个线程中,如果是在子线程创建的Handler,收到消息后需要更新UI,需要使用runOnUiThread(new Runnable(){})切换到主线程。

到此消息的发送、分发、轮询都讲完了,接下来还有一个重要的,那就是消息队列,消息在队列中是怎么排列的?消息是怎样插到队列中的?轮询从队列中取出消息又是怎样一个过程?请看MessageQueue

3. MessageQueue

★ 3.1 数据结构

MessageQueue是一种单向链表数据结构,Message中有一个next属性指向队列中下一条消息,队列排序是根据Message的when字段(消息延迟时间)来排序的,调用handler.sendMessage(msg)发送的消息,when==0表示需要立刻处理的消息,调用handler.sendMessageDelayed(msg, 1000)发送的属于延迟消息,when==距开机时间毫秒数+延迟时间。总之when最小值为0,when越小表示消息延迟时间越小越被早处理,在队列中越靠前,下图模拟一下消息队列:

                      越靠近队头的消息越早被处理 (x表示开机时间,后面+值表示延迟时间,when值越大越靠后)
						   -------------------------------------------------------
			          when   0      x+1000    x+1500    x+2000    x+3000      x+5000 
      
MessageQueue.next()