【Qigsaw系列03】Qigsaw如何加载插件

目录:

  • 1. 插件化加载简介
  • 2. Qigsaw 加载 class、libs
  • 3. Qigsaw 加载资源

 

1. 插件化加载简介

插件化技术最初源于免安装运行 APK 的想法,这个免安装的 APK 可以理解为插件。支持插件化的 APP 可以在运行时加载和运行插件,这样便可以将 APP 中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现 APP 功能的动态扩展。想要实现插件化,主要是解决下面三个问题:

  • 插件中代码的加载和与主工程的互相调用。
  • 插件中资源的加载和与主工程的互相访问。
  • 四大组件生命周期的管理。

(1) 代码加载

通过给插件 APK 生成相应的 DexClassLoader 便可以访问其中的类,这边又有两种处理方式,有单 DexClassLoader 和多DexClassLoader 两种结构。Android 中常用的有两种类加载器,DexClassLoader 和 PathClassLoader,它们都继承于 BaseDexClassLoader。区别在于调用父类构造器时,DexClassLoader 多传了一个 optimizedDirectory 参数,这个目录必须是内部存储路径,用来缓存系统创建的 Dex 文件。而 PathClassLoader 该参数为 null,只能加载内部存储目录的 Dex 文件。所以我们可以用 DexClassLoader 去加载外部的 APK.

多 DexClassLoader:
对于每个插件都会生成一个 DexClassLoader,当加载该插件中的类时需要通过对应 DexClassLoader 加载。这样不同插件的类是隔离的,当不同插件引用了同一个类库的不同版本时,不会出问题。RePlugin 采用的是该方案。

单 DexClassLoader:
将插件的 DexClassLoader 中的 pathList 合并到主工程的 DexClassLoader 中。这样做的好处时,可以在不同的插件以及主工程间直接互相调用类和方法,并且可以将不同插件的公共模块抽出来放在一个 common 插件中直接供其他插件使用。Small 采用的是这种方式。

插件调用主工程:
在构造插件的 ClassLoader 时会传入主工程的 ClassLoader 作为父加载器,所以插件是可以直接可以通过类名引用主工程的类。

主工程调用插件:

  • 若使用多 ClassLoader 机制,主工程引用插件中类需要先通过插件的 ClassLoader 加载该类再通过反射调用其方法。插件化框架一般会通过统一的入口去管理对各个插件中类的访问,并且做一定的限制。
  • 若使用单 ClassLoader 机制,主工程则可以直接通过类名去访问插件中的类。该方式有个弊病,若两个不同的插件工程引用了一个库的不同版本,则程序可能会出错,所以要通过一些规范去避免该情况发生。

Qigsaw 支持单、多 classloader 加载。

 

(2) 资源加载

资源路径的处理:

  • 合并式:addAssetPath 时加入所有插件和主工程的路径。
  • 独立式:各个插件只添加自己 APK 路径。

合并式由于 AssetManager 中加入了所有插件和主工程的路径,因此生成的 Resource 可以同时访问插件和主工程的资源。但是由于主工程和各个插件都是独立编译的,生成的资源 id 会存在相同的情况,在访问时会产生资源冲突。

独立式时,各个插件的资源是互相隔离的,不过如果想要实现资源的共享,必须拿到对应的 Resource 对象。

Qigsaw 采用合并式资源加载方式。

 

(3) 四大组件

Android 开发中有一些特殊的类,是由系统创建的,并且由系统管理生命周期。如常用的四大组件,Activity,Service,BroadcastReceiver 和 ContentProvider。 仅仅构造出这些类的实例是没用的,还需要管理组件的生命周期。其中以 Activity 最为复杂,不同框架采用的方法也不尽相同。下面以 Activity 为例详细介绍插件化如何支持组件生命周期的管理,大致分为两种方式:

  • ProxyActivity 代理。
  • 预埋 StubActivity,hook 系统启动 Activity 的过程。

由于 Qigsaw 是基于 AAB 开发的,AAB 会提前合并 base APK、dynamic feature APK 中所有的四大组件,所以 Qigsaw 的插件加载无需考虑四大组件的支持。

 

 

2. Qigsaw 加载 class、libs

SplitDelegateClassloader.java:

final class SplitDelegateClassloader extends PathClassLoader {

    private static final String TAG = "SplitDelegateClassloader";

    private static BaseDexClassLoader originClassLoader;

    private int splitLoadMode;

    SplitDelegateClassloader(ClassLoader parent) {
        super("", parent);
        originClassLoader = (BaseDexClassLoader) parent;
    }

    private static void reflectPackageInfoClassloader(Context baseContext, ClassLoader reflectClassLoader) throws Exception {
        Object packageInfo = HiddenApiReflection.findField(baseContext, "mPackageInfo").get(baseContext);
        if (packageInfo != null) {
            HiddenApiReflection.findField(packageInfo, "mClassLoader").set(packageInfo, reflectClassLoader);
        }
    }

    static void inject(ClassLoader originalClassloader, Context baseContext) throws Exception {
        SplitDelegateClassloader classloader = new SplitDelegateClassloader(originalClassloader);
        reflectPackageInfoClassloader(baseContext, classloader);
    }

    void setSplitLoadMode(int splitLoadMode) {
        this.splitLoadMode = splitLoadMode;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            return originClassLoader.loadClass(name);
        } catch (ClassNotFoundException error) {
            if (SplitLoadManagerService.hasInstance()) {
                if (splitLoadMode == SplitLoad.MULTIPLE_CLASSLOADER) {
                    Class<?> result = onClassNotFound(name);
                    if (result != null) {
                        return result;
                    }
                } else if (splitLoadMode == SplitLoad.SINGLE_CLASSLOADER) {
                    Class<?> result = onClassNotFound2(name);
                    if (result != null) {
                        return result;
                    }
                }
            }
            throw error;
        }
    }

    private Class<?> onClassNotFound(String name) {
        Class<?> ret = findClassInSplits(name);
        if (ret != null) {
            return ret;
        }
        Class<?> fakeComponent = AABExtension.getInstance().getFakeComponent(name);
        if (fakeComponent != null) {
            SplitLoadManagerService.getInstance().loadInstalledSplits();
            ret = findClassInSplits(name);
            if (ret != null) {
                return ret;
            }
            return fakeComponent;
        }
        return null;
    }

    private Class<?> onClassNotFound2(String name) {
        Class<?> fakeComponent = AABExtension.getInstance().getFakeComponent(name);
        if (fakeComponent != null) {
            SplitLoadManagerService.getInstance().loadInstalledSplits();
            try {
                return originClassLoader.loadClass(name);
            } catch (ClassNotFoundException e) {
                return fakeComponent;
            }
        }
        return null;
    }

    private Class<?> findClassInSplits(String name) {
        Set<SplitDexClassLoader> splitDexClassLoaders = SplitApplicationLoaders.getInstance().getClassLoaders();
        for (SplitDexClassLoader classLoader : splitDexClassLoaders) {
            try {
                Class<?> clazz = classLoader.loadClassItself(name);
                return clazz;
            } catch (ClassNotFoundException e) {
            }
        }
        return null;
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        return originClassLoader.getResources(name);
    }

    @Override
    public URL getResource(String name) {
        return originClassLoader.getResource(name);
    }

    //...

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return findClass(name);
    }

    @Override
    public String findLibrary(String name) {
        String libName = originClassLoader.findLibrary(name);
        if (libName == null) {
            Set<SplitDexClassLoader> splitDexClassLoaders = SplitApplicationLoaders.getInstance().getClassLoaders();
            for (SplitDexClassLoader classLoader : splitDexClassLoaders) {
                libName = classLoader.findLibraryItself(name);
                if (libName != null) {
                    break;
                }
            }
        }
        return libName;
    }
}

findClass 最终找不到时,如果 class 类型是四大组件,则会返回 FakeClass.

(1) 单 classloader 加载 libs

final class SplitCompatLibraryLoader {

    private static final String TAG = "SplitCompatLibraryLoader";

    static void load(ClassLoader classLoader, File folder)
            throws Throwable {

        // android o sdk_int 26
        // for android o preview sdk_int 25
        if ((Build.VERSION.SDK_INT == 25 && Build.VERSION.PREVIEW_SDK_INT != 0)
                || Build.VERSION.SDK_INT > 25) {
            try {
                V25.load(classLoader, folder);
            } catch (Throwable throwable) {
                V23.load(classLoader, folder);
            }
        } else if (Build.VERSION.SDK_INT >= 23) {
            try {
                V23.load(classLoader, folder);
            } catch (Throwable throwable) {
                V14.load(classLoader, folder);
            }
        } else if (Build.VERSION.SDK_INT >= 14) {
            V14.load(classLoader, folder);
        } else {
            throw new UnsupportedOperationException("don\'t support under SDK version 14!");
        }
    }

    private static final class V14 {
        private static void load(ClassLoader classLoader, File folder) throws Throwable {
            final Field pathListField = HiddenApiReflection.findField(classLoader, "pathList");
            final Object dexPathList = pathListField.get(classLoader);

            final Field nativeLibDirField = HiddenApiReflection.findField(dexPathList, "nativeLibraryDirectories");
            final File[] origNativeLibDirs = (File[]) nativeLibDirField.get(dexPathList);

            final List<File> newNativeLibDirList = new ArrayList<>(origNativeLibDirs.length + 1);
            newNativeLibDirList.add(folder);
            for (File origNativeLibDir : origNativeLibDirs) {
                if (!folder.equals(origNativeLibDir)) {
                    newNativeLibDirList.add(origNativeLibDir);
                }
            }
            nativeLibDirField.set(dexPathList, newNativeLibDirList.toArray(new File[0]));
        }
    }

    private static final class V23 {
        private static void load(ClassLoader classLoader, File folder) throws Throwable {
            final Field pathListField = HiddenApiReflection.findField(classLoader, "pathList");
            final Object dexPathList = pathListField.get(classLoader);

            final Field nativeLibraryDirectories = HiddenApiReflection.findField(dexPathList, "nativeLibraryDirectories");

            List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
            if (origLibDirs == null) {
                origLibDirs = new ArrayList<>(2);
            }
            final Iterator<File> libDirIt = origLibDirs.iterator();
            while (libDirIt.hasNext()) {
                final File libDir = libDirIt.next();
                if (folder.equals(libDir)) {
                    libDirIt.remove();
                    break;
                }
            }
            origLibDirs.add(0, folder);

            final Field systemNativeLibraryDirectories = HiddenApiReflection.findField(dexPathList, "systemNativeLibraryDirectories");
            List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
            if (origSystemLibDirs == null) {
                origSystemLibDirs = new ArrayList<>(2);
            }

            final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
            newLibDirs.addAll(origLibDirs);
            newLibDirs.addAll(origSystemLibDirs);

            final Method makeElements = HiddenApiReflection.findMethod(dexPathList,
                    "makePathElements", List.class, File.class, List.class);
            final ArrayList<IOException> suppressedExceptions = new ArrayList<>();

            final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs, null, suppressedExceptions);

            final Field nativeLibraryPathElements = HiddenApiReflection.findField(dexPathList, "nativeLibraryPathElements");
            nativeLibraryPathElements.set(dexPathList, elements);
        }
    }

    private static final class V25 {
        private static void load(ClassLoader classLoader, File folder) throws Throwable {
            final Field pathListField = HiddenApiReflection.findField(classLoader, "pathList");
            final Object dexPathList = pathListField.get(classLoader);

            final Field nativeLibraryDirectories = HiddenApiReflection.findField(dexPathList, "nativeLibraryDirectories");

            List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
            if (origLibDirs == null) {
                origLibDirs = new ArrayList<>(2);
            }
            final Iterator<File> libDirIt = origLibDirs.iterator();
            while (libDirIt.hasNext()) {
                final File libDir = libDirIt.next();
                if (folder.equals(libDir)) {
                    libDirIt.remove();
                    break;
                }
            }
            origLibDirs.add(0, folder);
            final Field systemNativeLibraryDirectories = HiddenApiReflection.findField(dexPathList, "systemNativeLibraryDirectories");
            List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
            if (origSystemLibDirs == null) {
                origSystemLibDirs = new ArrayList<>(2);
            }

            final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
            newLibDirs.addAll(origLibDirs);
            newLibDirs.addAll(origSystemLibDirs);

            final Method makeElements = HiddenApiReflection.findMethod(dexPathList, "makePathElements", List.class);

            final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);

            final Field nativeLibraryPathElements = HiddenApiReflection.findField(dexPathList, "nativeLibraryPathElements");
            nativeLibraryPathElements.set(dexPathList, elements);
        }
    }
}

(2) 单 classloader 加载 dex

final class SplitCompatDexLoader {

    private static final String TAG = "SplitCompatDexLoader";

    private static int sPatchDexCount = 0;

    static void load(ClassLoader classLoader, File dexOptDir, List<File> files)
            throws Throwable {
        if (!files.isEmpty()) {
            if (Build.VERSION.SDK_INT >= 23) {
                V23.load(classLoader, files, dexOptDir);
            } else if (Build.VERSION.SDK_INT >= 19) {
                V19.load(classLoader, files, dexOptDir);
            } else if (Build.VERSION.SDK_INT >= 14) {
                V14.load(classLoader, files, dexOptDir);
            } else {
                throw new UnsupportedOperationException("don\'t support under SDK version 14!");
            }
            sPatchDexCount = files.size();
        }
    }

    static void unLoad(ClassLoader classLoader) throws Throwable {
        if (sPatchDexCount <= 0) {
            return;
        }
        if (Build.VERSION.SDK_INT >= 14) {
            Field pathListField = HiddenApiReflection.findField(classLoader, "pathList");
            Object dexPathList = pathListField.get(classLoader);
            HiddenApiReflection.reduceFieldArray(dexPathList, "dexElements", sPatchDexCount);
        } else {
            throw new RuntimeException("don\'t support under SDK version 14!");
        }
    }

    /**
     * Installer for platform versions 19.
     */
    private static final class V23 {

        private static void load(ClassLoader loader, List<File> additionalClassPathEntries,
                                 File optimizedDirectory)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
            Field pathListField = HiddenApiReflection.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList<>();
            HiddenApiReflection.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
                    new ArrayList<>(additionalClassPathEntries), optimizedDirectory,
                    suppressedExceptions));
            if (suppressedExceptions.size() > 0) {
                for (IOException e : suppressedExceptions) {
                    SplitLog.e(TAG, "Exception in makePathElement", e);
                    throw e;
                }

            }
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makePathElements}.
         */
        private static Object[] makePathElements(
                Object dexPathList, ArrayList<File> files, File optimizedDirectory,
                ArrayList<IOException> suppressedExceptions)
                throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

            Method makePathElements;
            try {
                makePathElements = HiddenApiReflection.findMethod(dexPathList, "makePathElements", List.class, File.class,
                        List.class);
            } catch (NoSuchMethodException e) {
                SplitLog.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
                try {
                    makePathElements = HiddenApiReflection.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
                } catch (NoSuchMethodException e1) {
                    SplitLog.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
                    try {
                        return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions);
                    } catch (NoSuchMethodException e2) {
                        throw e2;
                    }
                }
            }

            return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
        }
    }

    /**
     * Installer for platform versions 19.
     */
    private static final class V19 {

        private static void load(ClassLoader loader, List<File> additionalClassPathEntries,
                                 File optimizedDirectory)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
            Field pathListField = HiddenApiReflection.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList<>();
            HiddenApiReflection.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
                    new ArrayList<>(additionalClassPathEntries), optimizedDirectory,
                    suppressedExceptions));
            if (suppressedExceptions.size() > 0) {
                for (IOException e : suppressedExceptions) {
                    SplitLog.e(TAG, "Exception in makeDexElement", e);
                    throw e;
                }
            }
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makeDexElements}.
         */
        private static Object[] makeDexElements(
                Object dexPathList, ArrayList<File> files, File optimizedDirectory,
                ArrayList<IOException> suppressedExceptions)
                throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

            Method makeDexElements;
            try {
                makeDexElements = HiddenApiReflection.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
                        ArrayList.class);
            } catch (NoSuchMethodException e) {
                SplitLog.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
                try {
                    makeDexElements = HiddenApiReflection.findMethod(dexPathList, "makeDexElements", List.class, File.class, List.class);
                } catch (NoSuchMethodException e1) {
                    SplitLog.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
                    throw e1;
                }
            }

            return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
        }
    }

    /**
     * Installer for platform versions 14, 15, 16, 17 and 18.
     */
    private static final class V14 {

        private static void load(ClassLoader loader, List<File> additionalClassPathEntries,
                                 File optimizedDirectory)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
            Field pathListField = HiddenApiReflection.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            HiddenApiReflection.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
                    new ArrayList<>(additionalClassPathEntries), optimizedDirectory));
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makeDexElements}.
         */
        private static Object[] makeDexElements(
                Object dexPathList, ArrayList<File> files, File optimizedDirectory)
                throws IllegalAccessException, InvocationTargetException,
                NoSuchMethodException {
            Method makeDexElements =
                    HiddenApiReflection.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
            return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
        }
    }

}

(3) 多 classloader 加载

final class SplitDexClassLoader extends BaseDexClassLoader {

    private static final String TAG = "SplitDexClassLoader";

    private final String moduleName;

    private Set<SplitDexClassLoader> dependenciesLoaders;

    private SplitDexClassLoader(String moduleName,
                                List<String> dexPaths,
                                File optimizedDirectory,
                                String librarySearchPath,
                                List<String> dependencies,
                                ClassLoader parent) throws Throwable {
        super((dexPaths == null) ? "" : TextUtils.join(File.pathSeparator, dexPaths), optimizedDirectory, librarySearchPath, parent);
        this.moduleName = moduleName;
        this.dependenciesLoaders = SplitApplicationLoaders.getInstance().getClassLoaders(dependencies);
        SplitUnKnownFileTypeDexLoader.loadDex(this, dexPaths, optimizedDirectory);
    }
    //...
}

 

 

3. Qigsaw 加载资源

Qigsaw 采用合并式加载资源,对宿主和插件的资源 id 做了分区:

@RestrictTo(LIBRARY_GROUP)
public class SplitCompatResourcesLoader {

    private static final String TAG = "SplitCompatResourcesLoader";

    private static final Object sLock = new Object();

    public static void loadResources(Context context, Resources resources) throws Throwable {
        checkOrUpdateResources(context, resources);
    }

    static void loadResources(Context context, Resources preResources, String splitApkPath) throws Throwable {
        List<String> loadedResDirs = getLoadedResourcesDirs(preResources.getAssets());
        if (!loadedResDirs.contains(splitApkPath)) {
            installSplitResDirs(context, preResources, Collections.singletonList(splitApkPath));
        }
    }

    private static void checkOrUpdateResources(Context context, Resources resources) throws SplitCompatResourcesException {
        List<String> loadedResDirsInAsset;
        try {
            loadedResDirsInAsset = getLoadedResourcesDirs(resources.getAssets());
        } catch (Throwable e) {
            throw new SplitCompatResourcesException("Failed to get all loaded split resources for " + context.getClass().getName(), e);
        }
        Collection<String> loadedSplitPaths = getLoadedSplitPaths();
        if (loadedSplitPaths != null && !loadedSplitPaths.isEmpty()) {
            if (!loadedResDirsInAsset.containsAll(loadedSplitPaths)) {
                List<String> unloadedSplitPaths = new ArrayList<>();
                for (String splitPath : loadedSplitPaths) {
                    if (!loadedResDirsInAsset.contains(splitPath)) {
                        unloadedSplitPaths.add(splitPath);
                    }
                }
                try {
                    installSplitResDirs(context, resources, unloadedSplitPaths);
                } catch (Throwable e) {
                    throw new SplitCompatResourcesException("Failed to install resources " + unloadedSplitPaths.toString() + " for " + context.getClass().getName(), e);
                }
            }
        }
    }

    private static Collection<String> getLoadedSplitPaths() {
        SplitLoadManager loadManager = SplitLoadManagerService.getInstance();
        if (loadManager != null) {
            return loadManager.getLoadedSplitApkPaths();
        }
        return null;
    }

    private static void installSplitResDirs(final Context context, final Resources resources, final List<String> splitResPaths) throws Throwable {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            V21.installSplitResDirs(resources, splitResPaths);
        } else {
            if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
                V14.installSplitResDirs(context, resources, splitResPaths);
            } else {
                synchronized (sLock) {
                    new Handler(Looper.getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                            synchronized (sLock) {
                                try {
                                    V14.installSplitResDirs(context, resources, splitResPaths);
                                } catch (Throwable throwable) {
                                    throw new RuntimeException(throwable);
                                }
                                sLock.notify();
                            }
                        }
                    });
                    sLock.wait();
                }
            }
        }
    }

    private static List<String> getLoadedResourcesDirs(AssetManager asset) throws NoSuchFieldException,
            IllegalAccessException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
        List<String> existedAppResDirList = new ArrayList<>();
        if (Build.VERSION.SDK_INT >= 28) {
            Object[] apkAssets = (Object[]) VersionCompat.getGetApkAssetsMethod().invoke(asset);
            for (Object apkAsset : apkAssets) {
                String path = (String) VersionCompat.getGetAssetPathMethod().invoke(apkAsset);
                existedAppResDirList.add(path);
            }
        } else {
            Object[] appStringBlocks = (Object[]) VersionCompat.mStringBlocksInAssetManager().get(asset);
            int totalResCount = appStringBlocks.length;
            for (int appResIndex = 1; appResIndex <= totalResCount; ++appResIndex) {
                String inApp = (String) VersionCompat.getGetCookieNameMethod().invoke(asset, appResIndex);
                existedAppResDirList.add(inApp);
            }
        }
        return existedAppResDirList;
    }

    private static class V21 extends VersionCompat {

        private static void installSplitResDirs(Resources preResources, List<String> splitResPaths) throws Throwable {
            Method method = VersionCompat.getAddAssetPathMethod();
            for (String splitResPath : splitResPaths) {
                method.invoke(preResources.getAssets(), splitResPath);
            }
        }
    }

    private static class V14 extends VersionCompat {

        private static Context getBaseContext(Context context) {
            Context ctx = context;
            while (ctx instanceof ContextWrapper) {
                ctx = ((ContextWrapper) ctx).getBaseContext();
            }
            return ctx;
        }

        private static void checkOrUpdateResourcesForContext(Context context, Resources preResources, Resources newResources)
                throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
            if (context instanceof ContextThemeWrapper && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                Resources themeWrapperResources = (Resources) mResourcesInContextThemeWrapper().get(context);
                if (themeWrapperResources == preResources) {
                    mResourcesInContextThemeWrapper().set(context, newResources);
                    mThemeInContextThemeWrapper().set(context, null);
                }
            }
            //find base context
            Context baseContext = getBaseContext(context);
            if (baseContext.getClass().getName().equals("android.app.ContextImpl")) {
                Resources baseContextRes = (Resources) mResourcesInContextImpl().get(baseContext);
                if (baseContextRes == preResources) {
                    mResourcesInContextImpl().set(baseContext, newResources);
                    mThemeInContentImpl().set(baseContext, null);
                }
            } else {
                try {
                    Resources baseContextRes = (Resources) HiddenApiReflection.findField(baseContext, "mResources").get(baseContext);
                    if (baseContextRes == preResources) {
                        HiddenApiReflection.findField(baseContext, "mResources").set(baseContext, newResources);
                        HiddenApiReflection.findField(baseContext, "mTheme").set(baseContext, null);
                    }
                } catch (NoSuchFieldException e) {
                }
                Resources baseContextRes = (Resources) mResourcesInContextImpl().get(baseContext);
                if (baseContextRes == preResources) {
                    mResourcesInContextImpl().set(baseContext, newResources);
                    mThemeInContentImpl().set(baseContext, null);
                }
            }
        }

        @SuppressLint("PrivateApi")
        private static void installSplitResDirs(Context context, Resources preResources, List<String> splitResPaths) throws Throwable {
            //create a new Resources.
            Resources newResources = createResources(context, preResources, splitResPaths);
            checkOrUpdateResourcesForContext(context, preResources, newResources);
            Object activityThread = getActivityThread();
            Map<IBinder, Object> activities = (Map<IBinder, Object>) mActivitiesInActivityThread().get(activityThread);
            for (Map.Entry<IBinder, Object> entry : activities.entrySet()) {
                Object activityClientRecord = entry.getValue();
                Activity activity = (Activity) HiddenApiReflection.findField(activityClientRecord, "activity").get(activityClientRecord);
                if (context != activity) {
                    SplitLog.i(TAG, "pre-resources found in @mActivities");
                    checkOrUpdateResourcesForContext(activity, preResources, newResources);
                }
            }

            Map<Object, WeakReference<Resources>> activeResources;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                activeResources = (Map<Object, WeakReference<Resources>>) mActiveResourcesInActivityThread().get(activityThread);
            } else {
                Object resourcesManager = getResourcesManager();
                activeResources = (Map<Object, WeakReference<Resources>>) mActiveResourcesInResourcesManager().get(resourcesManager);
            }
            for (Map.Entry<Object, WeakReference<Resources>> entry : activeResources.entrySet()) {
                Resources res = entry.getValue().get();
                if (res == null) {
                    continue;
                }
                if (res == preResources) {
                    activeResources.put(entry.getKey(), new WeakReference<>(newResources));
                    SplitLog.i(TAG, "pre-resources found in @mActiveResources");
                    break;
                }
            }

            Map<String, WeakReference<Object>> instance_mPackages =
                    (Map<String, WeakReference<Object>>) mPackagesInActivityThread().get(activityThread);
            for (Map.Entry<String, WeakReference<Object>> entry : instance_mPackages.entrySet()) {
                Object packageInfo = entry.getValue().get();
                if (packageInfo == null) {
                    continue;
                }
                Resources resources = (Resources) mResourcesInLoadedApk().get(packageInfo);
                if (resources == preResources) {
                    SplitLog.i(TAG, "pre-resources found in @mPackages");
                    mResourcesInLoadedApk().set(packageInfo, newResources);
                }
            }

            Map<String, WeakReference<Object>> instance_mResourcePackages =
                    (Map<String, WeakReference<Object>>) mResourcePackagesInActivityThread().get(activityThread);
            for (Map.Entry<String, WeakReference<Object>> entry : instance_mResourcePackages.entrySet()) {
                Object packageInfo = entry.getValue().get();
                if (packageInfo == null) {
                    continue;
                }
                Resources resources = (Resources) mResourcesInLoadedApk().get(packageInfo);
                if (resources == preResources) {
                    SplitLog.i(TAG, "pre-resources found in @mResourcePackages");
                    mResourcesInLoadedApk().set(packageInfo, newResources);
                }
            }
        }

        private static List<String> getAppResDirs(String appResDir, AssetManager asset) throws NoSuchFieldException,
                IllegalAccessException, NoSuchMethodException, InvocationTargetException {
            List<String> existedAppResDirList;
            AssetManager sysAsset = Resources.getSystem().getAssets();
            Object[] sysStringBlocks = (Object[]) mStringBlocksInAssetManager().get(sysAsset);
            Object[] appStringBlocks = (Object[]) mStringBlocksInAssetManager().get(asset);
            int totalResCount = appStringBlocks.length;
            int sysResCount = sysStringBlocks.length;
            existedAppResDirList = new ArrayList<>(totalResCount - sysResCount);
            for (int appResIndex = sysResCount + 1; appResIndex <= totalResCount; ++appResIndex) {
                String inApp = (String) getGetCookieNameMethod().invoke(asset, appResIndex);
                existedAppResDirList.add(inApp);
            }
            if (!existedAppResDirList.contains(appResDir)) {
                boolean inSystem = false;
                for (int i = 1; i <= sysResCount; i++) {
                    final String cookieNameSys = (String) getGetCookieNameMethod().invoke(sysAsset, i);
                    if (appResDir.equals(cookieNameSys)) {
                        inSystem = true;
                        break;
                    }
                }
                if (!inSystem) {
                    existedAppResDirList.add(0, appResDir);
                }
            }
            return existedAppResDirList;
        }

        private static Resources createResources(Context context, Resources oldRes, List<String> splitResPaths) throws NoSuchFieldException,
                IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            String appResDir = context.getPackageResourcePath();
            AssetManager oldAsset = oldRes.getAssets();
            List<String> resDirs = getAppResDirs(appResDir, oldAsset);
            resDirs.addAll(0, splitResPaths);
            AssetManager newAsset = createAssetManager();
            for (String recent : resDirs) {
                int ret = (int) getAddAssetPathMethod().invoke(newAsset, recent);
                if (ret == 0) {
                    throw new RuntimeException("invoke addAssetPath failure! apk format maybe incorrect");
                }
            }
            return newResources(oldRes, newAsset);
        }

        private static Resources newResources(Resources originRes, AssetManager asset)
                throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            return (Resources) HiddenApiReflection.findConstructor(originRes, AssetManager.class, DisplayMetrics.class, Configuration.class)
                    .newInstance(asset, originRes.getDisplayMetrics(), originRes.getConfiguration());
        }

        private static AssetManager createAssetManager() throws IllegalAccessException, InstantiationException {
            return AssetManager.class.newInstance();
        }
    }

    private static abstract class VersionCompat {

        private static Field mStringBlocksField;

        private static Method addAssetPathMethod;

        private static Method getCookieNameMethod;

        private static Method getAssetPathMethod;

        private static Method getApkAssetsMethod;

        private static Field mActivitiesInActivityThread;

        private static Object activityThread;

        private static Class<?> activityThreadClass;

        private static Class<?> contextImplClass;

        private static Field mResourcesInContextImpl;

        private static Field mThemeInContentImpl;

        private static Field mPackagesInActivityThread;

        private static Field mResourcePackagesInActivityThread;

        private static Field mActiveResourcesInActivityThread;

        private static Field mActiveResourcesInResourcesManager;

        private static Class<?> resourcesManagerClass;

        private static Object resourcesManager;

        private static Field mResourcesInContextThemeWrapper;

        private static Field mThemeInContextThemeWrapper;

        private static Class<?> loadedApkClass;

        private static Field mResourcesInLoadedApk;

        // ...
    }
}

 

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值