Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to load class dynamically from aar file with DexClassLoader

I have succeeded in dynamically loading classes from a dex file in the following way

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();
}

But what I want to do is load the class dynamically from the aar file, as shown in the android dev page(DexClassLoader : A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.)

I created a library module("testlibrary") in the Android studio, created Test.java(what I want to load dynimically at caller app) in the library module, and created an aar file through the Gradle Project -> Excute Gradle Task

How can I dynamically load a class via the dexclassloader in an aar file created in this general way? I have moved aar file via provider to CallerApp from CalledApp

Or is the process of creating an aar file wrong? During runtime, an error message appears

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
like image 793
woosuk Avatar asked Feb 04 '23 01:02

woosuk


2 Answers

the diffent about the jar and dex file

the below article detail descript about the diffrend and you can read it ,then you can get the result

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

The difference between JAR and AAR

JAR is just a Java library containing java class files and nothing more. AAR is a format for android libraries which contains a JAR file, android resource file (like layout XML, attribute XML, etc..) and an android manifest file. During the build, an R.java file is generated for each android library and for the main project of course, and all java files are compiled to a one or multiple DEX files (DEX is a Dalvik executable format which can be loaded by android runtime (ART) ). So in an APK, there are only DEX files(not JAR or AAR file), and resources and manifest. Android R.java file is an auto-generated file by AAPT (Android Asset Packaging Tool) that contains resource IDs for all the resources of res/ directory.

Why do I need to load some code at runtime?

There are many reasons to do that. Maybe your dependency libraries are too big and you want you APK to have a small size or maybe that libraries are requested for some feature which is not supported for all devices or it is not required at first launch and you have your own logic for differentiation if device supports that feature or not, or if you need to show user the feature or not. Why ship the APK with that feature code? If you are reading this I think you already have your own reasons :)

JAR To DEX

Android does not support loading JAR file, so there must be a way to compile the JAR file to DEX file. For that, there is the D8 tool which is located in android_sdk/build-tools/version/. To convert JAR to DEX you can run this command from command line

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

The DEX file is generated and there is no need to build the android project with that JAR library, so in gradle dependancies section instead of declaring that library as implementation or an api configuration, it needs to be a provided configuration which means that build this project as if this library exists but DO NOT include that JAR in application source files from which the DEX files are compiled

AAR to DEX

To get DEX file from AAR library is a little bit difficult because you must deal with resource files. AAR contains a JAR file and resources. There isn’t a need to make that resources downloadable because most libraries only contain a few resource files which aren’t large and are mostly layout XML files or some general numbers or booleans or something else. So the right thing to do is to merge that resources with the main project resources and change that dependency to be a provided dependency and convert the JAR file to DEX file. But there is a problem with that JAR file. it is not an ordinary JAR file. During the build time, AAPT will not generate an R java file for that library because the library is a provided dependency and the R file usages in that JAR file will crash at runtime. Instead of that, the application R java file will contain the resources ids including the library resources. So the solution to this problem is to manulay create a R.java file which will delegate all resources ids to the R file with the app package name and compile that R file and put it in jar file which can be done with jar -ufv option. And now imagine that an update for this library is released.

Solution: Injector

As I said at the beginning I have created a solution to this problem. What if I told you that this could be done at build time and you even can’t notice that some resource is being moved from one project to another and you don’t have to remember the command line tools with their flags. The solution is Injector. Injector is a Gradle plugin that does all the above explained for you automatically. First of all, you need to add injector to your Gradle buildscript classpath. Your gradle buildscript should look like this

and so on ......

like image 125
lixiaodaoaaa Avatar answered Feb 06 '23 15:02

lixiaodaoaaa


You can not load aar file at runtime because aar file contains resources and classes.jar file and does not conatain a dex file.
But
you can use 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 project

like image 31
artyomd Avatar answered Feb 06 '23 16:02

artyomd