Android开发

源码阅读#Handler(上)使用方法与运行原理

本文主要是介绍源码阅读#Handler(上)使用方法与运行原理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

本文重点:

  • HandlerThreadActivityThread为例,讲解Handler的用法;
  • 分析Handler中的重要角色以及运行过程;
  • 最后讲一个自己遇到的Handler使用不当的坑。

代码以 Android API 28 (Android 9)为准,如有不当,希望指正😀

🍊 🍋 🍌 🍉 🍇 🍓 🍈 🍒 🍑 🍍 🥥 🥝 🥭 🍏 🍎 🍐

一、Handler 基本用法

Handler的作用就是跨线程执行任务,可以在A线程中通知B线程去执行相应的任务。我们从Android的自带组件来分析Handler的使用方式,以线程种类作为区分——工作线程主线程

工作线程

这里说的工作线程就是指所有非主线程的线程。工作线程的典型用法就是HandlerThread,先简单介绍下HandlerThread是什么:

Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.

它是一个装备了Handler的线程类(继承Thread,自动创建Looper用于创建Handler),我们可以使用它的Handler发送消息,HandlerThread会为我们执行这个消息所代表的任务,当然,使用之前需要用start方法来启动这个线程。

当我们调用start方法后会触发run方法,在HandlerThreadrun方法中,它启动了Looper,而在另一个方法——getThreadHandler中创建了Handler

@Override
public void run() {
    mTid = Process.myTid();
    // 创建Looper
    Looper.prepare(); 
    synchronized (this) {
        // 保存Looper引用,用于创建Handler
        mLooper = Looper.myLooper(); 
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    // 启动Looper
    Looper.loop(); 
    mTid = -1;
}

public Handler getThreadHandler() {
    if (mHandler == null) {
        // 获取mLooper,创建Handler(注意要先启动线程,不然会报错的喔)
        mHandler = new Handler(getLooper());
    }
    return mHandler;
}
复制代码

先看run方法是如何启动Looper的:

1、创建Looper:Looper.prepare()(必须步骤)

首先我们来看Looper.prepare()方法的相关代码:

// 一定要先调用prepare()方法,sThreadLocal.get()才会返回不为空的值喔
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed); // 消息队列
    mThread = Thread.currentThread();       // 当前线程
}

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));
}
复制代码

Looper.prepare()方法中系统又调用了它的重载方法,其中创建了Looper对象;在Looper的构造方法中,消息队列MessageQueue被创建,并且记录了当前线程mThread。创建的Looper对象使用一个ThreadLocal静态变量“保存”(本质上不是ThreadLocal保存了Looper,而是Thread,继续往下看~)。

这里细心的人会发现问题,最后一个prepare方法是一个静态方法,调用时,如果sThreadLocal中已有对应的Looper对象就会抛出异常,没有则会创建一个新的Looper但是set方法只传入了Looper对象,get方法没有传入参数,这个存取的依据到底是什么呢?我在任何地方调用这个静态方法,什么时候算是重复创建了Looper呢?

我们可以先看抛出的这个异常:"Only one Looper may be created per thread" ,意思是一个Thread中只能创建一个Looper对象(要注意线程是没有自带Looper的,需要主动调用Looper.prepare()创建),那我们就有很大把握说这个get&setThread有关系。

这里先记住一个结论:set是将Looper对象存入当前线程中,get是获取当前线程中的Looper对象,而ThreadLocal对象是作为查找索引存在的,Looper对象就是对应的这个索引对应的值。ThreadLocal的实现很特别,可以看下:

源码阅读#ThreadLocal完全分析

总结一下,Looper.prepare()方法给当前线程创建了Looper对象,并保存在当前线程中,可以使用ThreadLocal对象获取当前线程对应的Looper

2、获取创建的Looper对象:Looper.myLooper()

调用Looper.myLooper()并不是必须的,HandlerThread里会获取Looper引用,在我们调用getThreadHandler方法时为我们生成Handler

@NonNull
public Handler getThreadHandler() {
    if (mHandler == null) {
        mHandler = new Handler(getLooper());
    }
    return mHandler;
}

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
        
    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}
复制代码

isAlive()Thread中的方法,代表线程是否在运行。getLooper()方法的逻辑是:线程不在运行时就返回null,在运行时会等待mLooper被赋值后返回mLooper对象,这里的synchronized是配合上面run方法中的这段代码的:

synchronized (this) {
    mLooper = Looper.myLooper();
    notifyAll();
}
复制代码

这里是为了保证mLooper获取过程的原子性(顺便简单复习下Java并发的三大特性),如果线程已经开始运行而Looper未创建完成,此时getLooper会等待Looper完成创建。

根据以上的代码我们知道,只有当我们将HandlerThread这个线程运行起来了,我们才能通过getHandler()获取对应的Handler对象。

3、启动Looper开始无限循环:Looper.loop()(必须步骤)

我们来看loop方法:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // 无限循环
    for (;;) {
        Message msg = queue.next(); // might block(获取消息,可能会阻塞)
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        msg.target.dispatchMessage(msg); // 分发消息
        msg.recycleUnchecked(); // 回收消息对象
    }
}
复制代码

首先,myLooper()获取当前线程中的Looper对象,如果为空就会抛出异常(提示必须先调用Looper.prepare()方法);然后获取Looper对象中的消息队列对象;最后在无限循环中不断地获取消息分发消息回收消息对象。当获取的消息为空的时候,循环就退出了。

这一步完成后,一个线程中的Looper就成功启动了

--- 🍋🍋🍋🍋🍋🍋 ---

以上就是HandlerThread的启动过程,示范了如何在工作线程中启动Looper并创建Handler,分为以下四步:

  • 启动线程:thread.start()
  • 在线程中调用Looper.prepare()创建Looper
  • 在线程中调用Looper.loop()启动Looper
  • 创建Handler

个人觉得HandlerThread其实挺鸡肋的,我们平时调用Handler有三种方式:

  1. sendMessage发送消息,需要重写Handler#handleMessage方法处理对应的消息
  2. sendMessage发送消息,需要在构造方法中传入Handler#Callback
  3. post一个Runnable

第1、2种在HandlerThread中无法使用,因为HandlerThread已经为我们创建了Handler,我们无法传入Callback也无法重写handleMessage方法,只能用第三种方法,但是第三种方法又与创建一个Thread并实现他的run方法没有太多差别,所以其实HandlerThread挺鸡肋的......并不常用😂 感觉只是Android出给大家看的一个示范。

(还有一点,我们使用Handler其实更多的是异步线程回调主线程,更多的是在主线程中创建Handler,然后在异步线程中调用sendMessage

主线程

我们知道Java进程被创建后都会有自己的主线程,Android系统fork出进程后会调用ActivityThreadmain方法创建ActivityThread对象,并关联主线程,用它来管理主线程中的事物,而应用进程会与系统进程进行通讯,使用的是Binder,当系统进程调用应用进程的Binder对象时,最终会在应用进程中的Binder线程池中执行的,为了在主线程执行相应方法,此时系统会使用一个Handler发送消息到主线程执行相应的任务,比如启动Activity、绑定Application等等,这些方法暴露出可重写的方法,比如ActivityonCreateonDestory。大致过程如下:

举个例子:Binder远程代理是IApplicationThread,调用到ApplicationThread(继承于IApplicationThread.Stub)对象中的对应方法,然后使用H(继承于Handler,绑定主线程Looper)发送消息,在H#handleMessage中执行相应方法。

接下来我们看这个H的创建过程。

--- 🍌 🍌 🍌 🍌 🍌 🍌 ---

我们先来看ActivityThread是怎么初始化Handler的:

final Looper mLooper = Looper.myLooper(); // 这里保留了主线程Looper的引用
final H mH = new H(); // 系统主要使用这个Handler从binder线程回调主线程
static volatile Handler sMainThreadHandler;  // set once in main()

public static void main(String[] args) {
    ......
    Looper.prepareMainLooper();                     // 1
    ActivityThread thread = new ActivityThread();   // 2
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();                                  // 3
    ......
}

final Handler getHandler() {
    return mH;
}
复制代码

这里要注意,ActivityThreadmain方法就是在主线程上调用的,所以此时线程是已启动的

1、创建主线程Looper:Looper.prepareMainLooper()

main方法中首先调用了Looper.prepareMainLooper(),这是专门提供给主线程用的方法(在这之后我们可以调用Looper.getMainLooper()在任何地方获取主线程的Looper):

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}
复制代码

这里的synchronized不同于getLooper中的,因为getMainLooper是个静态方法,这里锁的是Looper.class(锁类对象)。

我们可以看到prepareMainLooper本质上和Looper.prepare()方法调用的方法是一致的,只不过保存了主线程的Looper引用——sMainLooper,并保证sMainLooper只初始化一次,并且这个Looper是不可退出的(只能等到进程退出了才行)

2、创建Handler:H

创建ActivityThread的时候首先将主线程Looper对象赋值给了mLooper,然后创建了H,它是继承于Handler,主线程的各种重要事件都要经过它的手,比如Activity的生命周期、Service的生命周期等。

我们看到,这里其实还有一个静态的Handler——sMainThreadHandler它其实就是mH,但是是静态的,在使用静态Handler的时候我踩了一个坑,后面我会讲到。

3、启动Looper:Looper.loop()

启动Looper只需调用这个方法,前面说过,这里就不多说了。

其实主线程本质上与工作线程创建Handler的流程一致,只是主线程多了两个限制:

  • 主线程只有一个(sMainLooper只能初始化一次)
  • 不能终止loop过程(quitAllowedfalse

二、Handler运行原理

前面说到Looper启动了一个无限循环,要想了解Handler的运行原理,就要搞懂这个循环里都做了什么。在这之前,我们要了解下Handler发送的“消息”是什么,消息队列又是怎么组成的。

1、什么是“消息”,消息队列如何组成

我们所说的消息就是Message的对象,我们在用Handler发送消息的时候可以设置消息类型what(需要自定义),消息传参arg1arg2obj,同时我们可以通过setData设置Bundle进行数据传送:

public int what;
public int arg1;
public int arg2;
public Object obj;
/*package*/ Bundle data;
复制代码

Message中会使用对象池来重复利用Message对象,这里的对象池其实是一个单向链表Message中会持有这个链表的首个消息,即下面的sPool变量,而每个消息都会持有它的下一个消息next

// sometimes we store linked lists of these things
/*package*/ Message next;

public static final Object sPoolSync = new Object(); // 锁对象
private static Message sPool;                       // 链表头(Message对象链表的头部)
private static int sPoolSize = 0;                   // 链表长度
private static final int MAX_POOL_SIZE = 50;        // 最大长度
复制代码

这样就可以形成一个Message链(MessageQueue中的消息也是以此数据结构存储的,它会持有队列头的Message对象,变量名为mMessages),在对Message对象调用recycleUnchecked后,该对象就会被回收,我们可以通过一系列的静态方法obtain来获取这些回收的对象。

void recycleUnchecked() {
    ......
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;   // 插入链表头部 step1
            sPool = this;   // 插入链表头部 step2
            sPoolSize++;    // 长度+1
        }
    }
}

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;  // 取出消息 step1
            sPool = m.next;     // 取出消息 step2
            m.next = null;      // 取出消息 setp3
            m.flags = 0; // clear in-use flag
            sPoolSize--;        // 长度-1
            return m;
        }
    }
    return new Message();
}
复制代码

这里还要提一点,就是Message其实会持有发送它的Handler对象的引用:

/*package*/ Handler target;
复制代码

我们常说内存泄漏,这里的target就是元凶,Message如果被发送出去,会持有Handler的引用,如果此时Handler持有Activity的引用,就有可能出现内存泄漏了。

2、Loop无限循环都做了什么

好了,了解了什么是“消息”,什么是“消息队列”,接下来我们就来看看Looper的无限循环中都做了什么。我们先来复习下代码:

    for (;;) {
        Message msg = queue.next(); // might block(1、获取消息,可能会阻塞)
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        msg.target.dispatchMessage(msg); // 2、分发消息
        msg.recycleUnchecked(); // 3、回收消息对象
    }
复制代码

2.1、取消息的过程:queue.next()

首先是queue.next()方法,它是会阻塞的,而且在获取到的Message对象为空时会退出循环。具体如何阻塞?这里我们直接看MessageQueue#next的源码:

Message next() {
    // MessageQueue中有quit方法,调用后会清除所有Message并使用native方法将mPtr置为0
    // 这里 return null 后 Looper中的循环也就停止了
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    // 等待执行的IdleHandler个数
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    // 这个是一个等待时间,下面会用到
    int nextPollTimeoutMillis = 0;
    // 无限循环取消息
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            // 将进程中未执行的命令一并送往CPU处理
            Binder.flushPendingCommands();
        }
        // 这里调用了native方法,在底层进行阻塞
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            
            // 如果Message的target是空的,就会去寻找消息队列中的“异步消息”,这个机制叫同步屏障。
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                    // isAsynchronous 其实只是一个标志位,可以用set方法设置,代表这个消息为异步消息(有时候并不是真的异步)。
                } while (msg != null && !msg.isAsynchronous());
            }
            // 如果上面找到了异步消息就执行异步消息,否则执行消息队列的头部消息(消息插入时是插入到队列尾部的)
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        // 这里去除的是异步消息
                        prevMsg.next = msg.next;
                    } else {
                        // 除去头部消息
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg; // 返回Message
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose(); // 退出
                return null;
            }

            // 当找不到Message或者“Message is not ready”的时候就会进入下面的步骤
            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            // 有IdleHandler,初始化mPendingIdleHandlers
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}
复制代码

这段代码有几个重要的步骤:

  1. nativePollOnce阻塞。
  2. 判断是否有同步屏障消息,有的话去寻找异步消息。
  3. 如果获取到消息,不论是普通消息还是异步消息,直接返回
  4. 如果没有取到消息或者消息还没准备好,执行IdleHandler

关于第一点,可以参考这篇文章:Android 中 MessageQueue 的 nativePollOnce;关于第二点和第四点,我的下篇文章会分析: 源码阅读#Handler(下)同步屏障与IdleHandler

2.2、分发消息的过程:msg.target.dispatchMessage(msg)

这里调用的是HandlerdispatchMessage方法:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg); // Handler的post方法发送的带Runnable对象的Message
    } else {
        if (mCallback != null) {// 构造方法中传入的Callback
            if (mCallback.handleMessage(msg)) {// 这里返回true就不会继续执行下面的handlerMessage了
                return;
            }
        }
        handleMessage(msg);
    }
}

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

public void handleMessage(Message msg) {}
  
public interface Callback {
    public boolean handleMessage(Message msg);
}
复制代码

这里有三种处理方式:

  1. 首先是处理了Messagecallback,这个callback是个Runnable对象,当我们调用Handlerpost方法的时候会传入一个Runnable,然后发送一个带上这个RunnableMessage对象,最终会在dispatchMessage里执行。
  2. 在构造方法中我们可以传入一个Callback对象,它也有一个handleMessage方法,当这个方法返回true的时候,就不会调用Handler自身的handleMessage方法了,代表被消费掉了(这里和点击事件的分发逻辑有点像)。
  3. 调用Handler自身的handleMessage方法处理消息。

2.3、回收消息对象:msg.recycleUnchecked()

回收消息对象的过程上面第1小点分析过,这里就不再赘述啦

--- 🥝 🥝 🥝 🥝 🥝 🥝---

3、Handler运行原理总结

整理了一下方法调用的顺序:

我们以创建线程A为开始,先在线程A中创建LooperLooper.prepare());然后启动LooperLooper.loop())并创建Handler(可以传入Callback);创建Handler后我们就可以调用sendMessage方法或者post方法发送消息,最后调用到handleCallback或者handlerMessage方法处理消息。

图中区分了线程A和线程B,所有方法都被放在了执行该方法的线程中,记得之前被问过“MessageQueue在哪个线程执行?”的问题(是不是有点奇葩......),回答的是“放入队列的方法是在发送消息的线程中执行的,取消息的方法是在回调线程中执行的”,一放一取,就跨了线程


到这里,Handler的基本用法与原理就讲了一部分了,知识点可能有点穿插,下一篇我会接着讲两种高级使用方法:源码阅读#Handler(下)同步屏障与IdleHandler

三、静态Handler的一个坑

先给大家看一个类:

public class BugClass{
    public static boolean b;
    public static Handler handler = new Handler();
}
复制代码

这个handler的作用是回调主线程,写这个类的人希望这个handler能在主线程被创建,但是程序在运行的时候会报这个错:Can't create handler inside thread XXX that has not called Looper.prepare()。为什么呢?

答案是在App启动的时候创建了一个异步线程,在其中调用了BugClass.b,此时BugClass就会被类加载器加载,同时,静态的handler会被创建,而此时,这个异步线程是没有创建Looper的,所以就报了这个错。(当时见到这个BUG的时候也很新奇......)

最后这样解决了:

    public static Handler handler = new Handler(Looper.getMainLooper());
复制代码

最后,如果觉得这篇文章对你有帮助,也帮忙点个赞吧~

这篇关于源码阅读#Handler(上)使用方法与运行原理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!