Handler二十七问|你真的了解我吗?(下)

简介: 对于handler,你会想到什么呢?

可以多次创建Looper吗?


Looper的创建是通过Looper.prepare方法实现的,而在prepare方法中就判断了,当前线程是否存在Looper对象,如果有,就会直接抛出异常:


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));
    }
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }


所以同一个线程,只能创建一个Looper,多次创建会报错。


Looper中的quitAllowed字段是啥?有什么用?


按照字面意思就是是否允许退出,我们看看他都在哪些地方用到了:


void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
        }
    }


哦,就是这个quit方法用到了,如果这个字段为false,代表不允许退出,就会报错。


但是这个quit方法又是干嘛的呢?从来没用过呢。还有这个safe又是啥呢?


其实看名字就差不多能了解了,quit方法就是退出消息队列,终止消息循环。


  • 首先设置了mQuitting字段为true。
  • 然后判断是否安全退出,如果安全退出,就执行removeAllFutureMessagesLocked方法,它内部的逻辑是清空所有的延迟消息,之前没处理的非延迟消息还是需要取处理,然后设置非延迟消息的下一个节点为空(p.next=null)。
  • 如果不是安全退出,就执行removeAllMessagesLocked方法,直接清空所有的消息,然后设置消息队列指向空(mMessages = null)


然后看看当调用quit方法之后,消息的发送和处理:


//消息发送
    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
        }


当调用了quit方法之后,mQuitting为true,消息就发不出去了,会报错。


再看看消息的处理,loop和next方法:


Message next() {
        for (;;) {
            synchronized (this) {
                if (mQuitting) {
                    dispose();
                    return null;
                } 
            }  
        }
    }
    public static void loop() {
        for (;;) {
            Message msg = queue.next();
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
        }
    }


很明显,当mQuitting为true的时候,next方法返回null,那么loop方法中就会退出死循环。


那么这个quit方法一般是什么时候使用呢?


  • 主线程中,一般情况下肯定不能退出,因为退出后主线程就停止了。所以是当APP需要退出的时候,就会调用quit方法,涉及到的消息是EXIT_APPLICATION,大家可以搜索下。
  • 子线程中,如果消息都处理完了,就需要调用quit方法停止消息循环。


Looper.loop方法是死循环,为什么不会卡死(ANR)?


关于这个问题,强烈建议看看Gityuan的回答:https://www.zhihu.com/question/34652589


我大致总结下:


  • 1、主线程本身就是需要一只运行的,因为要处理各个View,界面变化。所以需要这个死循环来保证主线程一直执行下去,不会被退出。
  • 2、真正会卡死的操作是在某个消息处理的时候操作时间过长,导致掉帧、ANR,而不是loop方法本身。
  • 3、在主线程以外,会有其他的线程来处理接受其他进程的事件,比如Binder线程(ApplicationThread),会接受AMS发送来的事件
  • 4、在收到跨进程消息后,会交给主线程的Hanlder再进行消息分发。所以Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施,比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终执行到onCreate方法。
  • 5、当没有消息的时候,会阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗CPU资源。


Message是怎么找到它所属的Handler然后进行分发的?


在loop方法中,找到要处理的Message,然后调用了这么一句代码处理消息:


msg.target.dispatchMessage(msg);


所以是将消息交给了msg.target来处理,那么这个target是啥呢?


找找它的来头:


//Handler
    private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
        msg.target = this;
        return queue.enqueueMessage(msg, uptimeMillis);
    }


在使用Hanlder发送消息的时候,会设置msg.target = this,所以target就是当初把消息加到消息队列的那个Handler。


Handler 的 post(Runnable) 与 sendMessage 有什么区别


Hanlder中主要的发送消息可以分为两种:


  • post(Runnable)
  • sendMessage


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;
    }


通过post的源码可知,其实post和sendMessage的区别就在于:


post方法给Message设置了一个callback


那么这个callback有什么用呢?我们再转到消息处理的方法dispatchMessage中看看:


public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }


这段代码可以分为三部分看:


  • 1、如果msg.callback不为空,也就是通过post方法发送消息的时候,会把消息交给这个msg.callback进行处理,然后就没有后续了。
  • 2、如果msg.callback为空,也就是通过sendMessage发送消息的时候,会判断Handler当前的mCallback是否为空,如果不为空就交给Handler.Callback.handleMessage处理。
  • 3、如果mCallback.handleMessage返回true,则无后续了。
  • 4、如果mCallback.handleMessage返回false,则调用handler类重写的handleMessage方法。


所以post(Runnable) 与 sendMessage的区别就在于后续消息的处理方式,是交给msg.callback还是 Handler.Callback或者Handler.handleMessage


Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计?


接着上面的代码说,这两个处理方法的区别在于Handler.Callback.handleMessage方法是否返回true:


  • 如果为true,则不再执行Handler.handleMessage
  • 如果为false,则两个方法都要执行。


那么什么时候有Callback,什么时候没有呢?这涉及到两种Hanlder的 创建方式:


val handler1= object : Handler(){
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
        }
    }
    val handler2 = Handler(object : Handler.Callback {
        override fun handleMessage(msg: Message): Boolean {
            return true
        }
    })


常用的方法就是第1种,派生一个Handler的子类并重写handleMessage方法。而第2种就是系统给我们提供了一种不需要派生子类的使用方法,只需要传入一个Callback即可。


Handler、Looper、MessageQueue、线程是一一对应关系吗?


  • 一个线程只会有一个Looper对象,所以线程和Looper是一一对应的。
  • MessageQueue对象是在new Looper的时候创建的,所以Looper和MessageQueue是一一对应的。
  • Handler的作用只是将消息加到MessageQueue中,并后续取出消息后,根据消息的target字段分发给当初的那个handler,所以Handler对于Looper是可以多对一的,也就是多个Hanlder对象都可以用同一个线程、同一个Looper、同一个MessageQueue。


总结:Looper、MessageQueue、线程是一一对应关系,而他们与Handler是可以一对多的。


ActivityThread中做了哪些关于Handler的工作?(为什么主线程不需要单独创建Looper)


主要做了两件事:


  • 1、在main方法中,创建了主线程的LooperMessageQueue,并且调用loop方法开启了主线程的消息循环。


public static void main(String[] args) {
        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }


  • 2、创建了一个Handler来进行四大组件的启动停止等事件处理


final H mH = new H();
class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        public static final int EXIT_APPLICATION        = 111;
        public static final int RECEIVER                = 113;
        public static final int CREATE_SERVICE          = 114;
        public static final int STOP_SERVICE            = 116;
        public static final int BIND_SERVICE            = 121;


IdleHandler是啥?有什么使用场景?


之前说过,当MessageQueue没有消息的时候,就会阻塞在next方法中,其实在阻塞之前,MessageQueue还会做一件事,就是检查是否存在IdleHandler,如果有,就会去执行它的queueIdle方法。


private IdleHandler[] mPendingIdleHandlers;
    Message next() {
        int pendingIdleHandlerCount = -1;
        for (;;) {
            synchronized (this) {
                //当消息执行完毕,就设置pendingIdleHandlerCount
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                //初始化mPendingIdleHandlers
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                //mIdleHandlers转为数组
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            // 遍历数组,处理每个IdleHandler
            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);
                }
                //如果queueIdle方法返回false,则处理完就删除这个IdleHandler
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
        }
    }


当没有消息处理的时候,就会去处理这个mIdleHandlers集合里面的每个IdleHandler对象,并调用其queueIdle方法。最后根据queueIdle返回值判断是否用完删除当前的IdleHandler


然后看看IdleHandler是怎么加进去的:


Looper.myQueue().addIdleHandler(new IdleHandler() {  
    @Override  
    public boolean queueIdle() {  
        //做事情
        return false;    
    }  
});
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }


ok,综上所述,IdleHandler就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。


常见的使用场景有:启动优化


我们一般会把一些事件(比如界面view的绘制、赋值)放到onCreate方法或者onResume方法中。但是这两个方法其实都是在界面绘制之前调用的,也就是说一定程度上这两个方法的耗时会影响到启动时间。


所以我们可以把一些操作放到IdleHandler中,也就是界面绘制完成之后才去调用,这样就能减少启动时间了。


但是,这里需要注意下可能会有坑。


如果使用不当,IdleHandler会一直不执行,比如在View的onDraw方法里面无限制的直接或者间接调用View的invalidate方法


其原因就在于onDraw方法中执行invalidate,会添加一个同步屏障消息,在等到异步消息之前,会阻塞在next方法,而等到FrameDisplayEventReceiver异步任务之后又会执行onDraw方法,从而无限循环。


具体可以看看这篇文章:https://mp.weixin.qq.com/s/dh_71i8J5ShpgxgWN5SPEw


HandlerThread是啥?有什么使用场景?


直接看源码:


public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
    }


哦,原来如此。HandlerThread就是一个封装了Looper的Thread类。


就是为了让我们在子线程里面更方便的使用Handler。


这里的加锁就是为了保证线程安全,获取当前线程的Looper对象,获取成功之后再通过notifyAll方法唤醒其他线程,那哪里调用了wait方法呢?


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;
    }


就是getLooper方法,所以wait的意思就是等待Looper创建好,那边创建好之后再通知这边正确返回Looper。


IntentService是啥?有什么使用场景?


老规矩,直接看源码:


public abstract class IntentService extends Service {
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }


理一下这个源码:


  • 首先,这是一个Service
  • 并且内部维护了一个HandlerThread,也就是有完整的Looper在运行。
  • 还维护了一个子线程的ServiceHandler
  • 启动Service后,会通过Handler执行onHandleIntent方法。
  • 完成任务后,会自动执行stopSelf停止当前Service。


所以,这就是一个可以在子线程进行耗时任务,并且在任务执行后自动停止的Service。


BlockCanary使用过吗?说说原理


BlockCanary是一个用来检测应用卡顿耗时的三方库。


上文说过,View的绘制也是通过Handler来执行的,所以如果能知道每次Handler处理消息的时间,就能知道每次绘制的耗时了?那Handler消息的处理时间怎么获取呢?


再去loop方法中找找细节:


public static void loop() {
    for (;;) {
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg);
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
    }
}


可以发现,loop方法内有一个Printer类,在dispatchMessage处理消息的前后分别打印了两次日志。


那我们把这个日志类Printer替换成我们自己的Printer,然后统计两次打印日志的时间不就相当于处理消息的时间了?


Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }


这就是BlockCanary的原理。


具体介绍可以看看作者的说明:http://blog.zhaiyifan.cn/2016/01/16/BlockCanaryTransparentPerformanceMonitor/


说说Hanlder内存泄露问题。


这也是常常被问的一个问题,Handler内存泄露的原因是什么?


"内部类持有了外部类的引用,也就是Hanlder持有了Activity的引用,从而导致无法被回收呗。"


其实这样回答是错误的,或者说没回答到点子上。


我们必须找到那个最终的引用者,不会被回收的引用者,其实就是主线程,这条完整引用链应该是这样:


主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity


具体分析可以看看我之前写的这篇文章:https://juejin.cn/post/6909362503898595342


利用Handler机制设计一个不崩溃的App?


主线程崩溃,其实都是发生在消息的处理内,包括生命周期、界面绘制。


所以如果我们能控制这个过程,并且在发生崩溃后重新开启消息循环,那么主线程就能继续运行。


Handler(Looper.getMainLooper()).post {
        while (true) {
            //主线程异常拦截
            try {
                Looper.loop()
            } catch (e: Throwable) {
            }
        }
    }


还有一些特殊情况处理,比如onCreate内发生崩溃,具体可以看看文章


《能否让APP永不崩溃》https://juejin.cn/post/6904283635856179214


总结


大家应该可以发现,有一个问题常被问,但是全篇都没有提,那就是:


Hanlder机制的运行原理。


之所以不提这个问题,是因为要回答好这个问题需要大量知识储备,希望屏幕前的你在读完这篇之后,再结合自己的知识库,形成自己的“完美答案”


Hanlder,I Got it!


参考


《Android开发艺术探索》


https://www.zhihu.com/question/34652589

https://segmentfault.com/a/1190000003063859

https://juejin.cn/post/6844904150140977165

https://juejin.cn/post/6893791473121280013

目录
相关文章
|
消息中间件 Android开发
Handler postDelayed的实现原理
老生常谈之Handler
103 0
|
消息中间件 Android开发
Handler源码解读——handler使用时的注意事项
工作中经常会遇到从子线程发送消息给主线程,让主线程更新UI的操作,常见的有handler.sendMessage(Message),和handler.post(runnable)和handler.postDelayed(runnable, milliseconds);一直在使用这些方法,却不知道他们的原理,今天就来解释一下他们的原理。
|
存储 消息中间件 安全
Handler二十七问|你真的了解我吗?(上)
对于handler,你会想到什么呢?
122 0
Handler二十七问|你真的了解我吗?(上)
|
消息中间件 Android开发
【Android 异步操作】手写 Handler ( Handler 发送与处理消息 | Handler 初始化 | 完整 Handler 代码 )
【Android 异步操作】手写 Handler ( Handler 发送与处理消息 | Handler 初始化 | 完整 Handler 代码 )
124 0
|
Java Spring 微服务
JFinal-event v2.3.0 发布,简化 Event 事件类
说明 JFinal-event 2.x 参考 Spring 4.2.x 中 Event 的使用方式而生,为 JFinal 用户带来更多方便。其核心目标是深层次解耦,为您节约更多时间,去陪恋人、家人和朋友 :) 年初 @idoz 提议实现类似 Spring 4.2 里的 PayloadApplicationEvent,其实我早就有此想法,一直没有抽出时间来折腾。
1959 0
|
消息中间件 调度 Android开发
面试:Handler 的工作原理是怎样的?
面试场景 平时开发用到其他线程吗?都是如何处理的? 基本都用 RxJava 的线程调度切换,嗯对,就是那个 observeOn 和 subscribeOn 可以直接处理,比如网络操作,RxJava 提供了一个叫 io 线程的处理。
1165 0