当大家被问到这个问题的时候可能就很迷茫了
谁没事干这样做呢?
但是今天我们只关注能不能。
说到这个问题,我们就不能提一下什么是UI线程,UI线程也叫主线程,为什么这么说呢?
因为当Activity被创建后,我们布局的实现都由系统分配的一个线程(其实就是一个普通的线程)进行刷新,但是就因为这个线程做了刷新UI这个操作,所以被称之为UI线程,自此,除此之外的线程都自动被称之为子线程。
就有人要问了,你说什么啊?不是说子线程不能更新UI么,你这怎么界面是由一个普通线程进行刷新的呢?
其实这样的理解是对的,但是我这里还是要详细解释一下,UI是可以被线程更新的,当Activity被创建后,布局也被选中的这个线程刷新后,才有了主线程和子线程的概念,那么刚才提出的问题:子线程不能更新UI?这里就要提出一个View更新的原则,由谁创建,由谁更新,既然界面已经由一个线程(主线程)创建了,那么刷新就必定要在这个线程中进行刷新,所以才有了大家认定的结论,不过不要紧,我们其实在子线程中也能更新UI,因为View更新的原则是由一个ViewRoot类提供的,当我们在子线程中进行操作界面的时候会被ViewRoot进行检查一番:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
大概就是这样,当发现创建的新线程和当前的线程不是同一线程时就会抛出异常,但是大家不用担心,因为ViewRoot的创建是在Activity的onResume中的,所以说,当我们在这个生命周期之前用子线程更新UI就是可行的,但是这么一看,这种操作貌似就像是钻了空子一样,那么还有一个例子可以举,Toast大家应该不陌生,那么Toast的调用能不能在子线程中呢?这个问题看起来就像是和前面的问题一样,当我们去做的时候,系统是会抛出异常的:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
这个问题大家可能也见过,从字面意思我们可以知道,我们的Looper还没有准备好,说到这里,我又不得不提一下子线程去界面的刷新流程了,大概涉及以下几个概念:
Message、Handler、MessageQueue、Looper
这几个概念大家应该比较熟悉,但是我还是稍微解释以下,Message是消息载体,Handler是消息处理类,MessageQueue是存储消息的队列(也就是Message),Looper则是处理MessageQueue的工具,会不断的遍历MessageQueue,每当检查存在消息时就会取出来发送给Handler进行处理,那么这几个概念理清了,我们接着上面的问题,子线程调用Toast抛出的异常,那么这个问题我们可以反转一下,为什么在主线程调用就不会有异常呢?大家看了我上面说了子线程刷新界面的流程有没有思路了呢?
就是因为当Activity创建的时候,创建布局线程的Looper也被调用了,已经在工作中了,但是我们新创建的线程却没有准备的好,所以会提示上面的错误,当我们调用以下代码:
Looper.prepare();
Looper.loop();
并放在Toast的前后就可以让新创建的线程里面的Looper正常工作了。
那么同学有疑问了,那我不用Toast,换成其他更新UI的代码行不行?
回答可能让你失望了,不行!这是因为Toast的创建和我们普通界面的创建是有区别的,Toast的创建我们去查看一下就知道,是通过NotificationManager调用系统的WindowManager实现的,在这里我稍微说一下,我们所看到的界面实际上是由俩部分组成的,一个是View和它的子类,一个就是WindowManager,到这里就知道创建Toast时,使用的线程并不是主线程,所以我们可以这样操作,但是普通的布局就不行了。
但是有人可能就去试验了,咦?我的测试了在子线程可以更新UI哎!?
楼主!你写的啥,咋不对呢?
别紧张,其实面对不同的机型,得到的答案可能都会有或多或少的变化,因为并不能排除部分开发商对机型源码的修改和安卓Sdk不同版本的代码改动,我就遇到子线程调用Toast,既不报错也不展示的情况,所以,我们需要知道最基本的一个过程是什么样子,那么当下次有人问你这个问题的时候,最好的回答是不是:看源码你不就知道了?
好了,到这里大家应该对子线程能不能更新UI有了更新的认识了吧,下期再见。
网友评论