朝花夕拾Handler篇(补充)

Posted andy-songwei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了朝花夕拾Handler篇(补充)相关的知识,希望对你有一定的参考价值。

前言

       一年前写过一篇文章【朝花夕拾】Handler篇,随着这一年来对Handler更多的认识和理解,本文对Handler知识点做的一些补充。

 

一、为什么要引入Handler

       Handler的主要作用是切换线程,将线程切换到Handler所使用的Looper所在线程中去,我们大部分的开发者通常使用Handler是用于子线程通知主线程更新UI,我们需要明确的是更新UI只是Handler的其中一个作用而已。

       那么为什么只能在主线程中更新UI,而不能在子线程中完成呢?因为android系统规定,只能在主线程中访问UI,如果在子线程中访问UI,程序就会报错。在访问UI的时候,系统会调用ViewRootImpl类中的checkThread方法,如下所示:

 1 //=======ViewRootImpl.java=======
 2 final Thread mThread;
 3 ......
 4 public ViewRootImpl(Context context, Display display) {
 5     mThread = Thread.currentThread();
 6 }
 7 ......
 8 void checkThread() {
 9     if (mThread != Thread.currentThread()) {
10         throw new CalledFromWrongThreadException(
11                 "Only the original thread that created a view hierarchy can touch its views.");
12     }
13 }
14 ......

我们知道ViewRootImpl是View体系根View DecorView与Activity的PhoneWindow之间的纽带,最初ViewRootImpl实例化的时候,是在主线程中完成的。所以,上述checkThread方法可以用来判断当前是否为主线程,不是则报异常,该异常应该比较常见的了。

       那么,系统为什么不允许在子线程中访问UI呢?这是因为UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。那么为什么系统不对UI控件的访问加上锁机制呢?主要是因为如果加上锁机制会有两个缺点:1)使访问逻辑变得复杂。2)降低访问UI的效率,因为锁机制会阻塞某些线程的执行。所以,最简单且高效的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只需要通过Handler切换一下UI访问的执行线程即可。

       所以Handler的出现,就解决了子线程中不能访问UI的问题。

 

二、Handler回调所在线程问题

       在对Handler理解不深入的时候,一直没有认真注意过new一个Handler后,回调方法所在的线程问题,总以为任何时候都可以在回调方法中更新UI。事实上,之所以会有这样的错误认识,是因为我们使用Handler的时候基本上都用于更新UI了,就犯了经验主义错误。实际上,回调方法所在线程,和发送消息的handler使用的Looper所在的线程有关。下面我们先通过一些实验开看看结果。

  1、在子线程中使用main Looper的情况

 1 private void testHandler() {
 2         Log.i("songzheweiwang", "thread1=" + Thread.currentThread());
 3         new Thread(new Runnable() {
 4             @Override
 5             public void run() {
 6                 new Handler(Looper.getMainLooper()).post(new Runnable() {
 7                     @Override
 8                     public void run() {
 9                         Log.i("songzheweiwang", "thread2=" + Thread.currentThread());
10                     }
11                 });
12             }
13         }).start();
14     }

在主线程中调用如上方法,对应的log如下,可见回调方法是在主线程中:

1 08-31 12:48:49.342 9414-9414/com.example.demos I/songzheweiwang: thread1=Thread[main,5,main]
2 08-31 12:48:49.373 9414-9414/com.example.demos I/songzheweiwang: thread2=Thread[main,5,main]

  2、在子线程中使用子线程Looper的情况

 1 private void testHandler() {
 2         Log.i("songzheweiwang", "thread1=" + Thread.currentThread());
 3         new Thread(new Runnable() {
 4             @Override
 5             public void run() {
 6                 Looper.prepare();
 7                 new Handler().post(new Runnable() {
 8                     @Override
 9                     public void run() {
10                         Log.i("songzheweiwang", "thread2=" + Thread.currentThread());
11                     }
12                 });
13                 Looper.loop();
14             }
15         }).start();
16     }

在主线程中调用该方法,得到的log如下,可见回调方法是在当前子线程中:

1 08-31 12:53:49.718 9655-9655/com.example.demos I/songzheweiwang: thread1=Thread[main,5,main]
2 08-31 12:53:49.719 9655-9750/com.example.demos I/songzheweiwang: thread2=Thread[Thread-7,5,main]

 

       上述示例采用的是post方式,sendMessage方式也是一样的结果,这里就不举例了。我们平始使用Handler多数情况下是在主线程中new Handler的,默认情况下使用的是main Looper,然后在子线程中用该Handler实例来post或者sendMessage,所以默认情况下回调方法就是运行在主线程中,我们在该方法中访问UI就没有报错。

       另外上述示例中,第6行到13行展示了在子线程中,Handler使用子线程Looper的使用方法。Looper.prepare(),是获取当前子线程的Looper,如果没有Looper会报异常。Looper.loop(),就是用来开启线程的消息循环,否则就无法收到消息。

 

以上是关于朝花夕拾Handler篇(补充)的主要内容,如果未能解决你的问题,请参考以下文章

朝花夕拾Android性能优化篇之序言及JVM篇

朝花夕拾Android性能优化篇之Android虚拟机简介

朝花夕拾Broadcast篇

朝花夕拾Android Log篇

朝花夕拾Android Log篇

朝花夕拾Android性能优化篇之Apk打包