1.需求来源
有些APP在特定的设备上会有不一样的行为和显示效果,我们们如果是AOSP开发,想让它在自己的设备上也有同样的行为和显示效果,可以修改Build.java中的static final字段来满足要求。我们以Android13为例。
2.实现
在Build.java中提供的产品信息字段基本都是static final修饰的常量或者String类型:
/** The name of the overall product. */
public static final String PRODUCT = getString("ro.product.name");
/** The name of the industrial design. */
public static final String DEVICE = getString("ro.product.device");
/** The name of the underlying board, like "goldfish". */
public static final String BOARD = getString("ro.product.board");
正常是无法被直接修改,但是可以通过反射修改,在系统开发中我们可以在zygote中fork进程后去尽可能早的反射修改它。我们来看下普通APP进程的启动过程:
Android系统中Build类的static final字段是在Zygote主进程起来时被初始化的,后面fork出的子进程不会再重新赋值,除非在子进程的生命周期方法内才去调用这些字段。从ZygoteInit中的preloads中移除Build类也是没用的,Zygote进程本身有很多地方用到了Build类,也会被它加载。
我们只能在fork子进程之后尽可能早的去修改,如果在zygote进程修改了可能会影响到别的进程的,我们就选在子进程fork后的com.android.internal.os.Zygote#childMain方法中:
...
...
if (args == null) {
throw new AssertionError("Empty command line");
}
//这里判断APP进程的包名,做出相关修改
if ("com.xxx.aaa".equals(args.mPackageName)){
setupBuildInfo("BRAND", "Google");
setupBuildInfo("DEVICE", "xxx");
setupBuildInfo("MANUFACTURER", "Google");
setupBuildInfo("MODEL", "hahahahh");
setupBuildInfo("PRODUCT", "yyyyy");
}
...
...
setupBuildInfo方法实现:
private static void setupBuildInfo(String field, Object value){
try {
// BRAND
Field oldField = Build.class.getDeclaredField(field);
final int mod = oldField.getModifiers();
//设置字段可访问,public修饰的可以不用这步
oldField.setAccessible(true);
Log.d(TAG, "setupBuildInfo: "+field+" "+value+", mode="+mod+", isFinal="+ Modifier.isFinal(mod));
//去除final修饰,否则static final修饰的常量无法修改
Field modifier = Field.class.getDeclaredField("accessFlags");
modifier.setAccessible(true);
modifier.setInt(oldField, mod & ~Modifier.FINAL);
// 重新赋值
oldField.set(null, value);
}catch (Exception e){
Log.e(TAG, "", e);
}
}
测试效果:
939 939 D Zygote : Forked child process 9536
9536 9536 D Zygote : childMain: niceName=com.xxx.aaa, package=com.xxx.aaa, pid=9536, mInvoke=null
9536 9536 D Zygote : setupBuildInfo: BRAND Google, mode=25, isFinal=true
9536 9536 D Zygote : setupBuildInfo: DEVICE xxx, mode=25, isFinal=true
9536 9536 D Zygote : setupBuildInfo: MANUFACTURER Google, mode=25, isFinal=true
9536 9536 D Zygote : setupBuildInfo: MODEL hahahahh, mode=25, isFinal=true
9536 9536 D Zygote : setupBuildInfo: PRODUCT yyyyy, mode=25, isFinal=true
9536 9536 D Application: onCreate: com.xxx.aaa, 9536, 10131 brand=Google, device=xxx, manufacturer=Google, model=hahahahh, name=yyyyy
3.总结
这个修改不是对每个APP进程都能达到理想效果,如果进程内使用Build这些信息的地方不是APP进程的生命周期方法内,就会失效。因为APP的范围大于APP进程的范围。