Handler相关看这篇就够了

2019-03-11  本文已影响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异常。(参考这里

上一篇下一篇

猜你喜欢

热点阅读