Android--源码分析Handler巧妙的观察者模式及手写简单实现Handler

说到Handler,绝大多数人都知道,它就是一个死循环,不断的遍历消息队列,通过handler将消息入队,循环中有消息就取出来消费掉,以实现线程间的通信。

说个题外话,记得有一次面试,面试官问我线程间怎么通信?我当时就很疑惑,线程间本来就是资源共享的,谈何怎么通信,调用线程的方法或者改变线程的变量值就可以实现通信了,只不过需要自己做一些线程同步的处理。对我们来说,Hanlder机制只是安卓SDK封装了一个线程通信的工具罢了,它通过生产者消费者模式处理了多线程同步,当然了它封装的功能很强大

网上关于Handler的源码分析已经有很多了,自己看了源码后,也想做个总结,今天利用时序图和流程图来做一个简单分析,尽量使用简洁易懂的方式来帮助理解

一、Handler源码分析

1.Looper创建及启动轮询

Java程序的入口为main函数,每个App都是一个单独的Java程序,App启动流程涉及到底层dalvik/art虚拟机的fork进程,跨进程通信等,暂不深入探究。如果想要程序一直运行,那么main函数不能结束执行,还记得之前的Java--线程文章么,对于底层OS而言,main函数是一个进程也是线程,也就是我们常说的主线程

安卓中主线程的入口位于ActivityThread类中的main函数,代码如下,过下即可:

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

main函数中,我们需要关心的只有下面的两个方法:

1.1 Looper.prepareMainLooper方法

main函数中调用Looper.prepareMainLooper方法,做了一些初始化操作:

    public static void prepareMainLooper() {
        //初始化操作
        prepare(false);
        ...
    }

prepareMainLooper方法又调用了prepare方法,其中比较核心的是使用ThreadLocal保存当前线程的Looper,并在Looper的私有构造方法中,实例化了消息队列

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        //如果一个线程中调用两次prepare,抛出异常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //将Looper存入ThreadLocal
        sThreadLocal.set(new Looper(quitAllowed));
    }

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

关于ThreadLocal,Looper中方法基本都是静态的,ThreadLocal对象也是静态的,静态对象本该是线程间共享的,但ThreadLocal内部对线程进行了区分,其set方法可以根据当前线程来存放当前线程的私有工作内存,相应的get方法是获取当前线程存放的私有工作内存,这里相当于将线程和Looper进行了一对一关系的绑定,以便在线程中调用获取当前线程的Looper
我们通过Looper.myLooper方法就可以获取到调用该方法时运行的线程的Looper,主要提供给Looper的loop方法和Handler的空构造方法使用,后续分析中可以看到该方法的调用:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
1.1 Looper.loop方法

为了使main函数一直执行,Looper.loop方法会一直轮询,我们查看它的具体代码实现,由于代码太多,我做了简化:

    public static void loop() {
        //获取当前线程的Looper
        final Looper me = myLooper();
     ...
        final MessageQueue queue = me.mQueue;
     ...
        for (;;) {
            //阻塞获取Message
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

      ...
            try {
                //分发消息
                msg.target.dispatchMessage(msg);
                ...
            } catch (Exception exception) {
                ...
                throw exception;
            } finally {
                ...
            }
          ...
        }
    }

其中queue为MessageQueue对象,即调用了MessageQueue的next方法,next方法的实现也是个轮询,一旦MessageQueue中有消息了并且没有延迟执行,那么返回该队列头的Message给Looper:

    //类似链表的头节点
    Message mMessages;

    Message next() {
        ...
        // 轮询获取Message
        for (;;) {
            ...
            synchronized (this) {
                ...
                Message prevMsg = null;
                Message msg = mMessages;
                ...
                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;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        // 获取到后,将Message返回
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

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

                ....
            }

            ...
        }
    }

Looper.loop方法中的msg.target.dispatchMessage(msg)我们回头再来看

1.3 Looper创建和轮询的时序图

如果你对上面的流程还有不清楚的地方,可以配合时序图来方便理解,或者直接看时序图

Looper创建和轮询

流程总结:
1.主线程调用Looper.prepareMainLooper(),最终实例化了Looper对象,构造时又实例化了成员变量MessageQueue,并使用ThreadLocal将Looper和当前线程绑定
2.主线程调用Looper.loop()方法,从ThreadLocal中获取当前线程的Looper,开启轮询,不断从MessageQueue获取Message,获取后调用 msg.target.dispatchMessage(msg),往复轮询操作

2.Handler发送消息与接收消息

我们常常会在Activity中定义一个Handler成员变量(实际上不推荐直接new Handler(),容易内存泄漏),并重写handleMessage方法来接收消息,接下来分析Handler源码

2.1 Handler空构造

空构造函数如下,Activity中是主线程,所以 Looper.myLooper()返回的就是主线程创建的Looper

    @Deprecated
    public Handler() {
        this(null, false);
    }

    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
2.2 消息入队

MessageQueue也是主线程的消息队列,当别的线程操作这个handler时,就可以把消息往这个MessageQueue中入队,而主线程Looper在不断的轮询这个消息队列

Handler的sendMessage方法的调用链如下:

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

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

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        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);
    }

我们重点关注enqueueMessage方法,其中将Message的target设置成了当前Handler自己,最后将该msg入队MessageQueue :

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //将target设置为当前Handler
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

至此,就和Looper.loop方法关联起来了,Looper.loop方法最终调用了 msg.target.dispatchMessage(msg),就是调用了Handler的dispatchMessage方法

2.3 Handler dispatchMessage方法handleMessage方法

dispatchMessage方法只是做了转发,最终调用了handleMessage方法,而我们正是重写了该方法

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
2.4 Handler发送消息与接收消息时序图

在上面的时序图中,增加Handler发送和接收消息,有些调用链就简略掉了:

Handler机制

我们目前只争对主线程进行了分析,但不难理解的是,Handler机制除了主线程外,也可以作为其他线程消息通信的工具,只需要在其他的线程中实例化新的Looper,并且创建对应Looper的Handler就可以实现非主线程的消息通信,十分便利。这套架构的精髓是handler即作为被观察者,又作为观察者
Handler机制简单理解

二、手写Handler机制

有了上面的基础后,简单的手写一个Handler机制

1.定义Message类
/**
 * Created by aruba on 2021/11/13.
 */
class Message implements Comparable<Message> {
    public int what;
    public Object msg;
    protected Handler target;
    public int when;

    public Message() {
    }

    public Message(int what, Object msg) {
        this.what = what;
        this.msg = msg;
    }

    @Override
    public int compareTo(Message message) {
        return when - message.when - 1;
    }
}

2.定义Looper
import java.util.PriorityQueue;

/**
 * Created by aruba on 2021/11/13.
 */
public class Looper {
    PriorityQueue<Message> messageQueue;
    static ThreadLocal<Looper> mThreadLocal = new ThreadLocal<>();
    boolean quited = false;

    private Looper() {
        messageQueue = new PriorityQueue<>();
    }

    /**
     * 实例化当前线程Looper
     */
    public synchronized static void prepare() {
        if (mThreadLocal.get() != null) throw new RuntimeException("looper has created");
        mThreadLocal.set(new Looper());
    }

    /**
     * 线程轮询
     */
    public static void loop() {
        //获取当前线程的looper
        Looper looper = mThreadLocal.get();
        if (looper == null) throw new RuntimeException("looper has not created");

        PriorityQueue<Message> queue = looper.messageQueue;
        while (true) {
            synchronized (looper) {
                Message top = queue.poll();
                if (top != null) {//调用handler的handleMessage方法
                    top.target.handleMessage(top);
                }
            }

            if (looper.quited) {//退出
                break;
            }
        }
    }

    /**
     * 消息入队
     *
     * @param msg
     */
    public synchronized void enqueueMessage(Message msg) {
        messageQueue.offer(msg);
    }

    public void quite() {
        quited = true;
        mThreadLocal.remove();
    }
}

3.定义Handler
/**
 * Created by aruba on 2021/11/13.
 */
public class Handler {
    final Looper mLooper;

    public Handler(Looper mLooper) {
        this.mLooper = mLooper;
    }

    public Handler() {
        mLooper = Looper.mThreadLocal.get();
    }

    public void enqueueMessage(Message msg) {
        msg.target = this;
        synchronized (mLooper) {
            mLooper.enqueueMessage(msg);
        }
    }

    public void handleMessage(Message msg) {

    }

}

4.测试方法:
public class TestHanlder {
    public static void main(String[] args) {
        Looper.prepare();

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                System.out.println("what:" + msg.what + " msg:" + msg.msg);
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    handler.enqueueMessage(new Message(i, "hello"));
                }
            }
        }).start();

        Looper.loop();
    }
}

结果:
what:0 msg:hello
what:1 msg:hello
what:2 msg:hello
what:3 msg:hello
what:4 msg:hello
what:5 msg:hello
what:6 msg:hello
what:7 msg:hello
what:8 msg:hello
what:9 msg:hello

Process finished with exit code -1

我都不敢相信三个类就搞定了,当然系统的Handler机制还要复杂很多,其中包括没有消息时,线程进入挂起状态,以释放cpu资源,这些操作在native层,感兴趣的可以查阅这篇博客:详细解读Android Handler中Native挂起唤醒逻辑实现
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容