9

我已成功通过以下方式从 dex 文件中动态加载类

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文件动态加载类,如android dev页面所示(DexClassLoader:一个类加载器,从包含classes.dex条目的.jar和.apk文件加载类。可以使用执行未作为应用程序的一部分安装的代码。)

我在 Android 工作室创建了一个库模块(“testlibrary”),在库模块中创建了 Test.java(我想在调用者应用程序中动态加载的内容),并通过 Gradle Project -> Excute Gradle Task 创建了一个 aar 文件

如何通过 dexclassloader 在以这种通用方式创建的 aar 文件中动态加载类?我已通过提供程序将 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
4

2 回答 2

6

您无法在运行时加载 aar 文件,因为 aar 文件包含资源和 classes.jar 文件,并且不包含 dex 文件。
但是
您可以使用注入器gradle 插件从您的 aar 获取 dex 并将所有 aar 资源合并到您的项目中,然后您可以使用injector-android lib 在运行时加载该 dex 文件。查看注入示例项目

于 2018-04-29T11:12:55.990 回答
5

jar和dex文件的区别

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

[AAR to DEX] Android 应用运行时加载和运行代码

JAR 和 AAR 的区别

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

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

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

JAR 转 DEX

Android不支持加载JAR文件,所以必须有办法将JAR文件编译成DEX文件。为此,D8 工具位于 android_sdk/build-tools/version/ 中。要将 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 buildscript 类路径中。你的 gradle buildscript 应该是这样的

等等 ......

于 2019-12-11T02:18:34.440 回答