美文网首页
为什么主线程不用调用 Looper.prepare()

为什么主线程不用调用 Looper.prepare()

作者: wanweitong | 来源:发表于2019-10-06 16:06 被阅读0次

Looper.prepare ()

既然是研究Handler,我们先看看我们常用的Handler的构造函数,平时我们都是使用的第一个构造,而第一个构造会再调用下面的构造函数,我们看到里面有一个Looper.myLooper()方法,并赋值给成为Handler的成员变量

public Handler(Callback callback) {
        this(callback, false);
}

public Handler(Callback callback, boolean async) {
    ...
    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;
}

Looper.myLooper()非常简单,仅仅是从ThreadLocal类中去get一个Looper对象,我们继续找是在哪里设置进去的

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

还记得我们在使用HandlerThread的时候需要调用Looper.prepare吗?就是为了初始化一个looper保存到当前线程中,方便Handler的构造能使用Looper.myLooper()获取到当前线程的Looper对象,并且从上面的构造函数来看,Looper.prepare()的调用时机应该是优先于Handler的初始化时机

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");
    }
    // set方法会将当前ThreadLocal对象作为key,looper作为value保存到当前线程中,
    // 由于当前的ThreadLocal是Thread的成员变量,我们只需要在set的线程调用  
    // Looper.myLooper就可以获取到我们之前已经初始化好的looper对象
    sThreadLocal.set(new Looper(quitAllowed));
}

当应用初始化完成后,已经帮我们在主线程中保存好了一个Looper的事例,所以,由于是在同一个线程,Handler能调用Looper.myLooper获取到之前保存的Looper对象并且绑定到我们自己的的Handler中

那么问题来了,这个主线程的Looper对象是什么时候设置进去的呢???

思考:activity的生命周期以及ui更新都是依赖于handler通知,那么主线程Looper的初始化时机一定非常非常早,一个app的入口位于ActivityThread.java的main方法,我们看看是不是在这里面初始化的呢?

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();
    ...
    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");
}

我们看到了一个非常可疑的函数Looper.prepareMainLooper(),这个就是初始化主线程Looper的方法,我们在点进去看这个方法

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的prepare方法去初始化了一个Looper并保存了起来,而且,还把这个Looper赋值给了sMainLooper变量,Looper中有个getMainLooper返回的就是这个sMainLooper,这也就是使用 Looper.getMainLooper() == Looper.myLooper()来判断是否是主线程的由来

总结

在app启动时,系统会调用Looper.prepareMainLooper()以sThreaLocal为key,Looper为value保存到主线程中,而我们新建主线程Handler的时候,Handler的构造函数会调用Looper.myLooper从sThreadLocal中get出一个Looper,因为为同一个线程,而sThreadLocal又是同一个变量,自然能正确的获取到在ActivityThread.java中初始化好的全局唯一的Looper对象了,而在希望创建一个跑在其他线程的Handler的时候,我们并没有事先将这个线程的Looper给保存到当前线程中中,所以需要调用Looper.prepare来初始化

相关文章

网友评论

      本文标题:为什么主线程不用调用 Looper.prepare()

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