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();
相关推荐
kkkkk02110634 分钟前
黑马微服务保险(一)
笔记·微服务·架构
hour_go1 小时前
【知识图谱】图神经网络(GNN)核心概念详解:从消息传递到实战应用
笔记·深度学习·神经网络·1024程序员节
摇滚侠1 小时前
全面掌握PostgreSQL关系型数据库,设置远程连接,笔记05,笔记06
java·数据库·笔记·postgresql
蒙奇D索大2 小时前
【数据结构】数据结构核心考点:AVL树删除操作详解(附平衡旋转实例)
数据结构·笔记·考研·学习方法·改行学it·1024程序员节
开心-开心急了2 小时前
Flask入门教程——李辉 第5章: 数据库 关键知识梳理
笔记·后端·python·flask·1024程序员节
TeleostNaCl3 小时前
一种使用 PowerToys 的键盘管理器工具编辑惠普暗影精灵11 的 OMEN 自定义按键的方法
windows·经验分享·计算机外设·1024程序员节
溜追3 小时前
OEC-Turbo刷群晖&Armbian流程记录
linux·经验分享·嵌入式硬件
charlie1145141915 小时前
HTML 理论笔记
开发语言·前端·笔记·学习·html·1024程序员节
橙色云-智橙协同研发5 小时前
PLM实施专家宝典:离散制造企业研发数据“数字基因”构建方案
经验分享·工厂方法模式·解决方案·数字化转型·plm·国产plm·plm方案
岑梓铭6 小时前
考研408《操作系统》复习笔记,第二章《2.3 进程调度》
笔记·考研·操作系统·os