一、消息机制使用场景
每个 Android应用程序都运行在一个dalvik虚拟机进程中,进程开始的时候会启动一个主线程(MainThread),主线程负责处理和ui相关的事 件,因此主线程通常又叫UI线程。而由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作。如果在非UI线程直接对UI进行了操 作,则会报错:Called From Wrong Thread Exception : only the original thread that created a view hierarchy can touch its views。 对于运算量较大的操作和IO操作,我们需要新开线程来处理这些繁重的工作,以免阻塞UI线程。Android为我们提供了消息循环的机制,我们可以利用这个机制来实现线程间的通信。那么,我们就可以在非UI线程发送消息到UI线程,最终让UI线程来进行UI的操作。
二、消息机制架构
上面是消息机制的框架图。从图中看出,消息机制主要有四个核心类:Looper,Handler,Message和Message Queue。
1) Looper是消息循环类,它包括了mQueue成员变量;mQueue是消息队列MessageQueue的实例。Looper还包含了loop()方法,通过调用loop()就能进入到消息循环中。
2)
Handler是消息句柄类。Handler提供了sendMessage()来向消息队列发送消息;发送消息的API有很多,它们的原理都是一样的,
这里仅仅只列举了sendMessage()一个。
此外,Handler还提供了handleMessage()来处理消息队列的消息;这样,用户通过覆盖
handleMessage()就能处理相应的消息。
3)
Message是消息类。Message包含了next,next是Message的实例;由此可见,Message是一个单链表。Message还包
括了target成员,target是Handler实例。此外,它还包括了arg1,arg2,what,obj等参数,它们都是用于记录消息的相关内
容。
4) MessageQueue是消息队列类,它包含了mMessages成员;mMessages是消息Message的实例。MessageQueue提供了next()方法来获取消息队列的下一则消息。
当
我们启动一个应用程序时,ActivityManagerService会为我们的Activity创建并启动一个主线程(ActivityThread
对象);在启动主线程时,就会创建主线程对应的消息循环,并通过调用loop()进入到消息循环中。当我们需要往消息队列发送消息时,可以继承
Handler类,然后创建Handler类的实例;接着,通过该实例的sendMessage()方法就可以向消息队列发送消息。
也就是说,主线程的消息队列也一直存在的。当消息队列中没有消息时,消息队列会进入空闲等待状态;当有消息时,则消息队列会进入运行状态,进而将相应的消
息发送给handleMessage()进行处理。
ActivityThread创建代码如下:
通
常在新打开一个应用程序界面时,ActivityManagerService会为该应用启动一个新的新建ActivityThread作为该改应用的主
线程。该主线程的main函数主要做了两件事:1.新建ActivityThread对象。 2.使用主线程进入消息循环。
三、关键类详细介绍
1) Looper-线程的魔法师
Looper成员变量
Looper.prepare() && Looper.prepareMainLooper()
通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。每个线程只能有一个looper。
Looper.loop()
调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。
Looper总结有如下几点:
每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal
Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行
Looper使一个线程变成Looper线程。
2)Handler-异步处理大师
a. handler发送消息
有了handler之后,我们就可以使用post(Runnable),postAtTime(Runnable, long),postDelayed(Runnable, long),sendEmptyMessage(int),sendMessage(Message),sendMessageAtTime(Message, long)和sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了。源码如下:
handler发出的消息有如下特点:
message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler
post发出的message,其callback为Runnable对象
handler处理消息
消息的处理主要通过dispatchMessage和handlerMessage来实现的,其源码如下:
3) Message-任务封装
Message
是线程之间传递信息的载体,包含了对消息的描述和任意的数据对象。Message中包含了两个额外的
int字段和一个object字段,这样在大部分情况下,使用者就不需要再做内存分配工作了。虽然Message的构造函数是public的,但是最好是
使用Message.obtain( )或Handler.obtainMessage(
)函数来获取Message对象,因为Message的实现中包含了回收再利用的机制,可以提供效率。
Message结构定义:
消息获取接口:
消息回收接口:
四、ThreadLocal简介
当
使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,
而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)
设置当前线程的线程局部变量的值。
public Object get()
该方法返回当前线程所对应的线程局部变量。
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
set方法实现: