分享篇 - Qigsaw (基于 Android App Bundle 动态化⽅案探索) 插件实现分析

Qigsawhttps://github.com/iqiyi/Qigsaw, Qigsaw 是爱奇艺自主研发的动态化框架,其核心优势如下:

  • 利用 Android App Bundle 开发套件,极速开发体验;
  • 支持 Android App Bundle 所有功能特性,"山寨" Play Core Library 公开接口实现,开发者阅读官方文档即可愉快开发;
  • 任何进程均可动态加载插件,支持 Android 四大组件动态加载;
  • 如果您的应用有出海需求,可无缝切换至 Android App Bundle 方案;
  • 仅一处 Hook,少量私有 API 访问,保证框架稳定性。Android 动态化方案,在国内已蓬勃发展数年之久,其核心目的是减少应用包体积,提升应用安装率。

简而言之,Qigsaw 可以让我们在国内使用 Android App Bundle,并且可以无缝切换到 Google Play.

Qigsaw 打包流程图:

由于项目计划使用 Qigsaw,并做一些二次开发,所以需要先深入理解 Qigsaw 的机制和原理。目前准备从 2 个方面来着手研究:

  • BuildSrc  plugin
  • Split native code

本篇文章主要是 buildSrc  插件实现的分析。

目录:

  1. Dynamic feature plugin
  2. App plugin

1. Dynamic feature plugin

Qigsaw 提供了两个插件,分别作用于 App 和 Dynamic feature, 先来看看 Dynamic feature 部分。

扫描二维码关注公众号,回复: 11459576 查看本文章

需要注意几个点:

1.1 SplitInstallHelper.loadResources()

主要为了解决 feature apk 的资源访问问题,具体的实现原理将会在 split native code 篇进行分析,处理完的 Activity  代码如下:


package com.iqiyi.qigsaw.sample.java;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import com.google.android.play.core.splitinstall.SplitInstallHelper;

public class JavaSampleActivity extends Activity {
    public JavaSampleActivity() {
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(2114060288);
    }

    public Resources getResources() {
        SplitInstallHelper.loadResources(this, super.getResources());
        return super.getResources();
    }
}

1.2 {$projectName}SplitLibraryLoader

会创建 {$projectName}SplitLibraryLoader 类,如工程名为 java,则会创建 javaSplitLibraryLoader 类:

package com.iqiyi.android.qigsaw.core.splitlib;

public class javaSplitLibraryLoader {
    public javaSplitLibraryLoader() {
    }

    public void loadSplitLibrary(String var1) {
        System.loadLibrary(var1);
    }
}

这个类的作用是什么呢?首先我们要知道两点:

  1. Qigsaw 是基于对于 com.google.android.play.core 对外暴露的接口,进行了自定义实现。因为 AAB 目前只能对 Google play 上发布应用起作用,所以开发者重新实现了一套 com.google.android.play.core 包名的第三方库,这样就可以做到在国内市场,与国外应用市场无缝迁移。
  2. Qigsaw 提供两种加载方式加载插件 apk,单 classloader 和多 calssloader 模式,单 classloader 涉及私有 api 访问,而多classloader 不涉及私有 api 访问。

这个类的存在就是为了解决多 classloader 模式下的 so 加载问题:

// 该方法会使用调用方的 classloader 从中获取 so 信息并加载
System.loadLibrary(str); 

2. App plugin

用于处理 app 模块,整体的流程如下:

2.1 register ComponentInfoTransform

该 transform 作用是通过每个 Dynamic Feature 库的 merged manifest 文件收集所有的组件信息,包括:Activity, Service, BroadcastReceiver, ContentProvider 以及 Application. 然后通过收集的组件列表,创建一个名为 ComponentInfo 的类,如下:

package com.iqiyi.android.qigsaw.core.extension;

public class ComponentInfo {
    public static final String native_ACTIVITIES = "com.iqiyi.qigsaw.sample.ccode.NativeSampleActivity";
    public static final String java_ACTIVITIES = "com.iqiyi.qigsaw.sample.java.JavaSampleActivity";
    public static final String java_APPLICATION = "com.iqiyi.qigsaw.sample.java.JavaSampleApplication";

    public ComponentInfo() {
    }
}

2.2 qigsawProcessDebugManifest

修改 merged manifest 和 bundle manifest 文件,主要是处理 ContentProvider 结点。

为每个 ContentProvider 创建名为 providerName + "_Decorated_" + splitName 的代理类,其中 providerName 为原始 provider 类名,splitName 为插件 apk 对应的名称,并且该类继承 SplitContentProvider。如下:

package com.iqiyi.qigsaw.sample.java;

import com.iqiyi.android.qigsaw.core.splitload.SplitContentProvider;

public class JavaContentProvider_Decorated_java extends SplitContentProvider {
    public JavaContentProvider_Decorated_java() {
    }
}

之后将 base apk 的 Manifest.xml 文件中该 provider 替换掉,如下所示:

<provider     android:name="com.iqiyi.qigsaw.sample.java.JavaContentProvider_Decorated_java" android:authorities="java.feature" 
android:enabled="true" 
android:exported="false" 
android:splitName="java"/>  

这么做的原因是在 app 启动时 provider 的执行时机是比较靠前的:

Application->attachBaseContext ==>ContentProvider->onCreate ==>Application->onCreate ==>Activity->onCreate

在这个过程中插件 apk 并没有加载进来,一定会报 ClassNotFound。所以将插件 apk 的 provider 生成一个代理类,然后替换掉,如果插件没有加载进来,代理类什么也不执行即可。

2.3 qigsawProcessDebugOldApk

在 app module 中配置 qigsawSplit 时,可以看到 oldApk 这个属性,主要用于更新 split 版本:

def qigsawPath = file("qigsaw/")

qigsawSplit {

    /**
     * optional,default 'null'
     * if you want to update split version, oldApk must be set.
     */
    oldApk = "${qigsawPath}/app.apk"
    ...
}

qigsawProcessDebugOldApk 这个 task 做的事情如下:

(outputs 目录为:/Qigsaw/app/build/intermediates/qigsaw/old_apk/debug/)

2.4 generateDebugQigsawConfig

generateDebugQigsawConfig 依赖于 generateBuildConfigTask, generateDebugQigsawConfig 作用是生成 qigsaw 的 java 配置文件,方便 native 代码中快速获取相关配置,具体流程如下:

QigsawConfig.java 内容如下:

package com.iqiyi.qigsaw.sample;

public final class QigsawConfig {
    public static final boolean QIGSAW_MODE = Boolean.parseBoolean("true");
    public static final String QIGSAW_ID = "1.0.0_d9f55d4";
    public static final String VERSION_NAME = "1.0.0";
    public static final String DEFAULT_SPLIT_INFO_VERSION = "1.0.0_1.0.0";
    public static final String[] DYNAMIC_FEATURES = new String[]{"java", "assets", "native"};

    public QigsawConfig() {
    }
}

2.5 qigsawAssembleDebug

qigsawAssembleDebug 依赖 assembleTask,主要是处理 split apk 和配置文件内置问题。

生成的 apk 截图:

qigsaw json 配置文件,该⽂件记录了插件信息,包括下载地址:

{
	"qigsawId": "9.11.0_0593ffca2",
	"appVersionName": "9.11.0",
	"builtInUrlPrefix": "native://",
	"splits": [{
		"splitName": "WubaHuangyeFeature",
		"url": "http://10.252.209.45:3007/file/WubaHuangyeFeature.apk",
		"builtIn": false,
		"onDemand": true,
		"size": 1914057,
		"applicationName": "com.wuba.huangyefeature.HuangyeApp",
		"version": "1.0@1",
		"md5": "89295a009015a1c400cd2cf4e0696e5a",
		"minSdkVersion": 19,
		"dexNumber": 3
	}, {
		"splitName": "WubaHouseFeature",
		"url": "http://10.252.209.45:3007/file/WubaHouseFeature.apk",
		"builtIn": false,
		"onDemand": true,
		"size": 7866002,
		"applicationName": "com.wuba.housefeature.HouseApp",
		"version": "1.0@1",
		"md5": "124eaaca8b2489837a4b88a1c5e225fd",
		"minSdkVersion": 19,
		"dexNumber": 3
	}, {
		"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
	}, {
		"splitName": "WubaFinanceFeature",
		"url": "http://10.252.209.45:3007/file/WubaFinanceFeature.apk",
		"builtIn": false,
		"onDemand": true,
		"size": 22210,
		"version": "1.0@1",
		"md5": "d58650eb0b50e2782a5b2370f823859f",
		"minSdkVersion": 19,
		"dexNumber": 3
	}],
	"abiFilters": ["armeabi-v7a"]
}

App plugin 完整逻辑图如下: 

猜你喜欢

转载自blog.csdn.net/u014294681/article/details/105636497
今日推荐