Android plugin (2): Use DexClassLoader to dynamically load the apk in assets
As mentioned in the previous blog, we can use MultiDex. java to load offline apk files. Note that the class in the apk is loaded to the current PathClassLoader. If there are too many apk files, ANR may occur. So, can we use DexClassLoader to load the apk? Of course! First, take a look at the Doc documentation.
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.
That is to say, DexClassLoader can load a compressed package containing the classes. dex file, either jar or apk. So what should I pay attention to when loading an offline apk file?
1. DexClassLoader construction method:
DexClassLoader (String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
2. Private directory
This class loader requires an application-private, writable directory to cache optimized classes.
After learning about the above two points, we can dynamically load the apk in assets based on the parameters required by DexClassLoader.
Source code BundleClassLoaderManager
This class is mainly responsible for managing these DexClassLoader. First, we define a class called BundleDexClassLoader, which inherits from DexClassLoader and is used to load offline apk files. Each apk file corresponds to a BundleDexClassLoader, while BundleClassLoaderManager stores a List for searching classes during loading. The Code is as follows:
Package net. mobctrl. hostapk; import java. io. file; import java. io. filenameFilter; import java. util. arrayList; import java. util. list; import android. annotation. targetApi; import android. content. context; import android. OS. build;/*** @ Author Zheng Haibo * @ PersonalWebsite http://www.mobctrl.net * @ version $ Id: BundleClassLoaderManager. java, v 0.1 December 11, 2015 7:30:59 * mochuan. zhb Exp $ * @ Description */@ TargetApi (Build. VERSION_CODES.ICE_CREAM_SANDWICH) public class BundleClassLoaderManager {public static List
BundleDexClassLoaderList = new ArrayList
();/*** Load the apk file in Assets * @ param context */public static void install (Context context) {AssetsManager. copyAllAssetsApk (context); // obtain the dex File list File dexDir = context. getDir (AssetsManager. APK_DIR, Context. MODE_PRIVATE); File [] szFiles = dexDir. listFiles (new FilenameFilter () {@ Override public boolean accept (File dir, String filename) {return filename. endsWith (AssetsManager. FILE_FILTER) ;}}); for (File f: szFiles) {System. out. println ("debug: load file:" + f. getName (); BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader (f. getAbsolutePath (), dexDir. getAbsolutePath (), null, context. getClassLoader (); bundleDexClassLoaderList. add (bundleDexClassLoader);}/*** Search Class ** @ param className * @ return * @ throws ClassNotFoundException */public static Class
LoadClass (Context context, String className) throws ClassNotFoundException {try {Class
Clazz = context. getClassLoader (). loadClass (className); if (clazz! = Null) {System. out. println ("debug: class find in main classLoader"); return clazz ;}} catch (Exception e) {e. printStackTrace ();} for (BundleDexClassLoader bundleDexClassLoader: bundleDexClassLoaderList) {try {Class
Clazz = bundleDexClassLoader. loadClass (className); if (clazz! = Null) {System. out. println ("debug: class find in bundle classLoader"); return clazz ;}} catch (Exception e) {e. printStackTrace () ;}} throw new ClassCastException (className + "not found exception ");}}
Note:
1. install Method
The install method is to copy all the apks in assets to the private directory, traverse the private directory, use BundleDexClassLoader to load the apk file, and then save these BundleDexClassLoader to the array.
2. loadClass Method
This method first finds the required class from the current ClassLoader. If it cannot be found, it traverses the search from the List.
Run DEMO
In MainActivity, we can call the method in the apk class in the following way:
Private void loadApk () {try {Class
Clazz = BundleClassLoaderManager. loadClass (getApplicationContext (), "net.mobctrl.normal.apk. Utils"); Constructor
Constructor = clazz. getConstructor (); Object bundleUtils = constructor. newInstance (); Method printSumMethod = clazz. getMethod ("printSum", Context. class, int. class, int. class, String. class); printSumMethod. setAccessible (true); Integer sum = (Integer) printSumMethod. invoke (bundleUtils, getApplicationContext (), 10, 20, "calculation result"); System. out. println ("debug: sum =" + sum);} catch (ClassNotFoundException e) {e. printStackTrace ();} catch (SecurityException e) {e. printStackTrace ();} catch (NoSuchMethodException e) {e. printStackTrace ();} catch (IllegalArgumentException e) {e. printStackTrace ();} catch (InstantiationException e) {e. printStackTrace ();} catch (IllegalAccessException e) {e. printStackTrace ();} catch (InvocationTargetException e) {e. printStackTrace ();}}
Unlike MultiDex, we load classes through BundleClassLoaderManager, rather than the current ClassLoader.
Improvement Plan
Just like the loadClass method in BundleClassLoaderManager, we can create a ClassLoader object by rewriting the findClass method of the current ClassLoader. Then, in the findClass method of Override, We can first find the class from the current ClassLoader, then, we can traverse and search from BundleDexClassLoader, so that we can call the classes in Bundle in the Host project or the classes in the Host.
mClassLoader = new ClassLoader(super.getClassLoader()) { @Override protected Class
findClass(String className) throws ClassNotFoundException { Class clazz = BundleClassLoaderManager.loadClass(context,className); if (clazz == null) { throw new ClassNotFoundException(className); } return clazz; } };
Summary
Both the previous blog and this blog will load classes. If all the classes to be loaded are tool classes and resources do not need to be loaded, the above solution will be free of problems. However, if the loaded class is a Fragment or Activity UI and the resource file needs to be referenced, how can this problem be solved?