美文网首页
Handler相关看这篇就够了

Handler相关看这篇就够了

作者: 俗人浮生 | 来源:发表于2019-03-11 21:15 被阅读0次

    说起Handler,很容易就想起了Handler、Looper、Message、MessageQueue这4个东东,下面,我们通过几个问题来加深对Handler的了解和学习:

    首先,我们先做一下梳理:

    1、一个Thread里可以有多个Handler

    这个没什么好说的,你想在一个Thread里面创建多少个Handler都行,只是有没有这个必要而已。

    2、一个Thread只能对应一个Looper

    我们都知道使用Looper的第一步需要调用prepare(),下面我们直接来看一下这个方法的源码:

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

    很明显,prepare()调用了prepare(boolean quitAllowed)方法,其中的sThreadLocal是一个ThreadLocal<Looper>对象,也就是说,使用了ThreadLocal来为每一个线程创建一个Looper副本,而且是有且只有一个,从以上代码也可以直观的说明这一点,如果已经有Looper了,再调用prepare()是会报运行时错误的。(ThreadLocal相关请参考这个

    3、一个Looper只能管理着一个MessageQueue

    这个也没什么好说的,源码中有且只有一个,而Handler中的MessageQueue用的是Looper中的MessageQueue

    4、由以上就可以得出,一个Thread中的多个Handler共享了一个Looper和一个MessageQueue

    比如在同一Thread有Handler a和Handler b,那么调用a.sendMessage(msg1)和b.sendMessage(msg2)后,msg1和msg2都会进入到同一个MessageQueue中,然后由同一个Looper进行分发给自己的Handler进行消费。

    5、为什么在主线程中新建Handler不用调用Looper.prepare()和Looper.loop()方法呢?

    是的,如果我们在主线程使用Handler的话,那么我们直接Handler handler=new Handler();就可以用了,但是如果你在子线程这样的话是不行的,因为Looper都没创建,必须先调用Looper.prepare()为子线程创建Looper对象,并且之后调用Looper.loop()进行消息队列的循环。
    那么为什么在主线程可以不用呢?其实最终还是调用了,只是不用我们自己显示编码而已,我们看一下主线程的入口类ActivityThread的源码,如下:

    public static void main(String[] args) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    
            // 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();
    
            // Set the reporter for event logging in libcore
            EventLogger.setReporter(new EventLoggingReporter());
    
            // Make sure TrustedCertificateStore looks in the right place for CA certificates
            final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
            TrustedCertificateStore.setDefaultUserDirectory(configDir);
    
            Process.setArgV0("<pre-initialized>");
    
            Looper.prepareMainLooper();//在这里!!!
    
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            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");
        }
    
     public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    

    源码已经很清晰的说明这一点了(文中中文注释),这里就不做过多的说明了。

    6、HandlerThread又是什么呢?与Handler有什么关系呢?

    首先,HandlerThread继承于Thread,也就是说,它是一个线程,那么,它肯定有run方法,我们直接看一下其源码:

        @Override
        public void run() {
            mTid = Process.myTid();
            Looper.prepare();//在这里!!!
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            Process.setThreadPriority(mPriority);
            onLooperPrepared();
            Looper.loop();//在这里!!!
            mTid = -1;
        }
    

    注意上面源码中的中文注释,也就是说,HandlerThread帮我们完成了Looper的创建及相关实现什么的,这样我们就可以在子线程中轻松愉快的使用Handler了,使用起来的代码大概是这样子的:

        HandlerThread handlerThread=new HandlerThread("myThread");
        handlerThread.start();
        Handler handler=new Handler(handlerThread.getLooper());
    

    使用是很简单,但我们为什么要用HandlerThread呢?
    首先,如果是长时间多任务的话,肯定是不能放在主线程进行的,因为这样会阻塞主线程,导致触摸啊输入啊等一些动作的响应,甚至会出现ANR。
    于是,对于此类任务,我们肯定是要在子线程中去完成的,而HandlerThread让我们可以像在主线程中使用Handler那样,在子线程中方便快捷的使用Handler,所以,我们为何不用呢?
    当然,请注意一点,在使用完HandlerThread后,请调用handlerThread.quit()来释放Looper。

    7、为什么在主线程中调用了Looper.loop()这个死循环不会导致ANR呢?

    这个经常是作为一个面试题来着的,如果你平常没思考过的话,很容易会被问懵圈的,但当你想通了之后,你会发现,这个问题其实并不是个问题啊!
    首先,我们上面第5点已经说明了,我们在主线程的入口类ActivityThread调用了Looper.loop()进行了消息循环,而如果一旦退出消息循环的话,你的应用也就退出了。
    而Android 是由事件驱动的,Looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
    另一方面,主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
    当然,如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。
    因此,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。(参考这里

    相关文章

      网友评论

          本文标题:Handler相关看这篇就够了

          本文链接:https://www.haomeiwen.com/subject/quelpqtx.html