android 消息系统Handler、MessageQueue、Looper源代码学习
android消息系统
总体框架如图所看到的
在安卓的消息系统中,每一个线程有一个Looper,Looper中有一个MessageQueue,Handler向这个队列中投递Message,Looper循环拿出Message再交由Handler处理。总体是一个生产者消费者模式,这四部分也就构成了android的消息系统。
先来看一个最简单的样例
//这段代码在某个Activity的onCreate中
Handler handler = new Handler(Looper.getMainLooper());
Message msg = Message.obtain(handler, new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext,"I am a message",Toast.LENGTH_SHORT).show();
}
});
handler.sendMessage(msg);
效果就是。在当前窗体弹出I am a message。当然就事实上现的效果而言全然多此一举。可是就分析android消息系统。却是非常easy有效的样例。
源代码分析
Message
Message中封装了我们经常使用的what、arg1、arg2、obj等參数,除此之外还有target:一个Handler类型,由前文可知一个Message终于还是交给一个Handler运行的。这个target存放的就是消息的目的地、callback。一个消息的回调。我们通过handler.post(new Runnable{…})发送的消息。这个Runnable即被存为callback。
首先来看消息的获取:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
public static Message obtain(Handler h, Runnable callback) {
Message m = obtain();
m.target = h;
m.callback = callback;
return m;
}
对比最開始的样例,Message.obtain(Handler h, Runnable callback)首先调用obtain获取了一个新的Message对象。然后为其设置了目的地Handler和回调函数callback。Message类中有非常多不同的obtain函数。实际上仅仅是为我们封装了一些赋值的操作。
再看Message.obtain()方法。sPoolSync是一个给静态方法用的静态锁。sPool是一个静态的Message变量。在消息的获取这里,android使用了享元模式,对于会被反复使用的Message消息。没有对每一次请求都新建一个对象。而是通过维护一个Message链表,在有空暇消息的时候从链表中拿Message。没有时才新建Message。
能够看到obtain中仅仅有从链表中去Message和新建Message。而没有向链表中存储的过程。
存储这部分就要看Message.recycle()了:
public void recycle() {
clearForRecycle();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
回收过程。首先把原链表的头指向当前被回收消息的下一个节点。然后再把链表头指针知道当前节点就可以。整个操作也就是将Message加入到链表的首位。
MessageQueue 消息队列
MessageQueue是在Looper中的。这点从Looper的构造函数能够看出来:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
对于每一个MessageQueue,是链表实现的消息队列。
首先是入队操作:
boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
}
synchronized (this) {
if (mQuitting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
}
msg.when = when;
//mMessages是链表的头指针
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 将消息插入到队列的首位
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
//当抵达队列尾部、或者当前消息的时间小于队列中某消息的时间跳出循环
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//将消息插入到链表中间(包括尾部)
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
next操作。包括取出和删除一条消息。
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//从native层消息队列取出消息
nativePollOnce(mPtr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 找到非异步的Message或者消息队列尾部的Message取出
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 消息尚未到运行时间,下次循环挂起线程一段时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取一个Message
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// 检查退出标志位
if (mQuitting) {
dispose();
return null;
}
...
}
}
Handler
Handler的作用是放入消息和处理消息,承担了生产者的工作和部分消费者的工作。
首先通过Handler发送一条消息:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
通过一层一层嵌套,真正的逻辑在sendMessageAtTime,能够看到仅仅是运行了一下入队操作。作为生产者的工作也就运行完毕,消费者部分后面要结合Looper分析。
除了sendMessage方法,经常使用的handler.post方法也是封装为Message,主要过程和上面类似。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Looper
Looper类中,Looper的实例获取是通过ThreadLocal的。ThreadLocal会为每一个线程提供一个副本,通过set和get方法每一个线程获取作用域仅属于该线程的变量值。对于UI线程而言,会运行Looper.prepareMainLooper()来完毕Looper的初始化:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Looper.prepare()方法将当前线程的ThreadLocal设置了一个新的Looper对象。prepareMainLooper则是把当前线程的Looper对象赋值给类变量sMainLooper 。该方法在ActivityThread中调用,设置了一个全局的给UI线程使用的Looper。
Looper的loop方法就是消费者的处理逻辑了:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//从Looper中获取MessageQueue,循环取出消息
for (;;) {
Message msg = queue.next();
...
//将消息发送给目标处理。
msg.target.dispatchMessage(msg);
...
//回收消息,把消息放在消息池中
msg.recycle();
}
}
主要逻辑非常清晰,前面分析过msg.target是一个Handler,表示处理消息的目标,通过命令模式将消息交给相应Handler处理。
以下是Handler中处理消息的方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public void handleMessage(Message msg) {
}
假设我们是通过handler.post的方法发送一条消息,那么直接运行callback中的逻辑。
否则通过实现Callback接口回调,或者运行handleMessage,handleMessage也就是我们子类覆写的方法。
能够看到,尽管逻辑部分是我们在Handler中实现的。可是调用的地方却是Looper的线程。由于一个Looper绑定一个线程,我们也能够通过比較Looper来比較线程。
总结
通过分析源代码,能够知道android中能够通过Looper为每一个线程创建一个消息队里,UI线程的Looper在Activity启动前就已经初始化。
那么对于我们自己定义的线程。非常明显也能够绑定Looper。
自己定义线程绑定Looper。最明显的优点就是能够实现线程间通信了,同一时候由于借助了消息队列,也将并行转为串行实现了线程安全。看一个简单的样例:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handlerA = new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg) {
Log.d("TAG", msg.obj.toString());
}
};
Looper.loop();
}
}).start();
上述在线程中创建绑定了一个Looper,然后新建一个和当前Looper绑定的Handler,这样能够通过该Handler向Looper的MessageQueue中加入消息,然后由Looper.loop取出消息并运行。
Message msg = new Message();
msg.obj = "i am main thread";
handlerA.sendMessage(msg);
在主线程或者其他线程中获取handler然后发送消息,终于能够看到消息被线程接收并处理。
这里msg的target也就是handlerA。
注意假设线程工作结束,须要调用Looper.quit()。不然会由于Looper一直循环而导致线程无法结束。
最后经过上面的分析,流程图能够画的更为仔细:
android 消息系统Handler、MessageQueue、Looper源代码学习的更多相关文章
- (转)Android消息处理机制(Handler、Looper、MessageQueue与Message)
转自 http://www.cnblogs.com/angeldevil/p/3340644.html Android消息处理机制(Handler.Looper.MessageQueue与Messag ...
- [转]Handler MessageQueue Looper消息循环原理分析
Handler MessageQueue Looper消息循环原理分析 Handler概述 Handler在Android开发中非常重要,最常见的使用场景就是在子线程需要更新UI,用Handler ...
- 从Handler+Message+Looper源代码带你分析Android系统的消息处理机制
PS一句:不得不说CSDN同步做的非常烂.还得我花了近1个小时恢复这篇博客. 引言 [转载请注明出处:http://blog.csdn.net/feiduclear_up CSDN 废墟的树] 作为A ...
- Android主线程的消息系统(Handler\Looper)
前言: 之前的文章写的都是关于Bitmap和内存的优化技术,这一篇文章给大家谈谈Handler. Handler是Android系统中比较重要的一个知识,在Android多线程面试经常会被问到,在实际 ...
- Android Handler MessageQueue Looper 消息机制原理
提到Android里的消息机制,便会提到Message.Handler.Looper.MessageQueue这四个类,我先简单介绍以下这4个类 之间的爱恨情仇. Message 消息的封装类,里边存 ...
- Android消息处理机制(Handler、Looper、MessageQueue与Message)
Android是消息驱动的,实现消息驱动有几个要素: 消息的表示:Message 消息队列:MessageQueue 消息循环,用于循环取出消息进行处理:Looper 消息处理,消息循环从消息队列中取 ...
- ThreadLocal ——android消息机制handler在非主线程创建not called Looper.prepare() 错误的原因
引用自:https://www.jianshu.com/p/a8fa72e708d3 引出: 使用Handler的时候,其必须要跟一个Looper绑定.在UI线程可直接初始化Handler来使用.但是 ...
- Android——线程通讯 Handler、Looper、Message;
线程通讯问题 (主要用到了Handler类,Looper类和Message类以及MessageQueue) 在Android中主线程如何向子线程中发送消息的问题.让我们来想想,这其中的过程,无非就是创 ...
- Android消息机制——Handler
/**android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个MessageQueue(消息队列), * 但是MessageQueue被封装到Looper里 ...
随机推荐
- Image与byte[]数组的相互转换
近期项目有个需求是关于图片操作的,须要将图片保存到数据库中.经过尝试才知道Image类型文件是不能直接存储到数据库中的.保存之前须要我们做一步转换:将Image转换成字节数组类型Byte ...
- InnoDB引擎索引大观
InnoDB是mysql处理OLTP(online transcation process)类型业务的存储引擎.为了加快数据查询速度.InnoDB引擎提供了丰富的索引实现. 1. 索引的分类 索引能够 ...
- 圆形头像CircleImageView和Cardview使用
效果: 圆形头像在我们的日常使用的app中很常见,因为圆形的头像比较美观. 使用圆形图片的方法可能有我们直接将图片裁剪成圆形再在app中使用, 还有就是使用自定义View对我们设置的任何图片自动裁剪成 ...
- Day1上午解题报告
预计分数:100+60+0=160 实际分数:100+30+20=150 T1立方数(cubic) 题目描述 LYK定义了一个数叫“立方数”,若一个数可以被写作是一个正整数的3次方,则这个数就是立方数 ...
- Think Pad笔记本分区解决思路及方法
Think pad笔记本分区解决思路及方法 近日好友拿着新买的Thinkpad X300过来找我,说这个笔记本只有一个分区,所有的东西不得放在C盘,希望再多分出几个分区.抱怨原先在wind ...
- Regularization —— linear regression
本节主要是练习regularization项的使用原则.因为在机器学习的一些模型中,如果模型的参数太多,而训练样本又太少的话,这样训练出来的模型很容易产生过拟合现象.因此在模型的损失函数中,需要对模型 ...
- 今日题解------uvalive 2689
今天学到了代码以外的东西,就是你在vj上挂了content ,然后你想更新它,你就要刷新一下,不然你提交的那题可能提交到别的地方. 好了回到重点,本题的题意是: #include<bits/st ...
- 使用Ant 和 Maven打包发布命令行程序(转载)
From:https://www.linux178.com/Java/maven-release.html 用Java写了一个命令行的小程序,使用的Intellij IDE是IDEA13原来一直使用A ...
- Android获取当前连接的wifi名称
首先AndroidMainfest.xml文件里加入权限: <uses-permission android:name="android.permission.ACCESS_NETWO ...
- 解决使用SecureCRT不能连接Ubuntu的问题
一.现象 SecureCRT是远程登陆工具及串口,可以远程进行登陆Linux服务器或者串口打印数据.但我下载安装了之后想通过SecureCRT来远程登陆我的Ubuntu,出现一直连接不上. 二.问题原 ...