Handler --- Message code analyze

前言

在看公司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源码:

能得到的信息大致如下:

  1. 这个类是用来给Handler传递信息的,任何类型的对象都可以;
  2. 为了让你在有的情况下不分发信息,该类还提供了两个额外的int,一个额外的对象来作区分。
  3. 推荐的获取信息方式: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();
}

从这段代码里我们可以了解到如下资讯:

  1. 在同一个时间里,只能有一个资源通过obtain来得到信息;其它资源如果想obtain,要等上一个obtain处理完后才可以;
  2. message通过一个资源池来obtain资源,就是code里的sPool;
  3. 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();
相关推荐
路上阡陌11 分钟前
Java学习笔记(二十四)
java·笔记·学习
束照43 分钟前
noteboolm 使用笔记
笔记·notebooklm
安冬的码畜日常1 小时前
【Vim Masterclass 笔记23】第十章:Vim 缓冲区与多窗口的用法概述 + S10L42:Vim 缓冲区的用法详解与多文件编辑
笔记·vim·buffer·vim缓冲区·vim buffer·vim多文件编辑·vim多文件
初九之潜龙勿用2 小时前
我的创作纪念日,纪念我的第512天
笔记
有书Show2 小时前
媒体新闻发稿要求有哪些?什么类型的稿件更好通过?
经验分享
WPG大大通2 小时前
窥探QCC518x-308x系列与手机之间的蓝牙HCI记录与分析 - 耳机篇
经验分享·智能手机·教程·蓝牙·大大通·耳机
雾里看山4 小时前
【MySQL】 库的操作
android·数据库·笔记·mysql
Qiuner4 小时前
困境如雾路难寻,心若清明步自轻---2024年创作回顾
经验分享·博客之星·2024
cdut_suye5 小时前
踏浪而行,2024年技术创作的星光轨迹
经验分享·笔记·学习方法·热榜·博客之星·学习历程·回顾2024
怪小庄吖6 小时前
翻译:How do I reset my FPGA?
经验分享·嵌入式硬件·fpga开发·硬件架构·硬件工程·信息与通信·信号处理