• Android插件化(二):使用DexClassLoader动态载入assets中的apk


    Android插件化(二):使用DexClassLoader动态载入assets中的apk

    简单介绍

    上一篇博客讲到。我们能够使用MultiDex.java载入离线的apk文件。须要注意的是,apk中的类是载入到当前的PathClassLoader其中的,假设apk文件过多。可能会出现ANR的情况。那么。我们能不能使用DexClassLoader载入apk呢?当然是能够的!

    首先看一下Doc文档.

    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.

    也就是说,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了。

    源代码

    BundleClassLoaderManager

    该类主要是负责管理这些DexClassLoader的,首先,我们定义了一个叫做BundleDexClassLoader的类,它继承自DexClassLoader,用于载入离线的apk文件。每个apk文件相应一个BundleDexClassLoader,而BundleClassLoaderManager则保存了一个List,在载入的时候。用于查找类。

    详细代码例如以下:

    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 2015年12月11日 下午7:30:59
     *          mochuan.zhb Exp $
     * @Description
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public class BundleClassLoaderManager {
    
        public static List<BundleDexClassLoader> bundleDexClassLoaderList = new ArrayList<BundleDexClassLoader>();
    
        /**
         * 载入Assets里的apk文件
         * @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);
            }
        }
    
        /**
         * 查找类
         * 
         * @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"); } }

    注意点:

    • 1.install方法
      install方法主要是将assets中的apk所有复制到私有文件夹,然后再遍历私有文件夹。使用BundleDexClassLoader载入apk文件。然后将这些BundleDexClassLoader保存到数组中。

    • 2.loadClass方法
      该方法先从当前的ClassLoader中查找须要的类,假设找不到,在从List中遍历查找。

    DEMO执行

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

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

    改进方案

    正如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;
                }
            };
    

    总结

    上一篇博客和这一篇博客将的都是类的载入。假设所须要载入的类都是工具类,不须要载入资源等,那么上面的方案都没啥问题。可是假设载入的类是Fragment或者Activity等UI,须要引用资源文件,这又改怎样处理呢?

    下一篇博文:Android资源的离线载入。

    參考

    1.BaseDexClassLoader源代码

  • 相关阅读:
    Java之static作用的深度总结
    关于UiAutomator无法识别的元素
    pom.xml详解
    maven的依赖范围scope
    maven-surefire-plugin插件
    remote origin already exists解决办法
    resin启动报错:guava-15.0.jar!/META-INF/beans.xml:5: <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"> is an unexpected top-level tag. 异常
    Mysql存储过程
    ical4j 实现ICS文件的生成和解析
    设计模式之观察者模式
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7016180.html
Copyright © 2020-2023  润新知