源码分析之Handler
Handler是Android中的消息机制实现,可以实现UI线程和子线程的消息传递,这里就来深入了解Android的消息机制,来分析Handler的源代码
入手实例
在Android开发中,子线程与主线程通信是再寻常不过的事情了,那么为何需要子线程和主线程通信呢,相信只要是做过Android开发都知道这是为啥,为了博客的尽可能详尽,这里还是说说原因
举个简单例子,以前刚做Android开发的时候,啥也不懂,记得有一次,在主线程中做了耗时操作,具体记不得啥操作了,然后一运行,报了个无响应,也就是常说的ANR,然后就知道了主线程阻塞,耗时的操作放在子线程,主线程一旦阻塞超过5s就会报ANR,既然主线程不做耗时操作,那么子线程里面执行怎么和主线程互动呢,由此便引生出了Handler,当然AsyncTask也是基于同样的道理,AsyncTask的源代码在之前已经分析过了,这里就不再赘述
那么在开发中Handler是怎么使用的呢,这里结合一个例子作为讲解
public class MainActivity extends Activity {
private static final String TAG = "cj5785";
private TextView textView;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "handleMessage: " + Thread.currentThread().getName());
String str = (String) msg.obj;
textView.setText(str);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text_view);
}
public void buttonClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "run: " + Thread.currentThread().getName());
Message message = Message.obtain();
message.obj = "更新UI";
handler.sendMessage(message);
}
}).start();
}
}
这是一个简单的更新UI的例子,通过log可以清楚的看到,两个打印语句是处在不同的线程中的,然而却实现了主线程和子线程的消息传递
D/cj5785: run: Thread-13741
D/cj5785: handleMessage: main
如果直接在子线程中更新UI则会报出以下错误
Only the original thread that created a view hierarchy can touch its views.
那么这样的线程通信是怎么实现的呢,下面就来分析一下源代码
源代码分析
分析源代码,尤其是涉及到几个类的这种代码,找准切入点很关键,那么这个分析的切入点在哪呢,子线程发出消息,主线程接收,那么很明显是子线程的发出点,那么就应该先看子线程里面代码的处理
Message message = Message.obtain();
message.obj = "更新UI";
handler.sendMessage(message);
Message部分
首先是生成了一个Message对象,这里有两种方式,一种是直接new
一个出来,一种是使用obtain()
,那就去看看这个对象是怎么生成的
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
这里主要关注的是next参数,那么next是啥呢,有C语言基础的一看就知道是啥,没有也没关系,我们继续往下看
// sometimes we store linked lists of these things
/*package*/ Message next;
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
相信这句注释已经很明显了,这不就是链表么,Java中的链表使用对象引用实现,那么也就是说这里判断有没有Message有的话就实现复用,没有的话就new一个,而sPoolSync就是一个同步锁而已,到这里,并没有给出主线程和子线程消息传递的理由,那么继续往下看,message.obj = "更新UI";
这里只是设置了变量,也没有参考价值,那么只剩handler.sendMessage(message);
了,到此貌似Message的任务就完成了,那么来看看Message里面的一些值得注意的地方吧
照例先从类开始
public final class Message implements Parcelable {
···
}
Message实现了Parcelable接口,Parcelable和Serializable都是实现序列化,用于消息的传输,Message是一个final类,简而言之无法继承
再看看Message的成员变量,常用那几个就不说了,这里谈一谈重要的一些
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
private static final int MAX_POOL_SIZE = 50;
private static boolean gCheckRecycle = true;
首先是Bundle,这也就是Message为何可以设置Bundle的原因所在了,这里出现了Handler,也就是说在这里涉及到了Handler,这里简单来说就是Message会持有Handler的引用,这里注意到似乎只用带参的obtain()
以及setTarget()
才会初始化Handler,MAX_POOL_SIZE说明了Message的大小,gCheckRecycle则是清除信息的标志位
Handler部分
那么到这里已经没啥线索了,可以猜测在Handler中对target进行了初始化,那看看sendMessage()
做了啥吧
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
这里意外发现了常用的延迟设置,再继续看
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
所以从这就可以看出,当延迟小于零时会将其设为0,这里注意一下,SystemClock.uptimeMillis()
是一个native方法,返回的是boot时间,再继续追寻
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);
}
到这里,出现了一个新东西,这便是Handler组成的重要部分,那么MessageQueue是啥呢,熟悉数据结构的一眼就看出来了,这其实是个队列,稍后再看这个队列做了啥,队列不为空就要调enqueueMessage()
了,那么继续
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这里,对target赋值了,然后设置了异步状态标识,然后回到了MessageQueue
的enqueueMessage()
方法,至此,有必要暂停Handler的分析,去看一看MessageQueue
了
MessageQueue部分
在MessageQueue
中找到handler调用的方法,由于方法比较长,不便于描述,将其描述用注释形式写在代码中
boolean enqueueMessage(Message msg, long when) {
//target不能为空
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//msg不能正在被使用
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//同步代码块,保证线程安全
synchronized (this) {
//未退出
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle(); //清除Message
return false;
}
msg.markInUse(); //标记正在使用
msg.when = when; //得到延迟
Message p = mMessages; //赋值,在第一次调用此方法前,mMessages为null
boolean needWake;
if (p == null || when == 0 || when < p.when) { //由于是短路或,不会执行到第三个条件
// New head, wake up the event queue if blocked.
msg.next = p; //将msg的next指向p
mMessages = msg; //将msg赋值给mMessages
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev; //定义前节点
for (;;) { //条件循环
prev = p; //设置前节点为p
p = p.next; //p的后节点赋值给p
if (p == null || when < p.when) { //循环终止条件(到达尾节点或者when值小于后面的节点)
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; //msg的下一节点指向p
prev.next = msg; //prev的下一节点指向msg
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
如果只是一个msg,那么就只形成一个节点,如果多个msg,则会按时间顺序形成一条链表,虽然形成了链表,那么我们最关心的主线程与子线程的通信去哪了,或者说我们的分析过程问题出在哪里,注意到前面的mQueue
,这个东西是哪里来的
final MessageQueue mQueue;
那么是在那里初始化的,注意到在MainActivity中,调用前生成了Handler对象,使用的是无参的构造方法,那么来看看这个方法
public Handler() {
this(null, false);
}
额,好像没啥用,继续
public Handler(Callback callback, boolean async) {
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());
}
}
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;
mAsynchronous = async;
}
关于第一个if判断,这里说明一下:FIND_POTENTIAL_LEAKS标志设置为true以检测扩展的Handler类,如果不是静态的匿名,本地或成员类, 这类可能会产生泄漏。也就是我们常见构造时的警告说明!至于消除警告方法一般是设置成静态或弱引用
接下来发现有个mLooper,这是一个循环器,MessageQueue和Handler之间桥梁,那么这里就跳转到Looper吧
Looper
首先看看Looper.myLooper()
干了啥
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
结合注释知道了,这里返回带有当前线程的Looper,下面是get啥呢,继续来看get()
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
结合注释可知,这里返回的是线程值,注意sThreadLocal
的生成
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
嗯,也就是说在get前要调用prepare()
,从而可以推导出在ActivityThread里面调用了这个方法,查看一下
public static void main(String[] args) {
···
Looper.prepareMainLooper();
···
Looper.loop();
···
}
看来没错,那么再看看Looper.prepareMainLooper()
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
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));
}
这里还需要注意一点,这里的set,使用的是当前线程,也就保证了一个Thread只对应一个Looper
好像还差一点,再来,感觉到曙光了
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
到这里,终于找到了关键点,也就是说主线程和子线程通过MeaasgeQueue建立起了连接,这个Queue是同一个对象,那么具体是怎么执行的,前面执行了Looper.prepareMainLooper();
,后面还有Looper.loop();
,那么就来看看这里面做了什么吧
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
得到当前线程Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//得到当前线程Queue
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) { //条件循环
//执行queue的next()方法,不断从queue中取出Message
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
//之前设置了target,这里调用Handler的dispatchMessage()方法
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
//清除已执行的消息
msg.recycleUnchecked();
}
}
这里就是不断循环得到Message,然后分发消息,这里的重点在于dispatchMessage()
做了啥
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到,这里由于前面没有设置callback,所以执行的是handleMessage()
,那么再继续看看
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
这里并没有实现这个方法,注明了要实现这个方法才能收到消息,到此,在子线程发出的消息在主线程就能够得到了
那么到这里,消息机制的源代码分析也就完成了
思路整理
这里画个图就简单明了
其工作原理就是通过Handler将消息发送给MessageQueue,而MessageQueue又是由Looper创建的,这里Handler和Looper使用的是同一个Queue,此时在Looper中通过loop()会不断从MessageQueue中读取消息,当msg全部都发送以后,回收消息。
Message: 消息对象
MessageQueen: 存储消息对象的队列
Looper: 读取MessageQueue里面的消息,交由Handler处理
Handler:发送消息和处理消息
handler一定要记得在页面退出时remove
至此,Handler消息机制就分析完毕了
源码分析之Handler的更多相关文章
- 源码分析Android Handler是如何实现线程间通信的
源码分析Android Handler是如何实现线程间通信的 Handler作为Android消息通信的基础,它的使用是每一个开发者都必须掌握的.开发者从一开始就被告知必须在主线程中进行UI操作.但H ...
- Android Handler处理机制 ( 一 )(图+源码分析)——Handler,Message,Looper,MessageQueue
android的消息处理机制(图+源码分析)——Looper,Handler,Message 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习 google大牛们的设计思想. ...
- Android源码分析笔记--Handler机制
#Handler机制# Handler机制实际就是实现一个 异步消息循环处理器 Handler的真正意义: 异步处理 Handler机制的整体表述: 消息处理线程: 在Handler机制中,异步消息处 ...
- Android源码分析之Handler
接上一篇分析,正如Android doc所说,Handler主要有2方面用处: 1. delay执行同一线程中的某个操作,也就是schedule message.runnable在未来的某一时刻执行: ...
- android的消息处理机制(图文+源码分析)—Looper/Handler/Message[转]
from:http://www.jb51.net/article/33514.htm 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.andro ...
- Android -- 消息处理机制源码分析(Looper,Handler,Message)
android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因 ...
- spring websocket源码分析续Handler的使用
1. handler的定义 spring websocket支持的消息有以下几种: 对消息的处理就使用了Handler模式,抽象handler类AbstractWebSocketHandler.jav ...
- Android源码分析-消息队列和Looper
转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/17361775 前言 上周对Android中的事件派发机制进行了分析,这次博主 ...
- Spring源码分析之`BeanFactoryPostProcessor`调用过程
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...
随机推荐
- Tomcat项目内存参数调优
一.常见的Java内存溢出有以下三种: 1. Java.lang.OutOfMemoryError: Java heap space 即JVM Heap溢出 解释说明:JVM在启动的时候会自动设置JV ...
- 机房断电,导致xfs文件系统损坏
记一次机房断电,导致xfs文件系统损坏处理方法 挂载时报以下错误: mount: mount /dev/sdb on /dev/sdb failed: Structure needs cleaning ...
- Vue.directive全局自定义指令案例
今天正好这个知识点有点淡忘了,就随笔一下吧: Vue.directive(参数1,参数2) 参数1:指令名称,如"drag" 参数2:指令要实现的回调函数,其中回调函数中也有两个参 ...
- jmxtrans docker-compose 运行
以下是一个简单的demo,使用jmxtrans 进行jmx 指标的处理,项目使用docker-compose 运行 同时写入数据到graphite 环境准备 docker-compose文件 ve ...
- HAVING 搜索条件在进行分组操作之后应用
HAVING 搜索条件在进行分组操作之后应用: 如:查询帖子访问量大于15的用户id: select t.user_id,u.name,sum(count_view) from t_topic t l ...
- linux定时执行shell脚本
写一个shell脚本,定时执行简单示例 很多时候我们有希望服务器定时去运行一个脚本来触发一个操作,比如说定时去备份服务器数据.数据库数据等 不适合人工经常做的一些操作这里简单说下 Shell俗称壳,类 ...
- redis状态详解
redis查看状态信息 info all|default Info 指定项 server服务器信息 redis_version : Redis 服务器版本 redis_git_sha1 : Git S ...
- 关于证书如何完成身份验证(SSL证书)
一.写在前面 SSL和IPsec是现在VPN技术中最为常见的,在云计算的应用环境中,SSL更受企业青睐,至于原因的话简单的说就是SSL更为简洁,不需要像IPsec那样需要额外安装客户端,这会带来软件维 ...
- Win10电脑桌面壁纸自动变成黑色无法更换怎么解决
很多用户在升级到win10之后,发现在使用过程中经常会碰到一些问题,就是电脑桌面壁纸总是会自动变成黑色,而且无法设置桌面背景壁纸,这是怎么回事呢,出现这样的问题可能是因为系统不是正版,或者是电脑设置不 ...
- 每天有300W的pv,我们单台机器的QPS为58,大概需要部署几台这样机器?
每天有300W的pv,我们单台机器的QPS为58,大概需要部署几台这样机器? 一.总结 一句话总结: ( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS) 13 ...