Qigsaw 框架原理解析-如何在国内使用 Android App Bundle 的动态交付功能

目录

  1. Android App Bundle
  2. Qigsaw 简介
  3. 编译期处理
  4. 安装运行流程
  5. 热修复

1. Android App Bundle

Android App Bundle(AAB) 是一种改进的应用程序打包方式,能大幅度减少应用体积。简而言之,可以理解 Google 官方的动态发布方案。

好处

  1. Size 更小
  2. 安装更快
  3. 支持动态发布

限制

  1. 仅限于通过 Google Play 发布的应用,(Google 进一步巩固自身生态)。
  2. 需要加入到 Google 的 beta program enroll your app in app signing by Google
    Play in the Play Console.
  3. 最低支持版本 Android 5.0 (API level 21).
  4. 低于Android 5.0 (API level 21) 的版本 GooglePlay 会优化 size,但不支持动态交付。

成本

  1. 需要升级到 Android Studio 3.2 修改工程以便支持 App Bundle 格式
  2. 集成 Play Core Library

动态交付

  1. 为每个用户的设备配置生成并提供优化的 APK
  2. 动态下载安装

在这里插入图片描述


2. Qigsaw 简介

GitHub: https://gitee.com/mirrors/Qigsaw

由于国内无法直接使用 Google Play,从而也无法使用 AAB. 爱奇艺开源框架 Qigsaw 利用国内的插件化原理,实现了在国内使用 AAB 的动态交付的动态下载安装功能。

Qigsaw 是一套基于 Android App Bundles 实现的 Android 动态组件化方案,它无需应用重新安装即可动态分发插件。

在这里插入图片描述

Qigsaw 的核心优势:

  1. 利用Android App Bundle开发套件,极速开发体验。
  2. 支持Android App Bundle所有功能特性,“山寨” Play Core Library 公开接口实现,开发者阅读官方文档即可愉快开发。
  3. 任何进程均可动态加载插件,支持 Android 四大组件动态加载。
  4. 如果您的应用有出海需求,可无缝切换至 Android App Bundle 方案。
  5. 仅一处 Hook,少量私有 API 访问,保证框架稳定性。

已知问题:

  1. 插件无法更新 AndroidManifest.xml,例如新增四大组件等。
  2. 无法动态更新 base APK。
  3. 插件不支持增量更新。
  4. 不支持低于 4.0 的 Android 系统版本。

看看 58App 利用 Qigsaw 实现的 AAB 动态下载安装效果:
在这里插入图片描述
在这里插入图片描述

接下来来看一下 Qigsaw 的实现原理。


3. 编译期处理

Qigsaw 提供了两个 gradle 插件:

  1. App Plugin
  2. Dynamic Feature Plugin

我这边进行了提炼,它们主要做的工作包括如下几块:

  1. 修改四大组件及其配置
  2. 新增辅助类
  3. 上传 split Apk 文件
  4. 生成配置文件,重打包 Apk

3.1 修改四大组件及其清单配置

组件 处理
Activity getResources() 方法注入 SplitInstallHelper.loadResources(…), 解决访问插件资源的问题
Service onCreate() 函数中插入 SplitInstallHelper.loadResources(…),解决访问插件资源的问题
BroadcastReceiver onCreate() 函数中插入 SplitInstallHelper.loadResources(…),解决访问插件资源的问题
ContentProvider 为每个 ContentProvider 创建名为 providerName + “Decorated” + splitName 的代理类,其中 providerName 为原始 provider 类名,splitName 为插件 apk 对应的名称,并且该类继承 SplitContentProvider

可以看到,Activity, Service, BroadcastReceiver 的处理都是为了解决资源访问问题,而 ContentProvider 的处理​​,是替换成代理类,这么做的原因是在 app 启动时 ContentProvider 的执行时机是比较靠前的: ​ContentProvider 的初始化位于 Application 的 attachBaseContext 和 onCreate 之间,在这个过程中插件 apk 并没有加载进来,一定会报 ClassNotFound。所以将插件 apk 的 provider 生成一个代理类,然后替换掉,如果插件没有加载进来,代理类什么也不执行即可。

3.2 新增 class

新增 class 说明
javaSplitLibraryLoader 用于多 ClassLoader 模式下,调用插件自身的 ClassLoader 加载 so
ComponentInfo 用于存储插件四大组件信息
ContentProvider 代理类 用于解决 ContentProvider 初始化问题
SplitLibraryLoader 处理多 classloader 场景下 so 加载的问题
QigsawConfig 包含 Q​jigsaw 模式,id, 版本号,feature 数组配置

3.3 上传 split Apk 文件

每个应用了 Dynamic Feature Plugin 的库会变成动态 feature 库,动态 feature 库编译的产物的 Apk 文件。可以配置是内置还是动态下载安装。如果配置为动态下载安装,则会将对应的 Apk 文件上传到 CDN,否则会打包到基础包的 Apk 中。

splitUpload {
	//  配置上传接口
    uploadUrl = "https:xxxxxx"
}

实现 Qigsaw 对外暴露的上传接口,上传成功返回动态 Apk 的上传地址,写入到配置文件中。
在这里插入图片描述
在这里插入图片描述

3.4 生成配置文件,重打包 Apk

生成 Qigsaw 配置文件,文件中包含版本信息,以及每个动态库模块的信息(是否内置,下载地址,Apk 文件 md5 …),如下所示:

{
	"qigsawId": "9.11.0_0593ffca2",
	"appVersionName": "9.11.0",
	"builtInUrlPrefix": "native://",
	"splits": [{
		"splitName": "WubaPincheFeature",
		"url": "native://libsplit_WubaPincheFeature.so",
		"builtIn": true,
		"onDemand": false,
		"size": 558633,
		"version": "1.0@1",
		"md5": "1c338962f8a89edc48f2c1ac95b71649",
		"minSdkVersion": 19,
		"dexNumber": 3
	}, {
		"splitName": "WubaJobFeature",
		"url": "http://10.252.209.45:3007/file/WubaJobFeature.apk",
		"builtIn": false,
		"onDemand": true,
		"size": 5016932,
		"applicationName": "com.wuba.jobfeature.JobApp",
		"version": "1.0@1",
		"md5": "970d88c082e7fc9647978a85b3657542",
		"minSdkVersion": 19,
		"dexNumber": 3,
		"nativeLibraries": [{
			"abi": "armeabi-v7a",
			"jniLibs": [{
				"name": "libaesutil.so",
				"md5": "c26a5631ebad7ec47d19c837328526e4",
				"size": 17960
			}]
		}]
	}, {
		"splitName": "WubaCarFeature",
		"url": "http://10.252.209.45:3007/file/WubaCarFeature.apk",
		"builtIn": false,
		"onDemand": true,
		"size": 5415268,
		"applicationName": "com.wuba.carfeature.CarApp",
		"version": "1.0@1",
		"md5": "18b57baf0dae6745e8de3b2b27f6a937",
		"minSdkVersion": 19,
		"dexNumber": 3
	}
],
	"abiFilters": ["armeabi-v7a"]
}

内置的模块,如果是多 abi 架构,则会以 zip 格式内置在 assets 中,如果是单 abi 架构,则会以 so 格式内置在 libs 中。

最后对基础包 Apk 进行重打包签名处理。


4. 安装流程

4.1 下载

在这里插入图片描述
动态安装时,需要传递安装下载的 module 名称和回调接口。​安装下载过程会判断 split 是内置的还是需要远程下载的,如果是非内置,则会开启跨进程服务进行下载,下载成功后执行安装流程。


4.2 安装前处理

拷贝:

判断 split 动态包是存放在 libs 还是 assets 中,如果是存放在 assets,则拷贝到 data 的指定目录,存放在 libs 中不需要进行拷贝操作。安装完成会在 /data/app 包名/app_qigsaw/ 目录生成对应的文件夹,如安装 native 模块,则会生成 native 目录:
在这里插入图片描述

校验:

拷贝完成后,进行校验,会校验两个部分:签名和 split 文件的 md5,签名校验是可选的,而 md5 校验是必须的

以上两步为安装前处理,接下来进入安装流程。

4.3 安装

首次安装为顺序触发内置->安装/下载->安装的流程,二次安装为 App 启动时,加载已安装模块。
在这里插入图片描述

4.3.1 classloader

替换 App 默认的 classloader: 代理包装自定义的 PathClassLoader, 替换掉当前 LoadApk 的 classLoader. 特别注意,运行时在 class 加载失败时,如果 class 是 Activity, Service, BroadcastReceiver,则会返回一个对应类型的 Fake class, 然后可以在 Fake class 中加载安装加载对应的组件。

final class SplitDelegateClassloader extends PathClassLoader {

    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);
        }
    }
    
	// ...
	
    @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
    protected URL findResource(String name) {
        URL resource = super.findResource(name);
        if (resource == null) {
            Set<SplitDexClassLoader> splitDexClassLoaders = SplitApplicationLoaders.getInstance().getClassLoaders();
            for (SplitDexClassLoader loader : splitDexClassLoaders) {
                resource = loader.findResourceItself(name);
                if (resource != null) {
                    break;
                }
            }
        }
        return resource;
    }

    @Override
    protected Enumeration<URL> findResources(String name) {
        Enumeration<URL> resources = super.findResources(name);
        if (resources == null) {
            Set<SplitDexClassLoader> splitDexClassLoaders = SplitApplicationLoaders.getInstance().getClassLoaders();
            for (SplitDexClassLoader loader : splitDexClassLoaders) {
                resources = loader.findResourcesItself(name);
                if (resources != null) {
                    break;
                }
            }
        }
        return resources;
    }

    @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;
    }
}

单 classloader 安装实现:

从 android 7.0(25), android 6.0(23) 等做了不同的适配,反射获取当前的 classloader 的 pathList, 将插件的 so path 添加进去。适配不同 API,反射 classloader 的 pathList -> dexElements,将 dex 加进去。

/**
 * 单 classloder 安装
 */
final class SplitLoaderImpl2 extends SplitLoader {

    SplitLoaderImpl2(Context context) {
        super(context);
    }

    @Override
    void loadCode2(@Nullable List<String> dexPaths,
                   File optimizedDirectory,
                   @Nullable File librarySearchPath) throws SplitLoadException {
        ClassLoader curCl = SplitLoader.class.getClassLoader();
        loadLibrary(curCl, librarySearchPath);
        loadDex(curCl, dexPaths, optimizedDirectory);
    }

    private void loadLibrary(ClassLoader classLoader, File librarySearchPath) throws SplitLoadException {
        if (librarySearchPath != null) {
            try {
                SplitCompatLibraryLoader.load(classLoader, librarySearchPath);
            } catch (Throwable cause) {
                throw new SplitLoadException(SplitLoadError.LOAD_LIB_FAILED, cause);
            }
        }
    }

    private void loadDex(ClassLoader classLoader, List<String> dexPaths, File optimizedDirectory) throws SplitLoadException {
        if (dexPaths != null) {
            List<File> dexFiles = new ArrayList<>(dexPaths.size());
            for (String dexPath : dexPaths) {
                dexFiles.add(new File(dexPath));
            }
            try {
                SplitCompatDexLoader.load(classLoader, optimizedDirectory, dexFiles);
                SplitUnKnownFileTypeDexLoader.loadDex(classLoader, dexPaths, optimizedDirectory);
            } catch (Throwable cause) {
                throw new SplitLoadException(SplitLoadError.LOAD_DEX_FAILED, cause);
            }
        }
    }

}
/**
 * 单 classloder 安装 so
 */
final class SplitCompatLibraryLoader {
    static void load(ClassLoader classLoader, File folder)
            throws Throwable {
        if (folder == null || !folder.exists()) {
            return;
        }
        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!");
        }
    }
 }
/**
 * 单 classloder 安装 dex
 */
final class SplitCompatDexLoader {
    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();
        }
    }

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

            }
        }
        // ...
}
    

多 classloader 安装实现:

使用 DexClassLoader, 每个插件 new 一个 DexClassLoader, 然后使用这个 DexClassLoader 加载插件的代码。所有插件的 DexClassLoader 都会缓存起来。 而且该自定义的 DexClassLoader 重写了 findClass, findResources, findLibrary, 加载异常时去分别从插件它依赖的 classloader 中尝试加载 ​(其他 split classloader)。

final class SplitDexClassLoader extends BaseDexClassLoader {

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

    static SplitDexClassLoader create(String moduleName,
                                      List<String> dexPaths,
                                      File optimizedDirectory,
                                      File librarySearchFile,
                                      List<String> dependencies) throws Throwable {
        SplitDexClassLoader cl = new SplitDexClassLoader(
                moduleName,
                dexPaths,
                optimizedDirectory,
                librarySearchFile == null ? null : librarySearchFile.getAbsolutePath(),
                dependencies,
                SplitDexClassLoader.class.getClassLoader()
        );
        return cl;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            return super.findClass(name);
        } catch (ClassNotFoundException e1) {
            if (dependenciesLoaders != null) {
                for (SplitDexClassLoader loader : dependenciesLoaders) {
                    try {
                        return loader.loadClassItself(name);
                    } catch (ClassNotFoundException e2) {
                        SplitLog.w(TAG, "SplitDexClassLoader: Class %s is not found in %s ClassLoader", name, loader.moduleName());
                    }
                }
            }
            throw e1;
        }
    }

    String moduleName() {
        return moduleName;
    }

    @Override
    public String findLibrary(String name) {
        String libName = super.findLibrary(name);
        if (libName == null) {
            if (dependenciesLoaders != null) {
                for (SplitDexClassLoader loader : dependenciesLoaders) {
                    libName = loader.findLibrary(name);
                    if (libName != null) {
                        break;
                    }
                }
            }
        }
        if (libName == null) {
            if (getParent() instanceof BaseDexClassLoader) {
                libName = ((BaseDexClassLoader) getParent()).findLibrary(name);
            }
        }
        return libName;
    }
	// ...
}
4.3.2 resources

5.0 以上,反射调用 AssetManager.addAssetPath, 5.0 以下使用反射创建 Resources, 传入当前宿主的 resources 与插件的 resource path, 进行 add 添加,最后将 ContextImpl,或者当前 context 的 resources 对象替换成这个新的 resources 对象.

@RestrictTo(LIBRARY_GROUP)
public class SplitCompatResourcesLoader {
	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 {
                // ...
            }
        }
    }

	/**
	 * 反射 AssetManager.addAssetPath 进行添加
	 */
	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 void installSplitResDirs(Context context, Resources preResources, List<String> splitResPaths) throws Throwable {
            //create a new Resources.
            Resources newResources = createResources(context, preResources, splitResPaths);
            // ...
            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);
                }
            }
        }
   }
}
4.3.3 四大组件

由于 AAB 的特性,会在编译期间提前合并四大组件的清单配置,所以不支持四大组件的热更新。这样也就没有别的插件化框架针对四大组件热更新的处理:包括预埋、代理、hook ASM 等,所以 Qigsaw 框架整体 hook 少,稳定性强。

4.3.4 Application

安装完成后,会激活插件的 Applicaton, 调用其 attach():

    public void createAndActiveSplitApplication(Context appContext, boolean qigsawMode) {
        if (qigsawMode) {
            return;
        }
        final Set<String> aabLoadedSplits = new SplitAABInfoProvider(appContext).getInstalledSplitsForAAB();
        if (!aabLoadedSplits.isEmpty()) {
            for (String splitName : aabLoadedSplits) {
                try {
                    Application app = createApplication(AABExtension.class.getClassLoader(), splitName);
                    if (app != null) {
                        activeApplication(app, appContext);
                        aabApplications.add(app);
                    }
                } catch (AABExtensionException e) {
                    SplitLog.w(TAG, "Failed to create " + splitName + " application", e);
                }
            }
        }
    }

   

4.4 二次安装

启动 Application 时,将已安装的 split,根据对应的 classloader 来加载插件。

abstract class SplitLoadTask implements Runnable {
	// ...
	
    @Override
    public final void run() {
        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
            loadSplitInternal();
        } else {
            // ...
        }
    }

    private void loadSplitInternal() {
        SplitLoader loader = createSplitLoader();
        Set<Split> splits = new HashSet<>();
        List<SplitLoadError> loadErrors = new ArrayList<>(0);
        List<SplitBriefInfo> splitBriefInfoList = new ArrayList<>(splitFileIntents.size());
        for (Intent splitFileIntent : splitFileIntents) {
            String splitName = splitFileIntent.getStringExtra(SplitConstants.KET_NAME);
            SplitInfo info = infoManager.getSplitInfo(appContext, splitName);
            if (info == null) {
                continue;
            }
            // 加载 resources
            String splitApkPath = splitFileIntent.getStringExtra(SplitConstants.KEY_APK);
            try {
                loader.loadResources(splitApkPath);
            } catch (SplitLoadException e) {
                SplitLog.printErrStackTrace(TAG, e, "Failed to load split %s resources!", splitName);
                loadErrors.add(new SplitLoadError(splitBriefInfo, e.getErrorCode(), e.getCause()));
                continue;
            }
            // 加载 dex (class 和 so)
            List<String> addedDexPaths = splitFileIntent.getStringArrayListExtra(SplitConstants.KEY_ADDED_DEX);
            File optimizedDirectory = SplitPathManager.require().getSplitOptDir(info);
            File librarySearchPath = null;
            if (info.hasLibs()) {
                librarySearchPath = SplitPathManager.require().getSplitLibDir(info);
            }
            File splitDir = SplitPathManager.require().getSplitDir(info);
            ClassLoader classLoader;
            try {
                classLoader = loadCode(loader, splitName, addedDexPaths, optimizedDirectory, librarySearchPath, info.getDependencies());
            } catch (SplitLoadException e) {
                continue;
            }
            // 激活 split, 包含插件的 applicaton 以及 contentProvider 
            try {
                activator.activate(classLoader, splitName);
            } catch (SplitLoadException e) {                onSplitActivateFailed(classLoader);
                continue;
            }
            // ...
            splits.add(new Split(splitName, splitApkPath));
        }
        loadManager.putSplits(splits);
        reportLoadResult(splitBriefInfoList, loadErrors, System.currentTimeMillis() - time);
    }
}

5. 热修复

Qigsaw 热更新:

  1. 插件无法更新 AndroidManifest.xml,例如新增四大组件等。
  2. 无法动态更新 base APK。
  3. 插件不支持增量更新。

在这里插入图片描述

App 启动时会请求服务器检查当前版本是否有更新的配置文件,如果有,则会下载更新配置文件,然后在对应的动态包中进行下载更新。

猜你喜欢

转载自blog.csdn.net/u014294681/article/details/107605305