一、背景
R8作为谷歌官方的编译优化工具,在编译阶段会对字节码进行大规模修改,以追求包体优化和性能提升。但是Android应用开发者数量太过庞大,无论测试流程多么完善,终究难以避免在一些特定场景下出现问题。
近期我们在升级项目的AGP,遇到了一个指向系统SurfaceTexture类的native崩溃问题。经反编译分析发现问题最终指向了smali字节码中多余的一行new-instance指令。

该指令创建了一个SurfaceTexture对象,但是并未调用其<init>方法,这意味着构造方法没有执行,但是这个类重写了finalize方法,后续被gc回收时会调用其中的nativeFinalize这个JNI方法,最终在native层执行析构函数时触发了SIGNALL 11的内存访问错误.

二、复现问题
我们注意到多出来的new-instance指令下面紧接着的是对a0.e 类中的静态方法 i() 的调用,其内部实现就是SurfaceTexture的构造方法。这是典型的代码外联操作,即一段相同的代码在工程中多次出现,则会被抽出来单独作为一个静态函数,原先的调用点则替换成该函数的调用,这样可以减小代码体积,是常见的编码思路。
例如:
class Activity{
void onCreate(){
// ...
String a = xx.xxx();
String b = xx.xxx();
Log.e("log",a+b);
//...
}
void onReusme(){
// ...
String a = xx.xxx();
String b = xx.xxx();
Log.e("log",a+b);
//...
}
}
class Activity{
void onCreate(){
// ...
Activity$Outline.log();
//...
}
void onReusme(){
// ...
Activity$Outline.log();
//...
}
}
//外联生成的类
class Activity$Outline{
public static void log(){
String a = xx.xxx();
String b = xx.xxx();
Log.e("log",a+b);
}
}
我们根据这个生成类的类名可以知道是R8中ApiModelOutline功能生成了这个类。

我们进到R8工程中检索下相关的关键字,再加上demo多次尝试,可以确认满足以下条件能够必现该问题:
- 使用了高于当前minSdkVersion的系统函数/变量(仅限系统类,自己写的无效)
- 用synchronized或者try语句块包裹了该调用,或者给该函数传参时有任何计算行为(除了传局部变量)。例如:
- new SurfaceTexture( getParmas() )
- new SurfaceTexture( if(enable) 1 : 2)
- new SurfaceTexture ( (boolean) enable )
三、问题分析
在确认复现条件之后,我们带着几个问题来逐个分析。
ApiModel外联是什么?
R8中的优化大多数跟包体优化有关,代码外联也是其中一种,但是外联的前提是代码重复的次数满足一定阈值,但是ApiModel会对所有调用了高版本系统API的代码做外联,包括只调用一次的场景。
ApiModel并非为了包体优化,我们通过R8工程的issueTracker(https://issuetracker.google.com/issues/333477035)检索到了相关的信息:
![]()
译:AGP新增的ApiModel功能是为了防止在低版本设备上不可能执行的代码引起类验证错误,从而降低App启动耗时。
从这篇介绍ART虚拟机类验证的文档(https://chromium.googlesource.com/chromium/src/+/HEAD/build/android/docs/class_verification_failures.md#chromium_s-solution)就能够理解上面这句话的含义:
ART虚拟机会在APK安装之后立刻执行 AOT clas

最低0.47元/天 解锁文章
954

被折叠的 条评论
为什么被折叠?



