(4.1.53.2)示例:基于DexClassLoader的动态加载assets中的apk

DexClassLoader可以加载一个含有classes.dex文件的压缩包,既可以是jar也可以是apk。那么加载一个离线的apk文件需要注意哪些呢?

  1. DexClassLoader的构造方法:
    DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)

  2. 私有目录
    This class loader requires an application-private, writable directory to cache optimized classes.

了解到上述两点,我们就可以根据DexClassLoader所需要的参数,动态加载assets中的apk了

一、实现

1.1 BundleDexClassLoader

我们定义了一个叫做BundleDexClassLoader的类,它继承自DexClassLoader,用于加载离线的apk文件。

每一个apk文件对应一个BundleDexClassLoader,

public class BundleDexClassLoader extends DexClassLoader {

    public BundleDexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, optimizedDirectory, libraryPath, parent);
    }

}

1.2 BundleClassLoaderManager

该类主要是负责管理自定义的DexClassLoader的,保存了一个List,在加载的时候,用于查找类

public class BundleClassLoaderManager {

    public static List<BundleDexClassLoader> bundleDexClassLoaderList = new ArrayList<BundleDexClassLoader>();

    /**
     * 寻找Assets里的apk文件,并创建对应的classloader
     * @param context
     */
    public static void install(Context context) {
        AssetsManager.copyAllAssetsApk(context);
        // 获取dex文件列表
        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);
        }
    }

    /**
     * 查找class
     * 首先判断当前loader
     * @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");
    }
}

需要注意的是,由于我们自定的一堆ClassLoader 之间并不遵循双亲委托模型(创建时,都是设PathClassLoader为父loader,相互之间无依赖),因此在查找时需要遍历

1.3 插件加载宿主的class

同样适用于,插件loader自动变脸其他插件loader

正如BundleClassLoaderManager中的loadClass方法

  • 其实我们创建一个ClassLoader对象
  • 通过重写当前ClassLoader的findClass方法即可,然后在Override的findClass方法中
    • 首先从当前ClassLoader中查找类,然后再从BundleDexClassLoader中遍历查找

这样既可以在Host项目中调用Bundle中的类,也能够在Bundle中调用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;
            }
        };

二、Demo

AndroidPluginFramework/第二课-使用DexClassLoader加载普通apk/

与MultiDex不同时,我们是通过BundleClassLoaderManager来加载类的,而不是当前的ClassLoader

MainActivity中,我们可以通过如下方式,调用apk类中的方法:

     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, "计算结果");
            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();
        }
    }

猜你喜欢

转载自blog.csdn.net/fei20121106/article/details/80748655