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();
相关推荐
maknul5 小时前
【学习笔记】AD智能PDF导出(装配文件)
笔记·学习·pdf
pq113_65 小时前
ftdi_sio应用学习笔记 4 - I2C
笔记·学习·linux驱动·ftdi_sio
快乐飒男7 小时前
Linux基础05
linux·笔记·学习
田梓燊8 小时前
湘潭大学软件工程算法设计与分析考试复习笔记(六)
笔记·算法·软件工程
网安墨雨8 小时前
网络安全笔记
网络·笔记·web安全
南东山人8 小时前
关于内核编程的一些笔记
linux·笔记
重生之Java开发工程师8 小时前
算法笔记:前缀和
笔记·算法
漆黑的莫莫9 小时前
经验笔记:git checkout 与 git switch
笔记·git
小小逆向9 小时前
[SWPUCTF 2021 新生赛]老鼠走迷宫
笔记
LaoZhangGong1239 小时前
Linux第95步_Linux内核中的INPUT子系统
linux·运维·数据库·经验分享·stm32·input·stm32mp127