消息驱动是系统运行最重要的保证之一,线程与线程,进程与线程之间如何协作,系统进程是如何管理应用进程的生命周期和运作方式。 本篇文章主要解释线程之间传递信息是如何在应用程序框架层之间运作起作用的。
总体理解
-
1、关键类介绍
- ● Message 消息的实体序列化对象,包含了消息的标志、传递的内容的可循环使用的类。可以通过obtain()几个重 载方法清除内存中Message对象原本的值回收成初始对象。简单说,就是可循环使用的消息的抽象类。
public void recycle() { //在消息送达后会调用这个方法,重新添加到消息池中
//重置状态
clearForRecycle();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
//obtain拿到的消息体经过recycle后的对象
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
- ● Looper 消息泵,用来循环消息池中的消息体然后分发到消息自身携带的target,也就是handler 主要通过Looper.prepare()和Looper.loop()来完成任务,一个线程只能有一个Looper
// 创建一个新的Looper并存放在ThreadLocal变量中,在Looper中创建MessageQueue变量。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//保证一个线程只有一个Looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
//从消息泵中拿出数据
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
//消息通过消息泵自动分发给消息自带的handler
msg.target.dispatchMessage(msg);
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {//死循环从消息池中拿出消息体,
Message msg = queue.next(); // might block
if (msg == null) return;
Printer logging = me.mLogging;
final long newIdent = Binder.clearCallingIdentity();
msg.recycle();
}
- ● Handler 由于Looper在prepare时将Looper对象和线程关联存在TreadLocal中。在初始化Handler时可以根据当前 线程找到对象的Looper对象和与Looper对象的MessageQueue,并通过sendMessage发送消息
public Handler(Callback callback, boolean async) {
//debug code ,内部类持有外部类的引用,导致外部类不能被销毁。如果处在队列中,将会导致内存泄漏
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
//为Hanler准备Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
}
-
● MessageQueue 通过调用底层pipe,可以逐条的读出消息,在没有消息时会阻碍线程。一个线程只能有一个MessageQueue
这个MessageQueue比较复杂,涉及到的底层代码较多,暂时还没研究透彻,mark下!
-
● ThreadLocal 线程缓存,保存某个线程的值和对应的线程id.可以通过当前线程取出对于的值,用于跨线程对话。
-
2、连贯起来
- 首先调用Looper.prepare()要备好Looper和MessageQueue(注:主线程中的prepare是由系统调用,不能主动调用否则报错). 在收消息的线程中新建Handler类,Handler构造函数会拿到之前prepare()准备好的Looper和MessageQueue对象。在子线程中 Message.obtain()可以拿到消息池中的已被回收的消息,然后进行赋值,通过主线程的handler.sendMessage(),将Message 的tagert设置成自己(target=this)并且添加到MessageQueue中。在handler创建后面,一般都要跟一个Looper.loop(),这 个函数会不停的将MessageQueue中的消息拿出来(阻碍的)通过msg.target.dispatchMessaeg () 回调到Handlder或者 Message自己的callback,完成本次消息传递。
理解升华
-
1、自问自答
>> 如何保证按顺序从MessageQueue中取数据? 答: >> 如何保证线程中消息可寻址? 答: 保证一个线程只能有一个Looper和一个MessageQueue,这样就能让多个Message在一个线程的MessageQueue中存储以保证消息寻址 由于Hanlder是属于message的属性,不同的message可以有不同的handler,因此对handler没有限制。 >> 为什么要建议使用message.obtain()实例化消息体? 答: 消息体在消息泵中流转,当消息送达后,将消息体属性清空重新回到消息队列,因此我们建议使用message.obtain()从原有的消息队列中获取 >> ThreadLocal在Handler中起到什么作用? 答: 由于一个线程只能有一个Looper,把线程的Looper和本地线程绑定存放到本地变量。通过一个全局变量可以在子线程中取到主线程的 MessageQueue.这样,在子线程也可以通过主线程的handler拿到主线程的MessageQueue.需要注意的是,我们在子线程使用主线程的 handler,是将子线程的消息体添加到主线程的消息,主线程的消息泵使用的回调 handlerMessage()也是主线程中 >> 如何保证线程间的数据共享? 答: 在接受消息的线程A新建一个Handler,在新建Handler之前需要调用Looper.prepare().在Looper,prepare()中实例化Looper将MessageQueue 存放在Looper中,将Looper存放在ThreadLocal中。由于一个线程只存在一个Looper,所以在Handler的构造函数中,可以取到当前线程的Looper. 送消息线程B可以通过handler.sendMessage()将消息添加到MessageQueue中,并将Message的handler赋值为发送消息的handler.这样通过handler, 就可以将两个线程联系起来了。
-
2、读源码感悟
1),在读别人代码时,可以先找准切入点,忽视细节抽取代码逻辑,小步重构,良好的代码封装和命名 规则能够省去很多不必要的注释 2),主动抛异常能减少错误率,将一些绝对不能出现的问题在编译期间就暴露出来,去除以后的隐患。 3) ,写代码之前先进行可行性分析,在需求分析的基础上整理好基本架构布局,能够事半功倍,提早发现问题。
: