Android代码生成id
Android 4.2之后,View增加了一个代码生成id的方法,可以被用来设置View的id,并且不会和appt生成的R.id冲突。
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
public static int generateViewId() {
for (;;) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
这个有两个知识点:
- aapt 生成的id 前高字节都是非0的,所以随机生成的id只要保证前两位为0,就可以与aapt生成的id 不冲突。
- 高性能线程安全的写法。
首先看下不考虑线程安全写法,很直白:id从1开始,每次调用时自增,到达0x00FFFFFF时,再回退到从1开始:
private static int sNextGeneratedId = 1;
public static int generateViewId() {
int newValue = sNextGeneratedId + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
sNextGeneratedId = newValue;
return newValue;
}
再看下保证线程安全的写法,只需给generateViewId()方法加个 synchronized 修饰符,像下面这样:
private static int sNextGeneratedId = 1;
public static synchronized int generateViewId() {
int newValue = sNextGeneratedId + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
sNextGeneratedId = newValue;
return newValue;
}
回头看下Android 官方的写法,通过无限循环+ AtomicBoolean 变量,既保证了线程安全,又保证了性能。
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
public static int generateViewId() {
for (;;) {
final int result = sNextGeneratedId.get();
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
为何AtomicInteger和无限循环保证线程安全呢?
- 对于单线程场景,代码符合预期,线程安全;
- 对于多线程场景,
2.0 当前值为result,如果sNextGeneratedId符合该条件,则 compareAndSet(result, newValue)为true,则将sNextGeneratedId的value设置为newValue(result+1),并返回true,跳出循环;
2.1 如果不符合该条件,compareAndSet(result, newValue)为false,进入下次循环,重新获取
sNextGeneratedId的当前值,直到满足2.0的条件,并退出循环。
那为何这种方法比较高效呢?
参考文档:
Java魔法类:Unsafe应用解析