子线程Looper.loop之后

Posted 爱炒饭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了子线程Looper.loop之后相关的知识,希望对你有一定的参考价值。

android规定不允许子线程更新UI,在ViewRootImpl 类中很多操作UI的方法都会调用checkThread()方法检查线程,如果当前线程与创建ViewRootImpl (mThread )的线程不一致就会报“Only the original thread that created a view hierarchy can touch its views”

//ViewRootImpl.java
void checkThread() {
    if (mThread != Thread.currentThread()) {//检查当前方法调用线程与mThread是否一致
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
……
public ViewRootImpl(Context context, Display display) {
    ……
    mThread = Thread.currentThread();//mThread是构造ViewRootImpl时的线程
    ……
}

ViewRootImpl是在什么时候创建的呢?在 从Activity的setContentView方法说起一文中有提到ViewRootImpl是在
WindowManagerGlobal.java 中addView方法中创建的,所以这个线程还是操作UI的线程。

//WindowManagerGlobal.java  
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
        ……
        root = new ViewRootImpl(view.getContext(), display);
        ……
}

通常情况下可以通过主线程的handler来发送和处理消息,但是在某些情况下子线程拿不到handler或者不方便通过其他方式给主线程发送消息,as we all konw,子线程弹toast会导致应用挂掉。Handler,Looper,Message的理解与困惑一文最后总结道通过Looper.prepare()和 Looper.loop()就可以来创建和循环处理消息,消息的创建和处理在同一个线程就不怕线程不一致的问题了, 此时有些同学可能会有类似下面的骚操作

thread = new Thread(new Runnable() {
    @Override
    public void run() {
        LogUtil.d("run start");
        Looper.prepare();
        Toast.makeText(MainActivity.this,"hahha",Toast.LENGTH_SHORT).show();
        LogUtil.d("run 2");
        Looper.loop();
        LogUtil.d("run end");
    }
});
thread.start();

第5行通过Looper.prepare()方法可以为当前线程创建一个Looper对象,并放置到当前线程的ThreadLocal中,方便后续获取该Looper对象,ThreadLocal相关知识可以参考 ThreadLocal和InheritableThreadLocal一文。
第8行的Looper.loop()方法从当前线程的ThreadLocal中获取Looper对象,然后开启消息死循环。还记得android app为什么没有自动退出吗?其实就是在ActivityThread.java的main方法的最后通过Looper.loop()开启了消息死循环,这个死循环有两个方面作用:1、等待消息,有消息就处理,没有消息就休眠;2、防止程序执行结束就死掉。
上面的demo运行后log打印如下:
在这里插入图片描述
Looper.loop()下面的log没有打印,并且通过monitor.bat查看18531线程一直在运行。这样的话,该线程可能一直不会消亡(除非app进程死掉)。在这里插入图片描述
修改下代码,如果去掉Looper.prepare()、Toast.makeText、Looper.loop三行代码,程序运行完子线程就会结束。
log打印如下:
在这里插入图片描述
monitor.bat查看19324线程已经消亡了
在这里插入图片描述
所以子线程通过Looper.prepare和Looper.loop方式操作UI的方式还是要慎用,这样会导致子线程一直运行,浪费资源。

以上是关于子线程Looper.loop之后的主要内容,如果未能解决你的问题,请参考以下文章

Android 线程通信

Android Toast在子线程中为啥无法正常使用

Looper.prepare()和Looper.loop()

Android异步消息机制

Android中为什么主线程不会因为Looper.loop()方法造成阻塞

[Android源代码分析]Android消息机制,Handler,Message,Looper,MessageQueue