Handler到底是一个什么东东

Handler到底是一个什么东东

作为一个Android开发工程师,Handler简直是必须要了解的东西。每次面试前,Handler都会悄悄地钻到耳边对我说:“嘿,哥们,老地方见!”

果然,面试又问到了,而Handler又跑过来BB:“又被我难倒了吧!”

(内心独白,老子就不信搞不定你!)

于是便有了这篇解析。

先来几个问题

  1. 你了解Android的Handler机制吗?(我美不呲呵的说,那必须了解,一顿白话之后就有了下面的问题)
  2. 你知道Handler在安卓framework中有哪些应用吗?
  3. 你知道Handler中的同步屏障吗?
  4. 为什么在通过view.post()获取到的view宽高是准确的?post的消息不会出现在测量流程之后吗?
  5. 巴拉巴拉巴拉巴拉

行吧,在听到第一个问题的时候的信心满满到最后一个问题的灰心落寞,直接被打击的透透的。

为什么我要了解那么多,会写不就行了?

确实啊,我会用handler不就行了?作为一个画UI的,我有必要了解那么多吗?

但是我的理解是这样的,对底层了解的越透彻,写出bug的可能性就越低,因为老子知道怎么回事儿了呀。还有就是能了解能力边界,知道Handler能做什么不能做什么,这为开发其他技术方案有很大帮助。

话不多说,接下来,就要把「Handler」这王八犊子好好地扒一扒。

目录

  1. Android中的消息处理系统
  2. Handler是如何转起来的
  3. Handler在Android系统中的身影
  4. Handler的能力边界(这王八犊子都能用来做什么)

Android中的消息处理系统

在很多桌面操作系统中,都有自己的 消息处理框架,比如Windows、Android。

那么最基本的,消息处理框架,至少得有消息发送方(handler)、消息接收方(handler)、消息本身(Message)。

当消息生产速度非常快时,还需要一个存储方对消息进行暂时缓存(MessageQueue)。

而消息不是直达目标本身时,需要中间的一个调度中心(Looper),分别处理消息,方便统一调度。

这里举一个现实的例子:送快递

  1. 发快递方把物品打包,把包裹交给快递公司。
  2. 快递公司将很多人要发送的包裹统一装车运送到目标城市。
  3. 快递到达时,再将包裹分别运送给对应的收件方

当然有人说了,我就要用闪送,一次只送我的!那我只能说,你牛笔。

上面的案例中:

  1. handler就相当于一个快递员。负责收、发快递。
  2. Message就是一个快递。当然了快递也分(专送快递、普通快递、空包)
  3. MessageQueue就是一个存储快递的仓库
  4. Looper就是用来把每个快递分发给对应的快递员的,可以当做快递公司。
  5. 发快递方就是线程A
  6. 收件方就是线程B

当然了handler不仅局限于线程间通信。

Handler是如何转起来的

tips:基于API 30

接下来上一幅简单的小图来展示Handler是如何转起来的。

在这里插入图片描述

虽然这个图不是非常标准,但是它确实也描述了Handler的一整个工作机制。

那么我们就知道了,实际上除了Handler之外还有其他的几个角色,这里也让他们一并亮相吧。

  • Looper:用于开启循环,处理消息,相当于大总管。
  • MessageQueue:存储消息(实际上主要是操作Message链表),读取消息
  • Message:消息的封装,可以跨进程通信,本质是单向链表结构,并且会持有Handler(目标handler,其实就是发消息的Handler本身)。
  • Handler:用于发消息和处理消息的快递员。

那么他们真正的工作流程是什么样的呢?

如何在子线程中使用Handler

thread {
	// 1、创建Looper,必须创建了Looper,才能创建Handler 
    Looper.prepare()
    // 2、创建Handler
    handler2 = Handler()
    // 3、让Looper进入循环状态
    Looper.loop()
}.start()

总结下来的步骤就是:

  1. 创建Looper,Looper.prepare()。
  2. 创建Handler,用于发消息和接收消息。
  3. 进入循环,Looper.loop()。

接下来挨个步骤开始,深入源码,进行分析。

创建Looper

想要发送消息,先得有一个Looper才行。那么为什么一定要Looper.prepare();

那我们便深入源码看看。

发现Looper只有一个私有的构造方法:

private Looper(boolean quitAllowed) {
    // 创建MessageQueue,传入是否可退出,啥?还有不可退出的?
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

奥,原来Looper没有提供可直接创建对象的方法呀,(要不我通过反射自己创建一个吧!你可以试试!)

那么我们再来看看prepare方法

public static void prepare() {
	prepare(true);
}

private static void prepare(boolean quitAllowed) {
	// 判断当前线程中是否已经存在Looper对象,若存在则报错。
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 创建Looper对象并放入内部的静态对象sThreadLocal中。
    sThreadLocal.set(new Looper(quitAllowed));
}

由prepare方法发现,原来Looper提供了这个prepare方法,方便开发者创建Looper对象,那么它为什么要自己创建,而且只允许一个线程中创建一个呢?

Looper是死循环获取消息的,所以创建多个没有意义,白白浪费空间罢了。而这里我们看到了传递进来的quitAllowed默认也都是true,而可传递参数的prepare方法又是私有的,没法直接调用到。

内部又使用了ThreadLocal对象存储了Looper对象,可以方便线程中其他方法直接通过Looper.myLooper()获取对象。

创建Handler

Handler不像Looper,它主要通过获取当前线程中的Looper或者指定的Looper,来进行消息发送。

所以创建Handler时,可以指定Looper,也可以不指定(不指定时,当前线程汇总必须得有Looper对象),

在调用了Handler的构造方法(非主动传递Looper对象)之后,最终会调用到如下 构造方法:

public Handler(@Nullable Callback callback, boolean async) {
    .......
    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;
}

这里我们发现Handler中的构造方法中会存在一个async参数,当这个参数为true时,handler会在调用发送消息的方法时,最终会将Message设置为异步消息。

if (mAsynchronous) {
    msg.setAsynchronous(true);
}

然而,作为App开发者,我们是没有直接的接口可以设置这个参数的,那么我们也可以通过Message直接手动设置异步消息。

Message().apply {
	// 但是该方法是在API22之后才可以用
    isAsynchronous = true
}
进入循环状态

当调用了Looper.loop();整个消息处理处理算是打通了。

等等什么就打通了,你说的那个什么MessageQueue呢?那玩意不也需要创建么?

MessageQueue其实会在Looper创建时已经被创建了,作为Looper对象的成员存在。

那loop到底干了点啥?

public static void loop() {
	// 判断是否已经创建Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }

    me.mInLoop = true;
    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;
        }

        // 获取观察者,咦,这个观察者是什么?我是不是可以添加一个观察者?不行至少常规方法不行。因为它是@hide的
        final Observer observer = sObserver;
        Object token = null;
        if (observer != null) {
            token = observer.messageDispatchStarting();
        }
        
        
        try {
        	// 拿到消息之后,通过msg中的target进行消息分发。
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
       // 回收消息
        msg.recycleUnchecked();
    }
}

由以上代码可知,Looper.loop()最本质的行为,就是不断通过queue.next();从queue中获取Message并且处理。

那么queue.next的这个方法到底有什么特别的呢?

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        // ① 进入死循环,咦这里为什么是死循环?
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            	
		//② 这里就是进入等待状态,如果是进入等待状态为什么不用wait()?而用native的epoll方法?
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                
               // ③ 如果target == null,则进入循环,怎么target可以为null吗?不是通过Handler发送消息的时候默认设置的吗?
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                        // ④ 如果msg不为空,且不是异步消息,奥,原来这里是为了找到异步消息。
                    } 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;
                        // 只有在找到了异步消息的时候这里才会不为null吧
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                        		// 这里就让下一个做头部Message了
                            mMessages = msg.next;
                        }
                        
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                // 如果要退出的话,就直接退出。
                if (mQuitting) {
                    dispose();
                    return null;
                }
                
                ……

            }
    }

针对上面的代码,虽然我们都能看得懂,但是还是会有这样那样的疑问,接下来我们一起来分析一下这几个问题。

① 进入死循环,咦这里为什么是死循环?

我们知道在Looper中,已经出现一个死循环了,难道通过那个死循环还不够吗?按照我的想法,应该直接在那里直接进行死循环就行了呀,如果存在Message就执行,不存在就直接退出就行了。

首先直接退出肯定不行,万一后面还有消息要来呢,所以此处肯定不能直接退出。那么为什么再放到MessageQueue中获取呢?

MessageQueue只是获取下一个Message,并将Message返回,Looper负责处理。这样有利于责任划分,符合单一职责划分。

而当MessageQueue中取不到数据时,需要再次获取,所以要进入死循环,直到获取到那个Message。

更主要的问题是,主线程不能退出,所以通过死循环的方式可以让主线程一直处于运行状态。

② 这里就是进入等待状态,如果是进入等待状态为什么不用wait()?而用native的epoll方法?

首先wait,是监视器用来控制线程状态的,是控制多线程访问共享资源时的方法,并不能让当前线程进入等待状态。

使用native.epoll方法可以让线程进入等待状态。

③ 如果Message的target == null,则进入循环,怎么target可以为null吗?不是通过Handler发送消息的时候默认设置的吗?

首先来说,target==null的消息有什么特别吗?当target==null时,该消息为同步屏障消息。

在google提供的API中,我们确实不能直接发送target为空的消息。

当同步屏障消息出现时,mq会读取当前屏障消息后面的异步消息,优先处理异步消息,直到同步屏障被移除。

⑥ 对呀还要时间判断,那我其他设置了延时的消息怎么办?

为什么直接到6了?你猜。

延时的消息是在哪里设置的?如果超过了我设置的延时时间怎么办?

其实是在handler发送消息,消息进入队列时,就已经根据message的执行时间点排序好了。

还有一个问题,怎么保证消息的延迟时间是正确的?如果我修改了系统时间呢?

当Message设置when属性时,是通过SystemClock.updateMills()来获取的,获取的是系统开启到现在的执行时间,所以不会跟随系统时间改变而改变,所以时间就是准确的了。

Handler在Android系统中的身影

如果读过framework源码的都知道,在Android中有一个非常重要的类,叫 ActivityThead,在它里面有这样一个H类。它就是handler的子类里面记录了包含了很多系统消息的处理。

class H extends Handler {
……

public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case BIND_APPLICATION:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit();
                break;
            case RECEIVER:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                handleReceiver((ReceiverData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case CREATE_SERVICE:
                if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            ("serviceCreate: " + String.valueOf(msg.obj)));
                }
                handleCreateService((CreateServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case BIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
                handleBindService((BindServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case UNBIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
                handleUnbindService((BindServiceData)msg.obj);
                schedulePurgeIdler();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
                ……
            }
            ……
        }
           
……
}

还有当我们在调用View.post方法时,最终也会调用到Handler的Post方法,那么这个Handler是哪里来的呢?
在这个Handler是ViewRootImpl中传递过来的。而在ViewRootImpl中也有一个自定义的Handler,这个Handler主要用于做什么,大家可以自行探讨。我们发现这个Handler创建的时候,没有调用Looper.prepare()这是为什么呢?因为它拿到的是主线程的Looper啦。

final class ViewRootHandler extends Handler {
	……
}

handler的能力边界

首先来说它只是一个跨线程、进程通信的工具。那么它当然能用来跨线程、跨进程通信了。

那么通过它在Android中身份,和各种Handler所执行的行为,还能做些什么呢?

比如某大神通过Handler机制开发出了BlockCanary这种工具,来检测代码执行效率。

有些框架通过获取主线程Looper实现了主、子线程切换。

还有人实现了在子线程弹Toast。

那它的能力边界到底在哪呢?只能说,我也不知道~~~

其他小Tips

同步屏障

同步屏障这个问题已经被问到过很多次了。

在Handler中存在发送移除同步屏障方法。postSyncBarrier()、removeSyncBarrier()

在添加同步屏障之后,handler在碰到第一个屏障消息(target==null的那个消息)之后,会优先读取屏障消息后面的异步消息进行优先执行。

那么再Android中哪里有用到呢,毕竟这个方法我们不能直接通过api调用。

在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl#scheduleTraversals()

由于 UI 更新相关的消息是优先级最高的。

Handler怎么做到准确的计时的?

通过SystemClock来获取的时间,该时间为手机启动开始到现在的运行时间,不会随着系统时间的改变而改变。

View.post为什么能获取到准确宽高,如果它post的事件在绘制之前呢?

因为同步屏障会优先执行绘制代码。

好了可以了,今天先BB这么多,等再多看看源码,多总结总结~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值