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();
相关推荐
Rabitebla1 小时前
【Linux 底层原理】进程(十):进程等待详解---从僵尸进程到 wait、waitpid 与 status
linux·c++·笔记·学习·visual studio
小lu飞2 小时前
2026小红书小工具怎么制作:纯前端 ZIP 打包、沙箱限制与笔记挂载流程
前端·笔记·ai·ai编程
无心水6 小时前
24、系统架构师论文写作专题 核心考点与学习笔记
笔记·学习·系统架构·架构师·安全架构·软考架构师·软考系统架构设计师
蒸蒸yyyyzwd6 小时前
邓明 Agent 系统面试-意图识别学习笔记
笔记·学习
Zhao-Jichao9 小时前
AI Prompting for Everyone 课程笔记 - P21 总结 Conclusion
人工智能·笔记
欢欢李9 小时前
【LTE L3 笔记 05】LTE同步信号
笔记·无线通信·物理层·lte·空口协议
La Pulga11 小时前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
蒸蒸yyyyzwd11 小时前
秋招学习笔记 day106
笔记·学习
小李不想当小白12 小时前
WDG看门狗(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
欢欢李13 小时前
【LTE L3 笔记 04】LTE帧结构
笔记·无线通信·物理层·lte·空口协议