如何使用 DexClassLoader 动态加载 AAR 文件中的类

10

我已成功地以以下方式从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开发页面所示(DexClassLoader:一个类加载器,用于从包含classes.dex条目的.jar和.apk文件中加载类。 这可以用于执行未作为应用程序一部分安装的代码。)
我在Android Studio中创建了一个库模块(“testlibrary”),在库模块中创建了Test.java(我想在调用程序中动态加载该类),并通过Gradle Project -> Excute Gradle Task创建了一个aar文件。
我应该如何通过dexclassloader在以这种通用方式创建的aar文件中动态加载一个类? 我已经通过提供程序将aar文件从被调用应用程序移动到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

请查看我的 SO 答案,链接在这里这里。同时,请关注 bytebuddy ,它可以轻松解决DexClassLoader的问题。但是需要注意的是,整个主题有点复杂。其中包含了教程和源代码链接,如果您想学习低级别类导入的入门指南,也可以参考一下。祝愉快。 - Jon Goodwin
值得一提的是,动态加载类最明显的用例是执行从互联网下载的代码(例如来自应用程序包的动态模块),这种做法违反了Google开发者政策,这样做的应用可能会被从Google Play中移除。 - Cililing
2个回答

5

关于jar和dex文件的区别

以下文章详细描述了JAR和AAR之间的区别,您可以阅读它,然后得出结论。 [AAR to DEX] Loading and Running Code at Runtime in Android Application JAR只是一个包含Java类文件而不包含其他内容的Java库。 AAR是一种用于Android库的格式,其中包含一个JAR文件、Android资源文件(如布局XML、属性XML等)和一个Android清单文件。 在构建过程中,为每个Android库和主项目生成R.java文件,并将所有Java文件编译成一个或多个DEX文件(DEX是Dalvik可执行格式,可以由Android运行时(ART)加载)。因此,在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配置。这意味着在构建此项目时,应该将其视为存在该库,但不要将该JAR包含在用于编译DEX文件的应用程序源文件中。
从AAR库获取DEX文件有些困难,因为您必须处理资源文件。AAR包含一个JAR文件和资源文件。没有必要使这些资源可下载,因为大多数库只包含少量资源文件,这些文件不是很大,并且主要是布局XML文件、一些常规数字或布尔值等等。因此,正确的做法是将这些资源与主项目资源合并,并将该依赖项更改为提供的依赖项,并将JAR文件转换为DEX文件。但是,该JAR文件存在问题。它不是普通的JAR文件。在构建时,AAPT不会为该库生成R java文件,因为该库是提供的依赖项,而在该JAR文件中使用的R文件用法将在运行时崩溃。相反,应用程序R java文件将包含资源ID,包括库资源。因此,解决此问题的方法是手动创建一个R.java文件,该文件将所有资源ID委托给具有应用程序包名称的R文件,并编译该R文件并将其放入可以使用jar -ufv选项完成的jar文件中。现在想象一下,该库发布了一个更新。
解决方案:注射器。
作为我在开头所说的,我已经找到了解决这个问题的方法。如果我告诉你,这可以在构建时完成,你甚至不会注意到某些资源正在从一个项目移动到另一个项目,也不需要记住带有其标志的命令行工具。解决方案是Injector。Injector是一个Gradle插件,自动为您执行所有上述操作。 首先,您需要将injector添加到Gradle buildscript类路径中。您的gradle buildscript应如下所示:

5
无法在运行时加载aar文件,因为aar文件包含资源和类库(classes.jar),但不包含dex文件。
但是
您可以使用injector gradle插件从aar中获取dex,并将所有aar资源合并到您的项目中,之后您可以使用injector-android库在运行时加载这些dex文件。请查看inject例子工程

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接