有这样一个需求,当打开Activity界面时,开始倒计时,倒计时结束后跳转新的界面(思维活跃的朋友可能立马想到如果打开后自动倒计时,就类似于各个APP的欢迎闪屏页面),如下图:
可能觉得直接开启一个包含倒序循环的子线程就ok了,具体实现如下:
activity_main
MainActivity.java
但当点进入界面时,会发现程序奔溃了,logcat中错误日志如下(只有UI线程可以更改UI界面):
由此我们发现在安卓开发中,例如上面的示例,我们常常通过一个线程来完成某些操作,然后同步显示对应的视图控件UI上,通过上面的例子我们也知道了安卓中无法直接通过子线程来进行UI更新操作,对于这种情况,Android提供了一套异步消息处理机制Handler。
Handler一套 Android 消息传递机制,主要是子线程UI更细消息传递给主线程,从而主线程更新UI。
(1)Message,线程之间传递的消息,用于不同线程之间的数据交互。Message中的what字段用来标记区分多个消息,arg1、arg2 字段用来传递int类型的数据,obj可以传递任意类型的字段。
(2)Handler,用于发送和处理消息。其中的sendMessage()用来发送消息,handleMessage()用于消息处理,进行相应的UI操作。
(3)MessageQueue,消息队列(先进先出),用于存放Handler发送的消息,一个线程只有一个消息队列。
(4)Looper,可以理解为消息队列的管理者,当发现MessageQueue中存在消息,Looper就会将消息传递到handleMessage()方法中,同样,一个线程只有一个Looper。
Handler机制的工作流程主要包括4个步骤:
1、异步通信准备 2、消息发送 3、消息循环 4、消息处理
工作流程图:
线程(Thread)、循环器(Looper)、处理者(Handler)之间的对应关系如下:
Handler机制 中有3个重要的类:
下面的源码分析将根据 Handler的使用步骤进行
步骤1:在主线程中 通过匿名内部类 创建Handler类对象
从上面可看出:
那么,当前线程的Looper对象 & 对应的消息队列对象(MessageQueue) 是什么时候创建的呢?
创建Looper对象主要通过方法:Looper.prepareMainLooper()、Looper.prepare(); 创建消息队列对象(MessageQueue)方法:创建Looper对象时则会自动创建,即:创建循环器对象(Looper)的同时,会自动创建消息队列对象(MessageQueue)
总结:
1、创建主线程时,会自动调用ActivityThread的1个静态的main();而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象
即 主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象则需手动通过Looper.prepare()创建 在子线程若不手动创建Looper对象 则无法生成Handler对象 2、根据Handler的作用(在主线程更新UI),故Handler实例的创建场景 主要在主线程
3、生成Looper & MessageQueue对象后,则会自动进入消息循环:Looper.loop(),即又是另外一个隐式操作。
此处主要分析的是Looper类中的loop()方法
总结:
图表总结如下:
多线程的实现方式:AsyncTask、继承Thread类、实现Runnable
总结
Handler发送消息的本质 = 为该消息定义target属性(即本身实例对象) & 将消息入队到绑定线程的消息队列中。具体如下:
实际上,该方式与方式1中的Handler.sendMessage()工作原理相同、源码分析类似,下面将主要讲解不同之处
消息对象的创建 = 内部 根据Runnable对象而封装
参考下一节(6.4.3)
从上面的分析可看出:
下面,我们重新回到步骤1前的隐式操作2:消息循环,即Looper类中的loop()方法
至此,你应该明白使用 Handler.post()的工作流程与Handler.sendMessage()类似,区别在于:
关于使用 Handler.post()的源码解析完毕,总结如下:
首先来看看构造函数
构造函数之上定义了很多native方法
native之上定义了几类数据结构,Message、ArrayList、SparseArray、数组
Message有如下公有属性,供程序员调用:
Message有如下私有属性,用途如下:
Message的源码,我们可以得出如下结论,Message是一种链表结构,每个Message持有以下信息:
1、用于传递的数据,如what、arg1、arg2、obj 2、用于执行当前Message的Handler 3、用于执行当前Message的回调接口CallBack、子线程Runnable 4、当前Message的属性,如延时时间、执行标识、Bundle数据,下一个Message引用。这种结构构成了链表。
接下来我们看看Looper吧!
在任何线程要开启Loop,都要用Looper.prepare()+Looper.looper()的方式。
以APP主进程为例,APP进程启动入口的main方法,也是通过这种方式开启loop的。与子线程细微不同的是,主线程开启looper用的是prepareMainLooper。
Looper构造函数做了两件事情,初始化消息队列MessageQueue对象,记录当前线程信息。
可以看到myLooper是从threadLocal中取出Looper对象。在Looper类中定义了如下变量sThreadLocal、mQueue、sMainLooper、mThread
prepare就是 new 出一个 Looper。核心之处在于将 new 出的 Looper 设置到了线程本地变量 sThreadLocal 中。也就是说创建的 Looper 与当前线程发生了绑定。
Looper#prepareMainLooper原理
prepareMainLooper只有在APP进程启动的时候有用,并不推荐开发者调用这个函数。
也就是说,Message最终交由与Message绑定的Handler处理。Looper只是负责无限循环+从MessageQueue中读取。
1个简单 “更新UI操作” 的案例,主布局文件相同 = 1个用于展示的TextView,具体如下:
布局代码:activity_main.xml
实验结果:
实验结果
实验结果: