Android实现免安装插件(一)

在这个案例中,使用常规的startActivity启动一个未安装apk中的Activity,需要解决以下几个问题:
1、 ClassLoader如何找到需实例化Activity
2、 未在androidmanifest.xml中注册如何启动对应Activity
3、 Activity生命周期怎么实现
4、 资源如何加载


1、 ClassLoader如何找到需实例化Activity

ActivityThread中的performLaunchActivity中实例化Activity:

java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);

这里看到ClassLoader相信大家已经明白Activity实例怎么来的了,没错,就是通过反射。这里重点看下这个ClassLoader怎么来的,这样也许我们可以通过它干点事情。这个ClassLoader实际是在LoadedApk中创建的,如下:

mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
                    mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                    libraryPermittedPath, mBaseClassLoader,
                    mApplicationInfo.classLoaderName);

一直跟踪代码,最终找到ClassLoader创建的地方:

public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, String classloaderName) {
        if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent);
        } else if (isDelegateLastClassLoaderName(classloaderName)) {
            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
        }

        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
    }

classloaderName为null,所以创建的是PathClassLoader。
这里就要说到PathClassLoader和DexClassLoader及它们的父类BaseDexClassLoader了。PathClassLoader只能加载本地文件系统也就是自身应用的dex文件,如果需要加载其他的dex文件则需要使用DexClassLoader帮忙了,从两个类的构造函数即可以看出它们的差异。
在父类BaseDexClassLoader中有创建一个DexPathList对象pathlist,而自身应用的dex文件及其他的dex文件统一由pathlist的私有成员变量dexElements维护。
父类BaseDexClassLoader:

public class BaseDexClassLoader extends ClassLoader {
        private final DexPathList pathList;

        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
            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的findClass:

public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

到这里,我们是否可以将createClassLoader中PathClassLoader做一些修改,让它可以findClass到其他外部的dex文件中的类,而不抛出ClassNotFoundException呢?答案是肯定的。我们只需要创建一个DexClassLoader加载dex文件,并将它的dexElements拼接到PathClassLoader的dexElements中即可。
如下为拼接两个ClassLoader的Element数组:

private static Object getField(Object srcObject, String className, String fieldName) {
        Object object = null;
        try {
            Field field = getField(className, fieldName);
            object = field.get(srcObject);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return object;
    }

    private static Field getField(String className, String fieldName) {
        Field field = null;
        try {
            Class clazz = Class.forName(className);
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return field;
    }

    private static Object getPathList(Object srcObject) {
        return  getField(srcObject, "dalvik.system.BaseDexClassLoader", FIELD_PATH_LIST);
    }

    private static Object getDexElements(Object srcObject) {
        Class clazz = srcObject.getClass();
        Object object = null;
        try {
            Field field = clazz.getDeclaredField(FIELD_DEX_ELEMENTS);
            field.setAccessible(true);
            object = field.get(srcObject);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return object;
    }

    private static void mergeDexElements(ClassLoader src, ClassLoader des) {
        Object srcPathList = getPathList(src);
        Object desPathList = getPathList(des);
        Object srcElements = getDexElements(srcPathList);
        Object desElements = getDexElements(desPathList);
        int srcLen = Array.getLength(srcElements);
        int desLen = Array.getLength(desElements);
        Log.i(TAG, "desLen = " + desLen);
        Class compoment = srcElements.getClass().getComponentType();
        Object value = Array.newInstance(compoment, srcLen + desLen);
        for (int i = 0; i < srcLen + desLen; i++) {
            if (i < srcLen) {
                Array.set(value, i, Array.get(srcElements, i));
            } else {
                Array.set(value, i, Array.get(desElements,i - srcLen));
            }
        }
        try {
            Field field = srcPathList.getClass().getDeclaredField(FIELD_DEX_ELEMENTS);
            field.setAccessible(true);
            field.set(srcPathList, value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

这里需要注意的一点是需读取外部存储的权限,尤其是android N以上版本需动态申请权限才可以进行以上操作,如果未进行权限申请,ClassNotFoundException会一直困扰你。这个案例在启动界面中申请权限,成功后跳转到工作界面,确保权限申请成功后可以正常完成拼接逻辑。

猜你喜欢

转载自blog.csdn.net/rhythmjay/article/details/80705110