Handler异步消息传递机制(三)在主线程、子线程中创建Handler,源码(Android 9.0)解析

本文深入探讨为何在子线程中创建Handler前必须调用Looper.prepare(),否则会触发异常。并解析主线程中自动调用Looper.prepare()的过程,阐述Looper与Handler的关系。
摘要由CSDN通过智能技术生成

声明:本教程不收取任何费用,欢迎转载,尊重作者劳动成果,不得用于商业用途,侵权必究!!!

目录

一、前言

二、为什么子线程不调用Looper.prepare(),创建Handler后会报错呢?

三、主线程中的Handler之前也没有调用Looper.prepare()方法,为什么就没有崩溃呢?

四、总结说


系列文章

Handler异步消息传递机制(一)Handler常用基本用法

Handler异步消息传递机制(二)在子线程中创建Handler

Handler异步消息传递机制(三)在主线程、子线程中创建Handler,源码(Android 9.0)解析

Handler异步消息传递机制(四)Handler发送消息流程,源码(Android 9.0)解析

一、前言

上篇文章 Handler异步消息传递机制(二)Handler在主线程new还是子线程new?我们在子线程创建了Handler,程序崩溃提示报错信息为:java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare(),根据错误信息然后我们试试在线程中先调用一下Looper.prepare(),再创建Handler对象。bug解决,程序不再崩溃!

                Looper.prepare();
                handler = new Handler() {
                。。。。。。

二、为什么子线程不调用Looper.prepare(),创建Handler后会报错呢?

我们来看下Handler的源码,搞清楚为什么不调用Looper.prepare()就不行呢。Handler的无参构造函数如下所示:

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

然后继续跟踪,可以看到如下代码:

    public Handler(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;
    }

可以看到,在第11行调用了Looper.myLooper()方法获取了一个Looper对象,如果Looper对象为空,则会抛出一个运行时异常,提示的错误信息正是:"Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()",

那么什么时候Looper对象mLooper才会为空呢?这就要看看Looper.myLooper()中的代码了,我们继续跟踪源码:

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

我们发现就是从sThreadLocal对象中取出Looper,然后进行return返回。

如果从sThreadLocal中get到有Looper存在就返回Looper,如果没有Looper存在自然就返回null了。

而返回null就会报如上错误,然而我们通过错误提示信息在创建Handler之前执行Looper.prepare()就不报错了。

我们自然可以推断出当执行 Looper.prepare(),Looper对象mLooper不为null,即解决了报"Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()",的异常信息。

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

通过代码我们知道,首先判断sThreadLocal通过get方法,是否能获取到 Looper对象。

如果有的话,就会提示错误信息:“java.lang.RuntimeException: Only one Looper may be created per thread”,

直译过来就是“每个线程只能创建一个 Looper对象”,你可以二次调用 Looper.prepare(); 来验证下。如果还没有Looper对象,则会创建一个新的Looper设置进去。

这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。

三、主线程中的Handler之前也没有调用Looper.prepare()方法,为什么就没有崩溃呢?

Android的主线程就是 ActivityThread,主线程的入口方法为main,我们来看一下 ActivityThread 类中 main 方法的源代码:

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

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

可以看到,它在第20行调用了 Looper.prepareMainLooper() 方法,我们继续跟踪源码,

你会发现这个方法内部调用了 Looper.prepare() 方法,代码如下所示:

   public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要像子线程那样,再手动去调用Looper.prepare()方法了。

也就是说主线程内部已经自动调用了 Looper.prepare() 方法。这样基本就将Handler的创建过程完全搞明白了。

1、从代码操作层面上来讲:

在主线程中可以直接创建Handler对象,而在子线程中需要先调用Looper.prepare()才能创建Handler对象。

2、从源码解析上来说:

创建Handler对象之前,必须拥有不为null的Looper对象。如果Looper对象为null,将会报错:"Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"

3、每个线程只能创建一个 Looper对象

四、总结说

1、为什么子线程不调用Looper.prepare(),创建Handler后会报错呢?

(1)通过源码我们知道 Handler 构造器,做了 Looper 对象是否为空的判定。

如果 Looper 对象为空,则报错误信息为:不能在没有调用 Looper的prepare方法的线程中创建Handler

(2)Looper 类的prepare方法做了相关逻辑处理和说明:

如果 Looper 对象不为空,会抛出异常提示:每个线程只能创建一个 Looper对象

如果 Looper 对象为空,它会创建一个新的 Looper 设置进去

 

2、主线程中的Handler之前也没有调用Looper.prepare()方法,为什么就没有崩溃呢?

主线程中内部调用了Looper的prepareMainLooper方法,而prepareMainLooper方法里面调用了Looper的prepare() 方法

 

3、这个时候我们必须明白一点,创建Handler对象之前,必须拥有不为null的Looper对象

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

被开发耽误的大厨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值