插件化开发 -- 加载APK

在App项目中需要加载sd里面的一个APK,显而易见就是调用插件方法。

需要先了解Android的Dalvik/ART虚拟机,和java的JVM虚拟机几乎一样的标准,了解类加载的流程,通过反射Hook启动插件类。把插件的dex加到主dex里面中,从而调用。

类加载时序图

关注Android的类加载时序图,其中 DexPathLisElement 以及 DexFile

类加载的时序图

核心流程通过:

  1. BaseDexClassLoader --> findClass
  2. DexPathList -for- findClass || findClass
  3. DexFile --> loadClassBinaryName

核心源码

BaseDexClassLoader.源码


    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        List suppressedExceptions = new ArrayList();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

DexPathList.源码

/**
     * Finds the named class in one of the dex files pointed at by
     * this instance. This will find the one in the earliest listed
     * path element. If the class is found but has not yet been
     * defined, then this method will define it in the defining
     * context that this instance was constructed with.
     *
     * @param name of class to find
     * @param suppressed exceptions encountered whilst finding the class
     * @return the named class or {@code null} if the class is not
     * found in any of the dex files
     */
    public Class findClass(String name, List suppressed) {
        for (Element element : dexElements) {
            Class clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }



    public Class findClass(String name, ClassLoader definingContext,List suppressed) {
            return dexFile != null ?
                    dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
    }

步骤

目标:宿主dexElements = 宿主dexElements + 插件dexElements

  1. 获取宿主dexElements
  2. 获取插件dexElements
  3. 合并两个dexElements
  4. 将新的dexElements 赋值到 宿主dexElements
package com.example.myapplication;

import android.content.Context;

import java.lang.reflect.Array;
import java.lang.reflect.Field;

import dalvik.system.DexClassLoader;

public class LoadUtil {
    
    //存放apk的路径
    private final static String apkPath = "/sdcard/dxpak-debug.apk";

    public static void loadClass(Context context) {
        /**
         * 宿主dexElements = 宿主dexElements + 插件dexElements
         *
         * 获取的是宿主的类加载器  --- 反射 dexElements  宿主
         *
         * 获取的是插件的类加载器  --- 反射 dexElements  插件
         */
        try {
            Class clazz = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = clazz.getDeclaredField("pathList");
            pathListField.setAccessible(true);

            Class dexPathListClass = Class.forName("dalvik.system.DexPathList");
            Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);

            // 1.宿主的 类加载器
            ClassLoader pathClassLoader = context.getClassLoader();
            // DexPathList类的对象
            Object hostPathList = pathListField.get(pathClassLoader);
            // 宿主的 dexElements
            Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);


            // 2.插件的 类加载器
            ClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(),
                    null, pathClassLoader);
            // DexPathList类的对象
            Object pluginPathList = pathListField.get(dexClassLoader);
            // 插件的 dexElements
            Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);


            // 3.宿主dexElements = 宿主dexElements + 插件dexElements
            // 创建一个新数组
            Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
                    hostDexElements.length + pluginDexElements.length);

            System.arraycopy(hostDexElements, 0, newDexElements,
                    0, hostDexElements.length);
            System.arraycopy(pluginDexElements, 0, newDexElements,
                    hostDexElements.length, pluginDexElements.length);

            // 4.赋值
            dexElementsField.set(hostPathList, newDexElements);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

加载调用:

public void onButtonApk(View view) {
    try {
        Class clazz = Class.forName("com.example.dxpak.Test");
        Method print = clazz.getMethod("loginStar");
        print.invoke(null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

你可能感兴趣的:(插件化开发 -- 加载APK)