前言
在看公司wifi p2p framework代码时,老是能看到Handler的使用,之前也看到过一个经典的Handler运用------秒表计数器,大概的一个实现思路就是,主线程里开一个子Thread,设置delay=1000ms循环执行,更新UI的逻辑扔到了new的一个Handler里运行。
本篇文章大致谈下Handler的初印象,还有一些自己的看法,如有错误还请各位指出。
Android系统设定,超过5秒如果主线程未响应,就视为系统发生了异常(ANR)。所以,当主线程里,有耗时操作执行时,同步更新UI是比较危险的。这里只是举了一个计数器的例子,但如果涉及到更大的数据交互场景,耗时操作超过5秒,如果不用Handler来更新UI,就会使主界面进入一个假死状态,这个时候系统就会出现异常抛出ANR了。
Handler涉及到的类有:Message、Messenger、MessageQueue、Looper、Handler。
先来看下Message源码:
能得到的信息大致如下:
- 这个类是用来给Handler传递信息的,任何类型的对象都可以;
- 为了让你在有的情况下不分发信息,该类还提供了两个额外的int,一个额外的对象来作区分。
- 推荐的获取信息方式:Message.obtain(),Handler.obtainMessge()
成员变量
java
public int what; // 区分message
public int arg1;
public int arg2; // arg1,arg2都是可以放置到message里的整数
public Object obj; // obj同arg1,arg2,只不过它是任意对象.
public Messenger replyTo; // 可以将对此信息的回复,发送到指定的Messenger.具体使用要区分主体
/*package*/ int flags;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
// 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;
private static final int MAX_POOL_SIZE = 50;
private static boolean gCheckRecycle = true;
方法
obtain()
有很多不同的obtain方式,override了整整8种方式。首先看看最初始的obtain():
java
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
从这段代码里我们可以了解到如下资讯:
- 在同一个时间里,只能有一个资源通过obtain来得到信息;其它资源如果想obtain,要等上一个obtain处理完后才可以;
- message通过一个资源池来obtain资源,就是code里的sPool;
- message存储信息的形式,像是一个队列(queue),obtain一个之后,会通过m.next来指向下一段message(对应了前文中对next的描述)。
后面的重载obtain,都会先调用obtain()从pool里取出一个message,再把自己想赋值的参数,覆盖到取出来的message里。都是各种成员变量的排列组合,这里就不赘述了。
recycle()
顾名思义,资源回收,一看就知道是用来回收message的。先看recycle()的方法desc:
obtain是从pool拿出来,那么recycle就是把message放进去。放进去后的message就不能再使用了,对于在消息队列中的message,调用recycle是无效的。
java
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
@UnsupportedAppUsage
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
code也很清晰,init所有关于该Message的所有成员变量,并让pool的size+1。
getdata()、peekdata()
两个方法实现的都是一个功能,拿到data。从描述中就能知道差别了:
getdata()
peekdata()
java
public Bundle getData() {
if (data == null) {
data = new Bundle();
}
return data;
}
public Bundle peekData() {
return data;
}
官方描述是,一个缓慢的拿data,一个是马上拿到data。不过我没从code里看到getdata会比peekdata慢多少,new Bundle()看上去也不是一个特别耗时的操作,两者的执行时间真的有很大差别吗?对我来说最大的差别可能就在于,peekData会返回null,而getData至少会返回一个空Bundle差不多了。
sendToTarget()
发送消息到target,前提是Message有提前指定好target,否则会抛null pointer。
java
/**
* Sends this Message to the Handler specified by {@link #getTarget}.
* Throws a null pointer exception if this field has not been set.
*/
public void sendToTarget() {
target.sendMessage(this);
}
其它的一些常见的copy、setter and getter方法,这里就不多叙述了。
总结&心得
Message是Handler机制里负责扮演携带信息的一个角色,sPool永远指向即将会被obtain出去的message。要想滤清Handler机制,还有几个类等着去探索。
掌握更多可以创建message,并send的方式了:
java
// Handler: mHandler, String s
// 1.
Message message = Message.obtain(mHandler);
message.obj = s;
message.sendToTarget();
// 2. 这样写省去了再handler写handleMessage事件,不过也失去了msg判断的能力
Message message = Message.obtain(mHandler, new Runnable() {
@Override
public void run() {
mTextView.setText(s);
}
});
message.sendToTarget();