💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 10.1 Android的消息机制概述 前面提到,**Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,这三者实际上是一个整体,只不过我们在开发过程中比较多地接触到Handler而已**。 Handler的主要作用是将一个任务切换到某个指定的线程中去执行,那么Android为什么要提供这个功能呢?或者说Android为什么需要提供在某个具体的线程中执行任务这种功能呢?这是因为**Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常**。[ViewRootImpl](https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/core/java/android/view/ViewRootImpl.java)对UI操作做了验证,这个验证工作是由ViewRootImpl的checkThread方法来完成的,如下所示。 ``` void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } ``` 针对checkThread方法中抛出的异常信息,相信读者在开发中都曾经遇到过。**由于这一点的限制,导致必须在主线程中访问UI,但是Android又建议不要在主线程中进行耗时操作,否则会导致程序无法响应即ANR**。 考虑一种情况,**假如我们需要从服务端拉取一些信息并将其显示在UI上,这个时候必须在子线程中进行拉取工作,拉取完毕后又不能在子线程中直接访问UI,如果没有Handler,那么我们的确没有办法将访问UI的工作切换到主线程中去执行。因此,系统之所以提供Handler,主要原因就是为了解决在子线程中无法访问UI的矛盾**。 这里再延伸一点, * **系统为什么不允许在子线程中访问UI呢**? 这是因为**Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态**, * 那**为什么系统不对UI控件的访问加上锁机制呢**? 缺点有两个: * 首先**加上锁机制会让UI访问的逻辑变得复杂**; * 其次**锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行**。 **鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只是需要通过Handler切换一下UI访问的执行线程即可**。 Handler的使用方法这里就不做介绍了,这里描述一下Handler的工作原理。 **Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper,那么就会报错**,如下所示。 ``` E/AndroidRuntime(27568): FATAL EXCEPTION: Thread-43484 E/AndroidRuntime(27568): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() E/AndroidRuntime(27568): at android.os.Handler.<init>(Handler.java:121) E/AndroidRuntime(27568): at com.ryg.chapter_10.TestActivity$3.run(TestActivity.java:57) ``` 如何解决上述问题呢?其实很简单,**只需要为当前线程创建Looper即可,或者在一个有Looper的线程中创建Handler也行**,具体会在10.2.3节中进行介绍。 Handler创建完毕后,这个时候其(线程)内部的Looper以及MessageQueue就可以和Handler一起协同工作了,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理(**这里这样说其实是不恰当的,Looper其实是运行在创建Handler的线程中的,不能说Looper属于Handler,一个线程对应一个Looper,一个MessageQueue,但是Handler可以有多个**),也可以通过Handler的send方法发送一个消息,这个消息同样会在Looper中去处理。**其实post方法最终也是通过send方法来完成的**, 接下来主要来看一下send方法的工作过程。**当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用。注意Looper是运行在创建Handler所在的线程中的,这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了**,这个过程可以用图10-1来表示。 :-: ![](https://img.kancloud.cn/30/57/3057947d23453017950e3e8b7d2c0e49_1027x603.png) 图10-1 Handler的工作过程