Android 直接在子线程中创建Handler为什么会报错

为什么在子线程中创建handler时会报以下错误

Can’t create handler inside thread that has not called Looper.prepare()

正如我们常常使用Handler从子线程中发送一个消息到UI线程中去修改UI界面,同样,也可以在子线程中创建Handler,从主线程中获取数据后利用handler发送并处理消息,让上传数据等耗时操作运行在同一条子线程中。不过,在子线程中创建Hanler有一点小小的不同:

public void init(Context con) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler(Looper.myLooper());
            Looper.loop();
        }
    }).start();
}

这里在创建hander的前后分别调用了两个方法Looper.prepare()和Looper.loop();这两个方法分别是在当前线程中初始化一个Looper轮询器和使Looper轮询器开始轮询,Looper的作用是不断的轮询当前线程MessageQueue消息队列中的消息,然后交给Handler去处理。要使handler消息机制开始运作,就必须使当前线程生成一个Looper(一个线程只能有一个Looper与之关联),那为什么平时我们通过主线程中创建的handler去修改UI界面的时候只需要初始化一个handler就可以,而从来没管过Looper呢,这要从app的入口说起。
初学Android的时候以为一个应用的入口就是Activity的OnCreate方法,其实,android应用程序的入口理解为ActivityThread中的main方法更为合适一点(还是不准确)。ActivityThread就是应用程序的主线程,打开它的main方法可以看到

public static void main(String[] args) {

    SamplingProfilerIntegration.start();
    CloseGuard.setEnabled(false);
    Environment.initForCurrentUser();
    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());
    Security.addProvider(new AndroidKeyStoreProvider());
    // 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"));
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

无关的代码暂且不看,代码的第14行和第26行,在应用程序初始化的时候,系统早已经帮我们调用了这两个方法为主线程关联了一个Looper并使它轮询。我们进一步打开Looper.prepareMainLooper();方法

 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与它相关联,则会抛出异常(所以一个线程只能有一个Looper与之关联),如果没有,则调用myLooper方法将主线程与当前线程的Looper相关联。我们先看Looper的初始化:prepare(false);方法的实现

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

ThreadLocal是一个方便解决多线程并发问题的工具类,ThreadLocal详解,这里不用对它理解多透彻,简单来说,它可以为每一个使用它的线程提供一个独立的副本,而不会被其他线程影响。比如在线程A中调用ThreadLocal的set方法,它会将我们set方法中传递参数对象的引用做一个记录,然后在线程A中,可以通过ThreadLocal的get方法将其取出,但在线程B中就无法获取。然后再看Looper的prepare()方法,可以看出执行流程为:如果当前线程存在Looper对象,则抛出异常,如果不存在,则初始化一个Looper对象并保存到ThreadLocal。
最后看sMainLooper = myLooper();方法:

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

很简单的一行代码,将当前线程的Looper对象取出,赋值给sMainLooper作为主线程的Looper。至此,ActivityThread中的Looper.prepareMainLooper()方法执行完成,系统调用Looper.Loop()方法使Looper轮询器开始轮询消息。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值