Android java.lang.IncompatibleClassChangeError

最近为了解决一个问题,需要在升级X应用时对其做startInstrumentation。功能上线后,数据监控平台发现存在IncompatibleClassChangeError,主要集中在安卓5.1,6.0,少量在安卓8.0。究竟是怎么回事呢?一起来分析一下。

安卓6.0机器

java.lang.IncompatibleClassChangeError: retrofit2.converter.gson.GsonConverterFactory at dalvik.system.DexFile.defineClassNative(Native Method) at dalvik.system.DexFile.defineClass(DexFile.java:226) at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:219) at dalvik.system.DexPathList.findClass(DexPathList.java:339) at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:54) at java.lang.ClassLoader.loadClass(ClassLoader.java:511) at java.lang.ClassLoader.loadClass(ClassLoader.java:469) at

安卓8.0机器:

java.lang.IncompatibleClassChangeError: Structural change of retrofit2.Converter$Factory is hazardous (/data/app/com.xxx.xx-2zz63J0ZnL47Rw6U5B3wfw==/oat/arm/base.odex at compile time, /data/app/com.xxx.tool-L0IRpZwtps6TiRfTUrRDAw==/oat/arm/base.odex at runtime): Direct method count off: 3 vs 1 Lretrofit2/Converter$Factory; (Compile time): Static fields: Instance fields: Direct methods: ()V getParameterUpperBound(ILjava/lang/reflect/ParameterizedType;)Ljava/lang/reflect/Type;

注意安卓6抛出的AndroidRuntime没有直接说明原因,不如安卓8清晰。

首先看一下IncompatibleClassChangeError的定义:

java.lang.Object
java.lang.Throwable
java.lang.Error
java.lang.LinkageError
java.lang.IncompatibleClassChangeError

当某个类定义发生了不兼容的变更时抛出。当前正在执行的方法所依赖的某个类的类定义已经发生了变化。

问题原因

经过分析源码发现,谷歌在Jan 15, 2015,也就是安卓5.1.1开始,加入了以下提交:

ART: Simple structural class check (123364) · Gerrit Code Review

1
2
3
4
5
6
7
8
9
10
11
ART: Simple structural class check

Adds a simple check to class-loading when the embedded dex file in
an oat file and the dex file on the class path where we found the
class do not match.

We require that the number of methods and fields do not change, as
that will almost certainly mean that quickened and other compiled
offsets are wrong now. This is a reasonably lightweight change, but
we should investigate a full comparison including name and type of
members.

关键方法就是CheckSuperClassChangeSimpleStructuralCheck,简单来说就是当art虚拟机发现加载类的dex路径和父类dex路径不一致时,就会执行SimpleStructuralCheck检查,如果发现来自两个dex文件的父类的方法数、字段数不一致,就会抛出异常。

但是从类的兼容性角度来看,这样的粗暴检查是不合理的。

Add API method If method need not be reimplemented by Client If method must be reimplemented by Client Binary compatible (0) Breaks compatibility (1)
Delete API method - Breaks compatibility
Move API method up type hierarchy If method in supertype need not be reimplemented by Client <br If method in supertype must be reimplemented by Client Binary compatible Breaks compatibility (9)
Move API method down type hierarchy - Breaks compatibility (9)
Add API constructor If there are other constructors If this is only constructor Binary compatible Breaks compatibility (2)
Delete API constructor - Breaks compatibility
Add API field If class is not subclassable by Client (this includes enums) If class is subclassable by Client Binary compatible May break compatibility (3)
Delete API field - Breaks compatibility
Expand superinterface set (direct or inherited) - Binary compatible
Contract superinterface set (direct or inherited) - Breaks compatibility (4)
Expand superclass set (direct or inherited) - Binary compatible
Contract superclass set (direct or inherited) - Breaks compatibility (4)
Add, delete, or change static or instance initializers - Binary compatible
Add API type member - Binary compatible
Delete API type member - Breaks compatibility
Re-order field, method, constructor, and type member declarations - Binary compatible
Add or delete non-API members; that is, private or default access fields, methods, constructors, and type members - Binary compatible
Change abstract to non-abstract - Binary compatible
Change non-abstract to abstract - Breaks compatibility (5)
Change final to non-final - Binary compatible
Change non-final to final - Breaks compatibility (6)
Add type parameter If class has no type parameters If class has type parameters Binary compatible (7) Breaks compatibility
Delete type parameter - Breaks compatibility
Re-order type parameters - Breaks compatibility
Rename type parameter - Binary compatible
Add, delete, or change type bounds of type parameter - Breaks compatibility
Rename enum constant - Breaks compatibility
Add, change, or delete enum constant arguments - Binary compatible
Add, change, or delete enum constant class body - Binary compatible
Add enum constant - Binary compatible (8)
Delete enum constant - Breaks compatibility
Re-order enum constants - Binary compatible (8)

后面谷歌意识到了这个问题,在安卓8.1去掉了,Revert “ART: Simple structural class check” (421051) · Gerrit Code Review

1
2
3
4
5
6
7
Revert "ART: Simple structural class check"

This reverts commit fd9eb3923dcf417afcf5ed4ebb13867fd10f2de3.

Remove the simple structural check. Its naive expectations for
resolution have been wrong since we introduced compiling APKs with
a classpath for shared libraries and have never been updated.

所以,java.lang.IncompatibleClassChangeError: Structural change of ……. 这个错误波及范围是安卓5.1到安卓8.0。

源码分析

安卓6.0

class_linker.cc - OpenGrok cross reference for /art/runtime/class_linker.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Very simple structural check on whether the classes match. Only compares the number of
// methods and fields.
// 校验当前加载的父类与编译时(dex2oat)的父类是否一致(odex中的数据结构)
static bool SimpleStructuralCheck(const DexFile& dex_file1, const DexFile::ClassDef& dex_class_def1,
const DexFile& dex_file2, const DexFile::ClassDef& dex_class_def2,
std::string* error_msg) {
ClassDataItemIterator dex_data1(dex_file1, dex_file1.GetClassData(dex_class_def1));
ClassDataItemIterator dex_data2(dex_file2, dex_file2.GetClassData(dex_class_def2));

// Counters for current dex file.
size_t dex_virtual_methods1, dex_direct_methods1, dex_static_fields1, dex_instance_fields1;
CountMethodsAndFields(dex_data1, &dex_virtual_methods1, &dex_direct_methods1, &dex_static_fields1,
&dex_instance_fields1);
// Counters for compile-time dex file.
size_t dex_virtual_methods2, dex_direct_methods2, dex_static_fields2, dex_instance_fields2;
CountMethodsAndFields(dex_data2, &dex_virtual_methods2, &dex_direct_methods2, &dex_static_fields2,
&dex_instance_fields2);

if (dex_virtual_methods1 != dex_virtual_methods2) {
std::string class_dump = DumpClasses(dex_file1, dex_class_def1, dex_file2, dex_class_def2);
*error_msg = StringPrintf("Virtual method count off: %zu vs %zu\n%s", dex_virtual_methods1,
dex_virtual_methods2, class_dump.c_str());
return false;
}
if (dex_direct_methods1 != dex_direct_methods2) {
std::string class_dump = DumpClasses(dex_file1, dex_class_def1, dex_file2, dex_class_def2);
*error_msg = StringPrintf("Direct method count off: %zu vs %zu\n%s", dex_direct_methods1,
dex_direct_methods2, class_dump.c_str());
return false;
}
if (dex_static_fields1 != dex_static_fields2) {
std::string class_dump = DumpClasses(dex_file1, dex_class_def1, dex_file2, dex_class_def2);
*error_msg = StringPrintf("Static field count off: %zu vs %zu\n%s", dex_static_fields1,
dex_static_fields2, class_dump.c_str());
return false;
}
if (dex_instance_fields1 != dex_instance_fields2) {
std::string class_dump = DumpClasses(dex_file1, dex_class_def1, dex_file2, dex_class_def2);
*error_msg = StringPrintf("Instance field count off: %zu vs %zu\n%s", dex_instance_fields1,
dex_instance_fields2, class_dump.c_str());
return false;
}

return true;
}

// klass 表示当前加载的类
// dex_file 为当前加载的类所在的 dex 文件
// class_def 表示当前类在 dex 文件中的数据结构
// super_class 表示当前加载的父类,正常来说(app_image的场景除外)这个类是按照双亲委托模型加载的父类,而要校验的正是这个类
// Checks whether a the super-class changed from what we had at compile-time. This would
// invalidate quickening.
static bool CheckSuperClassChange(Handle<mirror::Class> klass,
const DexFile& dex_file,
const DexFile::ClassDef& class_def,
mirror::Class* super_class)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// Check for unexpected changes in the superclass.
// Quick check 1) is the super_class class-loader the boot class loader? This always has
// precedence.
if (super_class->GetClassLoader() != nullptr &&
// Quick check 2) different dex cache? Breaks can only occur for different dex files,
// which is implied by different dex cache.
klass->GetDexCache() != super_class->GetDexCache()) {
// Now comes the expensive part: things can be broken if (a) the klass' dex file has a
// definition for the super-class, and (b) the files are in separate oat files. The oat files
// are referenced from the dex file, so do (b) first. Only relevant if we have oat files.
const OatDexFile* class_oat_dex_file = dex_file.GetOatDexFile();
const OatFile* class_oat_file = nullptr;
if (class_oat_dex_file != nullptr) {
class_oat_file = class_oat_dex_file->GetOatFile();
}
// class_oat_file不能为空
if (class_oat_file != nullptr) {
const OatDexFile* loaded_super_oat_dex_file = super_class->GetDexFile().GetOatDexFile();
const OatFile* loaded_super_oat_file = nullptr;
if (loaded_super_oat_dex_file != nullptr) {
loaded_super_oat_file = loaded_super_oat_dex_file->GetOatFile();
}

if (loaded_super_oat_file != nullptr && class_oat_file != loaded_super_oat_file) {
// Now check (a).
const DexFile::ClassDef* super_class_def = dex_file.FindClassDef(class_def.superclass_idx_);
if (super_class_def != nullptr) {
// Uh-oh, we found something. Do our check.
std::string error_msg;
if (!SimpleStructuralCheck(dex_file, *super_class_def,
super_class->GetDexFile(), *super_class->GetClassDef(),
&error_msg)) {
// Print a warning to the log. This exception might be caught, e.g., as common in test
// drivers. When the class is later tried to be used, we re-throw a new instance, as we
// only save the type of the exception.
LOG(WARNING) << "Incompatible structural change detected: " <<
StringPrintf(
"Structural change of %s is hazardous (%s at compile time, %s at runtime): %s",
PrettyType(super_class_def->class_idx_, dex_file).c_str(),
class_oat_file->GetLocation().c_str(),
loaded_super_oat_file->GetLocation().c_str(),
error_msg.c_str());
ThrowIncompatibleClassChangeError(klass.Get(),
"Structural change of %s is hazardous (%s at compile time, %s at runtime): %s",
PrettyType(super_class_def->class_idx_, dex_file).c_str(),
class_oat_file->GetLocation().c_str(),
loaded_super_oat_file->GetLocation().c_str(),
error_msg.c_str());
return false;
}
}
}
}
}
return true;
}

就拿我司应用报的错误来说,A应用在编译时集成了一个新版本的Lretrofit2/Converter$Factory,插桩应用B集成的是一个旧版本的Lretrofit2/Converter$Factory,执行插桩时,A和B是运行在同一个进程的,又因为B应用的classloader路径排在A的前面。所以,当A应用初始化GsonConverterFactory (B不包含这个类)时,根据类双亲委派原则,优先加载的是应用B的Lretrofit2/Converter$Factory,符合CheckSuperClassChange规则:GsonConverterFactory 类的dex路径和父类Lretrofit2/Converter$Factorydex路径不一致,进入了SimpleStructuralCheck检查,这时候就会拿A和B应用里面的Lretrofit2/Converter$Factory类进行检查,结果发现方法数不一致,导致了异常。

疑问

上面源码分析提到在安卓5.1到安卓8.0都有这个检查,但根据监控平台的数据显示在安卓8.0基本不会发生,为什么呢?

我们再来看一下CheckSuperClassChange这个方法,除了要求类和父类来自不同的dex文件,还要求class_oat_file不能为空,也就是对应的odex文件不能为空,否则也不会执行SimpleStructuralCheck。那么什么情况class_oat_file为空呢?那么我们就需要了解oat文件的打开过程了。

oat文件加载流程

在这里我不会详细分析oat文件的加载流程,如需深入了解可以参考附录。我更关心的是class_oat_file在什么情况为空。

oat文件加载流程关键入口是:ClassLinker::OpenDexFilesFromOat

首先来看一下安卓6的:

class_linker.cc - OpenGrok cross reference for /art/runtime/class_linker.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat(
const char* dex_location, const char* oat_location,
std::vector<std::string>* error_msgs) {
CHECK(error_msgs != nullptr);
.....
.....
// Check if we already have an up-to-date oat file open.
//检查内存是否存在对应的oat文件
const OatFile* source_oat_file = nullptr;
{
ReaderMutexLock mu(Thread::Current(), dex_lock_);
for (const OatFile* oat_file : oat_files_) {
CHECK(oat_file != nullptr);
if (oat_file_assistant.GivenOatFileIsUpToDate(*oat_file)) {
source_oat_file = oat_file;
break;
}
}
}

// If we didn't have an up-to-date oat file open, try to load one from disk.
//如果内存没有则从磁盘加载
if (source_oat_file == nullptr) {
// Update the oat file on disk if we can. This may fail, but that's okay.
// Best effort is all that matters here.
// 关键地方:检查磁盘的oat文件是否需要更新
if (!oat_file_assistant.MakeUpToDate(&error_msg)) {
LOG(WARNING) << error_msg;
}

// Get the oat file on disk.
// 从磁盘加载oat文件
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
if (oat_file.get() != nullptr) {
// Take the file only if it has no collisions, or we must take it because of preopting.
// 关键地方:检查待加载的oat文件是否和已经加载的存在类冲突
// 如果存在类冲突,考虑直接加载apk里面的dex文件
bool accept_oat_file = !HasCollisions(oat_file.get(), &error_msg);
if (!accept_oat_file) {
// Failed the collision check. Print warning.
if (Runtime::Current()->IsDexFileFallbackEnabled()) {
LOG(WARNING) << "Found duplicate classes, falling back to interpreter mode for "
<< dex_location;
} else {
LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to "
" load classes for " << dex_location;
}
LOG(WARNING) << error_msg;

// However, if the app was part of /system and preopted, there is no original dex file
// available. In that case grudgingly accept the oat file.
// 系统应用大部分都会在编译服务器做dex优化,这时候apk里面就不会包含dex文件了。这种情况的话还是加载oat文件
if (!DexFile::MaybeDex(dex_location)) {
accept_oat_file = true;
LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. "
<< "Allow oat file use. This is potentially dangerous.";
}
}

if (accept_oat_file) {
// 获取oat文件
source_oat_file = oat_file.release();
RegisterOatFile(source_oat_file);
}
}
}

std::vector<std::unique_ptr<const DexFile>> dex_files;

// Load the dex files from the oat file.
// 加载oat文件里面的dex文件
if (source_oat_file != nullptr) {
dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);
if (dex_files.empty()) {
error_msgs->push_back("Failed to open dex files from "
+ source_oat_file->GetLocation());
}
}

// Fall back to running out of the original dex file if we couldn't load any
// dex_files from the oat file.
// 如果加载oat文件里面的dex文件失败,则直接尝试加载apk里面的dex文件。
if (dex_files.empty()) {
if (oat_file_assistant.HasOriginalDexFiles()) {
if (Runtime::Current()->IsDexFileFallbackEnabled()) {
if (!DexFile::Open(dex_location, dex_location, &error_msg, &dex_files)) {
LOG(WARNING) << error_msg;
error_msgs->push_back("Failed to open dex files from " + std::string(dex_location));
}
} else {
error_msgs->push_back("Fallback mode disabled, skipping dex files.");
}
} else {
error_msgs->push_back("No original dex files found for dex location "
+ std::string(dex_location));
}
}
return dex_files;
}

从上面源码分析可以发现,如果dex文件来自于apk而不是oat文件的话class_oat_file就会为空,不会执行SimpleStructuralCheck

安卓8:

入口也是ClassLinker::OpenDexFilesFromOat,只不过迁移到了oat_file_manager.cc里面:

oat_file_manager.cc - OpenGrok cross reference for /art/runtime/oat_file_manager.cc

过程也是大同小异的,研究发现HasCollisions方法有不一样的地方。

安卓6开始引入HasCollisions这个方法

安卓6:

1
2
3
4
5
6
7
8
9
10
// For b/21333911.
static constexpr bool kDuplicateClassesCheck = false;

bool ClassLinker::HasCollisions(const OatFile* oat_file, std::string* error_msg) {
if (!kDuplicateClassesCheck) { //这个设置成了false
return false;
}
........
........
}

搜索一下kDuplicateClassesCheck看是干什么的,runtime/oat_file_manager.cc - platform/art - Git at Google

1
2
3
4
// For b/21333911.
// Only enabled for debug builds to prevent bit rot. There are too many performance regressions for
// normal builds.
static constexpr bool kDuplicateClassesCheck = kIsDebugBuild;

所以在安卓6是不会触发这个检查的,查看源码后发现安卓7.0才开始默认启用。

因此,在安卓7.0开始,如果插桩应用或者插件应用包含了和宿主应用一样的类,一般来说,也不会触发SimpleStructuralCheck

实验

我在一台64位的安卓6.0机器进行插桩和加载插件测试:

插桩测试

安装应用时会做dex优化,生成oat文件。

1
2
3
4
5
6
06-24 16:56:07.393 21329 21384 I PackageManager.DexOptimizer: Running dexopt (dex2oat) on: /data/app/vmdl566966682.tmp/base.apk pkg=com.example.myapplication.myapplication isa=arm vmSafeMode=false debuggable=false oatDir = /data/app/vmdl566966682.tmp/oat
06-24 16:56:07.422 5953 5953 I dex2oat : Starting dex2oat.
06-24 16:56:07.874 5953 5958 I dex2oat : Skipping compilation of long com.alibaba.fastjson.parser.JSONLexer.longValue(): it contains a non natural loop
06-24 16:56:07.933 5953 5958 I dex2oat : Skipping compilation of double[] com.alibaba.fastjson.parser.JSONLexer.scanFieldDoubleArray(long): it contains a non natural loop
06-24 16:56:07.963 5953 5958 I dex2oat : Skipping compilation of float[] com.alibaba.fastjson.parser.JSONLexer.scanFieldFloatArray(long): it contains a non natural loop
06-24 16:56:09.083 5953 5953 I dex2oat : dex2oat took 1.659s (threads: 4) arena alloc=10MB java alloc=971KB native alloc=15MB free=600KB

情况1:64位应用对32位应用做插桩

加载oat文件,执行MakeUpToDate检查时,发现安装时的oat文件不适用于32位环境,需要重新生成oat文件,结果因为权限问题生成失败。结果就是class_oat_file为空,不会执行SimpleStructuralCheck。所以,一般不会出现IncompatibleClassChangeError

加载类时, fork出来的dex2oat是应用权限,不能对/data/dalvik-cache/(root权限)进行读写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
06-24 16:36:29.851 32154 32154 W art     : wesley GivenOatFileIsUpToDate is false, dex_location:/data/app/com.example.myapplication.myapplication-1/base.apk
06-24 16:36:29.851 32154 32154 W art : wesley source_oat_file is null
06-24 16:36:29.851 32154 32154 W art : wesley :GetDexOptNeeded HasOriginalDexFiles: 1
06-24 16:36:29.889 32174 32174 E dex2oat : Failed to create oat file: /data/dalvik-cache/arm/data@app@com.example.myapplication.myapplication-1@base.apk@classes.dex: Permission denied
06-24 16:36:29.889 32174 32174 I dex2oat : dex2oat took 307.667us (threads: 4)
06-24 16:36:29.893 32154 32154 W art : wesley MakeUpToDate fail: Failed execv(/system/bin/dex2oat --runtime-arg -classpath --runtime-arg --instruction-set=arm --instruction-set-features=smp,div,-atomic_ldrd_strd --runtime-arg -Xrelocate --boot-image=/system/framework/boot.art --runtime-arg -Xms64m --runtime-arg -Xmx512m --instruction-set-variant=cortex-a7 --instruction-set-features=default --dex-file=/data/app/com.example.myapplication.myapplication-1/base.apk --oat-file=/data/dalvik-cache/arm/data@app@com.example.myapplication.myapplication-1@base.apk@classes.dex) because non-0 exit status
06-24 16:36:29.893 32154 32154 W art : wesley oat_file is null:
06-24 16:36:29.893 32154 32154 W art : wesley :HasOriginalDexFiles true
06-24 16:36:29.985 32154 32154 W art : wesley :load OriginalDexFiles success
06-24 16:36:29.986 32154 32154 W art : wesley GivenOatFileIsUpToDate is false, dex_location:/data/app/com.tianci.de-2/base.apk
06-24 16:36:29.986 32154 32154 W art : wesley source_oat_file is null
06-24 16:36:29.989 32154 32154 W art : wesley :GetDexOptNeeded: kNoDexOptNeeded
06-24 16:36:29.989 32154 32154 W art : wesley MakeUpToDate success
06-24 16:36:29.989 32154 32154 W art : wesley accept_oat_file:1

情况2:32位应用对32位应用做插桩

通过adb install --abi armeabi-v7a <path to apk>强制指定应用运行在32位,这种情况class_oat_file不会为空了,就会触发:Incompatible structural change detected: Structural change......

插件加载

情况1:通过PathClassLoaderloadClass(通过adb install安装的插件),会触发:Incompatible structural change detected: Structural change......(两个应用都是64位安装)

情况2:没有安装的插件,应用运行正常,也是因为权限问题做dex2oat失败,直接从apk文件加载了dex。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
06-24 16:11:56.975 24635 24635 W art     : wesley GivenOatFileIsUpToDate is false, dex_location:/data/app/com.example.myapplication.myapplication-2/base.apk
06-24 16:11:56.975 24635 24635 W art : wesley source_oat_file is null
06-24 16:11:56.979 24635 24635 W art : wesley :GetDexOptNeeded: kNoDexOptNeeded
06-24 16:11:56.979 24635 24635 W art : wesley MakeUpToDate success
06-24 16:11:56.979 24635 24635 W art : wesley accept_oat_file:1
06-24 16:11:57.050 24635 24635 W art : wesley GivenOatFileIsUpToDate is false, dex_location:/data/app/plugin.platform-1/base.apk
06-24 16:11:57.050 24635 24635 W art : wesley GivenOatFileIsUpToDate is false, dex_location:/data/app/plugin.platform-1/base.apk
06-24 16:11:57.050 24635 24635 W art : wesley source_oat_file is null
06-24 16:11:57.053 24635 24635 W art : wesley :GetDexOptNeeded: kNoDexOptNeeded
06-24 16:11:57.053 24635 24635 W art : wesley MakeUpToDate success
06-24 16:11:57.053 24635 24635 W art : wesley accept_oat_file:1

06-24 16:11:57.063 24635 24635 W art : wesley GivenOatFileIsUpToDate is false, dex_location:/data/user/0/com.example.myapplication.myapplication/files/Plugin.apk
06-24 16:11:57.063 24635 24635 W art : wesley GivenOatFileIsUpToDate is false, dex_location:/data/user/0/com.example.myapplication.myapplication/files/Plugin.apk
06-24 16:11:57.063 24635 24635 W art : wesley GivenOatFileIsUpToDate is false, dex_location:/data/user/0/com.example.myapplication.myapplication/files/Plugin.apk
06-24 16:11:57.063 24635 24635 W art : wesley source_oat_file is null
06-24 16:11:57.063 24635 24635 W art : wesley :GetDexOptNeeded HasOriginalDexFiles: 1
06-24 16:11:57.107 24661 24661 E dex2oat : Failed to create oat file: /data/dalvik-cache/arm64/data@user@0@com.example.myapplication.myapplication@files@Plugin.apk@classes.dex: Permission denied
06-24 16:11:57.107 24661 24661 I dex2oat : dex2oat took 295.167us (threads: 4)
06-24 16:11:57.108 24635 24635 W art : wesley MakeUpToDate fail: Failed execv(/system/bin/dex2oat --runtime-arg -classpath --runtime-arg --instruction-set=arm64 --instruction-set-features=smp,a53 --runtime-arg -Xrelocate --boot-image=/system/framework/boot.art --runtime-arg -Xms64m --runtime-arg -Xmx512m --instruction-set-variant=generic --instruction-set-features=default --dex-file=/data/user/0/com.example.myapplication.myapplication/files/Plugin.apk --oat-file=/data/dalvik-cache/arm64/data@user@0@com.example.myapplication.myapplication@files@Plugin.apk@classes.dex) because non-0 exit status
06-24 16:11:57.108 24635 24635 W art : wesley oat_file is null:
06-24 16:11:57.108 24635 24635 W art : wesley :HasOriginalDexFiles true
06-24 16:11:57.121 24635 24635 W art : wesley :load OriginalDexFiles success

解决

看完上面的内容后,应该很容易知道怎么解决了。要么就是想办法通过SimpleStructuralCheck,要么就是想办法不进人SimpleStructuralCheck

想办法通过SimpleStructuralCheck:插桩或者插件的类和宿主保持一致。

想办法不进人SimpleStructuralCheck:插件compileOnly公共接口;插件不安装,以文件的方式进行加载;插件安装,类加载入口中转到插件文件等等。

参考

插件化之 Incompatible structural change detected 问题分析 - 掘金

[原创]菜鸟学8.1版本dex加载流程笔记–第一篇:oat_file_manager与oat_file_assistant-Android安全-看雪-安全社区|安全招聘|kanxue.com

Chapter 13. Binary Compatibility


Android java.lang.IncompatibleClassChangeError
https://iwesley.top/article/a299c970/
作者
Wesley
发布于
2024年6月30日
许可协议