如何使用 DexClassLoader 从 aar 文件动态加载 class

how to load class dynamically from aar file with DexClassLoader

我已经成功地通过以下方式从 dex 文件动态加载 classes

enter code here
File file = getDir("dex", 0);
DexClassLoader dexClassLoader = new DexClassLoader("/data/data/com.example.callerapp/files/test.dex", file.getAbsolutePath(), null, getClassLoader());
try {
    Class<Object> _class = (Class<Object>) 
    dexClassLoader.loadClass("com.example.calledapp.test");
    Object object = _class.newInstance();
    Method method = _class.getMethod("function");
    method.invoke(object);
} catch (Exception e) {
    e.printStackTrace();
}

但我想做的是从 aar 文件动态加载 class,如 android 开发页面所示(DexClassLoader : A class loader that loads classes 来自包含 classes.dex 条目的 .jar 和 .apk 文件。这可用于执行未作为应用程序的一部分安装的代码。)

我在 Android 工作室中创建了一个库模块 ("testlibrary"),在库模块中创建了 Test.java(我想在调用者应用程序中动态加载的内容),并创建了通过 Gradle 项目 -> 执行 Gradle 任务

的 aar 文件

如何在以这种通用方式创建的 aar 文件中通过 dexclassloader 动态加载 class?我已经通过提供商将 aar 文件从 CalledApp

移动到 CallerApp

或者是创建aar文件的过程出错了? 在运行期间,出现错误消息

02-10 09:43:48.744 16487-16487/com.example.callerapp W/System.err: java.lang.ClassNotFoundException: Didn't find class "com.example.calledlibrary.Test" on path: DexPathList[[zip file "/data/data/com.example.callerapp/files/testlibrary.aar"],nativeLibraryDirectories=[/system/lib64, /vendor/lib64]]
02-10 09:43:48.744 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
02-10 09:43:48.744 16487-16487/com.example.callerapp W/System.err:     at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
02-10 09:43:48.744 16487-16487/com.example.callerapp W/System.err:     at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
02-10 09:43:48.745 16487-16487/com.example.callerapp W/System.err:     at com.example.callerapp.CallerActivity.onClick(CallerActivity.java:42)
02-10 09:43:48.745 16487-16487/com.example.callerapp W/System.err:     at android.view.View.performClick(View.java:6877)
02-10 09:43:48.745 16487-16487/com.example.callerapp W/System.err:     at android.widget.TextView.performClick(TextView.java:12651)
02-10 09:43:48.745 16487-16487/com.example.callerapp W/System.err:     at android.view.View$PerformClick.run(View.java:26069)
02-10 09:43:48.745 16487-16487/com.example.callerapp W/System.err:     at android.os.Handler.handleCallback(Handler.java:789)
02-10 09:43:48.746 16487-16487/com.example.callerapp W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:98)
02-10 09:43:48.746 16487-16487/com.example.callerapp W/System.err:     at android.os.Looper.loop(Looper.java:164)
02-10 09:43:48.746 16487-16487/com.example.callerapp W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6938)
02-10 09:43:48.746 16487-16487/com.example.callerapp W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
02-10 09:43:48.746 16487-16487/com.example.callerapp W/System.err:     at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
02-10 09:43:48.747 16487-16487/com.example.callerapp W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
02-10 09:43:48.747 16487-16487/com.example.callerapp W/System.err:  Suppressed: java.io.IOException: No original dex files found for dex location (arm64) /data/data/com.example.caller/files/testlibrary.aar
02-10 09:43:48.747 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.DexFile.openDexFileNative(Native Method)
02-10 09:43:48.747 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.DexFile.openDexFile(DexFile.java:353)
02-10 09:43:48.747 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.DexFile.<init>(DexFile.java:100)
02-10 09:43:48.748 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.DexFile.<init>(DexFile.java:74)
02-10 09:43:48.748 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374)
02-10 09:43:48.748 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337)
02-10 09:43:48.748 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.DexPathList.<init>(DexPathList.java:157)
02-10 09:43:48.748 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65)
02-10 09:43:48.748 16487-16487/com.example.callerapp W/System.err:     at dalvik.system.DexClassLoader.<init>(DexClassLoader.java:57)
02-10 09:43:48.748 16487-16487/com.example.callerapp W/System.err:     at com.example.caller.CallerActivity.onClick(CallerActivity.java:40)
02-10 09:43:48.749 16487-16487/com.example.callerapp W/System.err:      ... 10 more

您无法在运行时加载 aar 文件,因为 aar 文件包含资源和 classes.jar 文件,但不包含 dex 文件。
但是
你可以使用 injector gradle plugin to get dex from your aar and merge all your aar resources into your project and after that you can use injector-android lib to load that dex files at runtime. Check out inject-example 项目

jar和dex文件的区别

下面的文章详细描述了差异 然后你可以阅读它,然后你可以得到结果

[AAR to DEX] Loading and Running Code at Runtime in Android Application

JAR和AAR的区别

JAR 只是一个 Java 库,包含 java class 个文件,仅此而已。 AAR 是 android 库的一种格式,它包含一个 JAR 文件、android 资源文件(如布局 XML、属性 XML 等)和一个 android 清单文件。 在构建过程中,每个 android 库和主项目都会生成一个 R.java 文件,所有 java 文件都被编译成一个或多个 DEX 文件(DEX 是一个可以通过 android 运行time (ART) 加载的 Dalvik 可执行格式。所以在 APK 中,只有 DEX 文件(不是 JAR 或 AAR 文件),以及资源和清单。 Android R.java 文件是由 AAPT(Android 资源打包工具)自动生成的文件,其中包含 res/ 目录下所有资源的资源 ID。

为什么我需要在 运行 时加载一些代码?

这样做的原因有很多。也许你的依赖库太大,你希望你的 APK 有一个小尺寸,或者可能请求库用于某些不支持所有设备的功能,或者在第一次启动时不需要它,你有自己的差异化逻辑,如果设备是否支持该功能,或者您是否需要向用户展示该功能。为什么要发布带有该功能代码的 APK?如果您正在阅读本文,我想您已经有自己的理由了:)

JAR 到 DEX

Android不支持加载JAR文件,所以必须有办法将JAR文件编译成DEX文件。为此,有位于 android_sdk/build-tools/version/ 中的 D8 工具。要将 JAR 转换为 DEX,您可以从命令行 运行 此命令

d8 --release --output lib.dex path_to_jar_lib.jar

DEX 文件已生成,无需使用该 JAR 库构建 android 项目,因此在 gradle 依赖项部分而不是将该库声明为实现或 api 配置,它需要是一个提供的配置,这意味着构建这个项目就像这个库存在一样,但不要在编译 DEX 文件的应用程序源文件中包含该 JAR

AAR 到 DEX

从AAR库中获取DEX文件有点困难,因为你必须处理资源文件。 AAR 包含 JAR 文件和资源。没有必要让这些资源可下载,因为大多数库只包含一些资源文件,这些文件并不大,而且大部分是布局 XML 文件或一些通用数字或布尔值或其他内容。所以正确的做法是将该资源与主要项目资源合并,并将该依赖项更改为提供的依赖项,并将 JAR 文件转换为 DEX 文件。但是那个 JAR 文件有问题。它不是普通的 JAR 文件。在构建期间,AAPT 不会为该库生成 R java 文件,因为该库是提供的依赖项,并且该 JAR 文件中的 R 文件用法将在 运行 时崩溃。相反,应用程序 R java 文件将包含资源 ID,包括库资源。所以这个问题的解决方案是手动创建一个 R.java 文件,它将所有资源 ID 委托给具有应用程序包名称的 R 文件,并编译该 R 文件并将其放入 jar 文件中,这可以用 jar - ufv 选项。现在假设发布了此库的更新。

解决方案:喷油器

正如我一开始所说,我已经为这个问题创建了一个解决方案。如果我告诉您这可以在构建时完成并且您甚至没有注意到某些资源正在从一个项目移动到另一个项目并且您不必记住命令行工具及其标志会怎样?解决方案是注射器。 Injector 是一个 Gradle 插件,可以自动为您完成上述所有操作。 首先,您需要将注入器添加到您的 Gradle 构建脚本 class 路径中。您的 gradle 构建脚本应如下所示

等等……