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();
相关推荐
努力变厉害的小超超2 小时前
ArkTS中的组件基础、状态管理、样式处理、class语法以及界面渲染
笔记·鸿蒙
aloha_7896 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
dsywws7 小时前
Linux学习笔记之vim入门
linux·笔记·学习
A-超9 小时前
vue3展示pag格式动态图
笔记
做网站建设制作设计小程序推广10 小时前
如何建购物网站提升用户体验
经验分享
程思扬10 小时前
为什么Uptime+Kuma本地部署与远程使用是网站监控新选择?
linux·服务器·网络·经验分享·后端·网络协议·1024程序员节
u01015265810 小时前
STM32F103C8T6学习笔记2--LED流水灯与蜂鸣器
笔记·stm32·学习
weixin_5182850510 小时前
深度学习笔记10-多分类
人工智能·笔记·深度学习
丘狸尾10 小时前
ubuntu【桌面】 配置NAT模式固定IP
笔记
王俊山IT11 小时前
C++学习笔记----10、模块、头文件及各种主题(二)---- 预处理指令
开发语言·c++·笔记·学习