The Android App Bundle is a pre-dependency for Qigsaw.
Android App Bundle is a new official distribution format for Android. Aab, which allows you to develop and distribute applications in a more efficient way. With the Android App Bundle, you can more easily provide a great use experience in smaller applications, increasing installation success and reducing uninstalls. The conversion process is easy and convenient. You don’t have to refactor your code to start taking advantage of smaller apps. By switching to this format, you can experience and benefit from modular application development and customizable functionality delivery (PS: must rely on GooglePlay).
Qigsaw is implemented based on AAB, and at the same time, it completely copies the play Core Library interface provided by AAB to load the plug-in. Development can be started by consulting the official documents. Companies with international needs can seamlessly switch between domestic and international versions. At the same time, Qigsaw implements 0 hook, only a few private API access, to ensure its compatibility and stability.
Github:Qigsaw
This article focuses on Qigsaw related plugins.
Qigsaw plug-in
Main engineering to apply plugin: ‘com. Iqiyi. Qigsaw. Application plug-in dependency;
Feature project carries out the following dependencies:
apply plugin: 'com.android.dynamic-feature'
apply plugin: 'com.iqiyi.qigsaw.dynamicfeature'
Copy the code
Gradle. properties file QIGSAW_BUILD=true to generate some information about the feature package.
com.iqiyi.qigsaw.application
Com. Iqiyi. Qigsaw. Application. The properties file contents as follows:
implementation-class=com.iqiyi.qigsaw.buildtool.gradle.QigsawAppBasePlugin
Copy the code
QigsawAppBasePlugin default will register a SplitComponentTransform, in open QIGSAW_BUILD will also register SplitResourcesLoaderTransform after = true. Implement AOP on plug-in content via Transform.
The QigsawAppBasePlugin generates Qigsaw artifacts for processing plugin and base package information, in addition to registering two transforms.
com.iqiyi.qigsaw.dynamicfeature
Com. Iqiyi. Qigsaw. Dynamicfeature. The properties file contents as follows:
implementation-class=com.iqiyi.qigsaw.buildtool.gradle.QigsawDynamicFeaturePlugin
Copy the code
Registers after QigsawDynamicFeaturePlugin in open QIGSAW_BUILD = true SplitResourcesLoaderTransform and SplitLibraryLoaderTransform of AOP plug-in content .
SplitResourcesLoaderTransform
Mainly to the Activity, Service, and the Receiver class getResources injection SplitInstallHelper. LoadResources (this, super getResources ()).
interface SplitComponentWeaver {
/** * link target */
String CLASS_WOVEN = "com/google/android/play/core/splitinstall/SplitInstallHelper"
/** * link method */
String METHOD_WOVEN = "loadResources"
byte[] weave(InputStream inputStream)
}
Copy the code
The relevant injection classes are:
class SplitResourcesLoaderInjector {
WaitableExecutor waitableExecutor
/** * embedded Activity */
Set<String> activities
Set<String> services
Set<String> receivers
SplitActivityWeaver activityWeaver
SplitServiceWeaver serviceWeaver
SplitReceiverWeaver receiverWeaver
/** ** */
}
Copy the code
The main difference between the base package and the plug-in is the target of registration:
Basic package just read build. Gradle file qigsawSplit. BaseContainerActivities configuration Activity.
The plugin needs to read the Activity, Service, and Receiver in the Androidmanifest.xml file.
SplitInstallHelper.loadResources(this, super.getResources()); Add all plug-in resource paths to AssetManager, so that each plug-in can access all resources, the key implementation code is as follows:
static Method getAddAssetPathMethod(a) throws NoSuchMethodException {
if (addAssetPathMethod == null) {
addAssetPathMethod = HiddenApiReflection.findMethod(AssetManager.class, "addAssetPath", String.class);
}
return addAssetPathMethod;
}
Copy the code
SplitComponentTransform
The Transform performs two operations:
- Reading individual plug-ins
apk
theManifest
File, createComponentInfo
Class and will integrate individual plug-insapk
theApplication
.Activity
.Service
.Recevier
Record in the field of the class, field nameProject name + componentType name with values for individual plug-insapk
Contains components, such as multiple components, separated by commas.
//com.iqiyi.android.qigsaw.core.extension.ComponentInfo
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";
}
Copy the code
- For each
provider
Create the proxy class nameString providerClassName=providerName+"Decorated"+splitName
, includingproviderName
As the originalprovider
The name of the class,splitName
For the plug-inapk
The corresponding name, and the class inheritsSplitContentProvider
.
public class JavaContentProvider_Decorated_java extends SplitContentProvider {}
Copy the code
Why do you do that?
Because the execution time of the provider is relatively early when the APP starts, Application->attachBaseContext ==>ContentProvider->onCreate ==>Application->onCreate ==>Activity->onCreate in this process our plugin APk is not loaded, must be reported ClassNotFound. So we generate a proxy class for the plugin APK’s provider, and then replace it. If the plugin is not loaded, the proxy class does nothing. That’s a good solution to our problem.
SplitLibraryLoaderTransform
SplitLibraryLoaderTransform class of operation is to dynamic – feature in the process of constructing apk, Created to “com. Iqiyi. Android. Qigsaw. Core. Splitlib.” + project. The name + “SplitLibraryLoader” class.
// com.iqiyi.android.qigsaw.core.splitlib.assetsSplitLibraryLoader
// com.iqiyi.android.qigsaw.core.splitlib.javaSplitLibraryLoader
// com.iqiyi.android.qigsaw.core.splitlib.nativeSplitLibraryLoader
package com.iqiyi.android.qigsaw.core.splitlib;
public class javaSplitLibraryLoader {
public void loadSplitLibrary(String str) { System.loadLibrary(str); }}Copy the code
What does this class do?
Let’s explain it and you’ll find it interesting.
Qigsaw
Is based oncom.google.android.play.core
External exposure method, a custom implementation. becauseaab
For the time being, onlygoogle play
Releasing the app worked, so the developers re-implemented itcom.google.android.play.core
Third party library of package name, so that can be achieved in the domestic market, with foreign application market seamless migration.Qigsaw
Two loading methods are provided to load plug-insapk
Alone,Classloader
And moreClassloader
Model, singleClassloader
Involved in privateapi
Visit, but moreClassloader
No private involvementapi
Access.
This class is designed to solve the problem of loading so in multiple classLoaders. This method takes so information from it and loads it using the caller’s Classloader.
//java.lang.System.java
@CallerSensitive
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
Copy the code
In multi-classLoader mode, each plug-in needs its own Classloader,so and dex are recorded in its own Classloader. Therefore, in multi-classLoader mode, System.loadlibrary should be called by the plug-in apK’s respective Classloader. See the SplitLibraryLoaderHelper class for an implementation.
//com.iqiyi.android.qigsaw.core.splitload.SplitLibraryLoaderHelper.java
private static boolean loadSplitLibrary0(ClassLoader classLoader, String splitName, String name) {
try{ Class<? > splitLoaderCl = classLoader.loadClass("com.iqiyi.android.qigsaw.core.splitlib." + splitName + "SplitLibraryLoader");
Object splitLoader = splitLoaderCl.newInstance();
Method method = HiddenApiReflection.findMethod(splitLoaderCl, "loadSplitLibrary", String.class);
method.invoke(splitLoader, name);
return true;
} catch (Throwable ignored) {
}
return false;
}
Copy the code
Qigsaw compiled and parsed
Qigsaw packaging process
copySplitManifestDebug
Implement a copy of the Androidmanifest.xml file generated under the feature package.
The target file and address: featureName/build/intermediates/merged_manifests/debug/AndroidManifest. XML.
Copy the address: app/build/intermediates/qigsaw/split – outputs/manifests/debug.
The copied file name is $featureName
ProcessTaskDependenciesBetweenBaseAndSplitsWithQigsaw
Trigger copySplitManifestDebug task, will feature packets generated by the product and data output to the qigsawProcessDebugManifest tasks.
extractTargetFilesFromOldApk
Will app_debug. Apk decompression to release all assets/directory content into app/build/intermediates/qigsaw/old apk/target files/XXX
qigsawProcessDebugManifest
The $ContentProviderName_Decorated_$featureName created by SplitComponentTransform inherits the SplitContentProvider instead of the Provider.
Since the Provider needs to be loaded when the application is started, in case the feature package is not downloaded at this time, load a proxy Provider first.
generateDebugQigsawConfig
Generate the following files:
@Keep
public final class QigsawConfig {
public static final String DEFAULT_SPLIT_INFO_VERSION = "1.0.0 _1. 0.0";
public static final String[] DYNAMIC_FEATURES = {"java"."assets"."native"};
public static final String QIGSAW_ID = "1.0.0 _c40ab5d";
public static final boolean QIGSAW_MODE = Boolean.parseBoolean("true");
public static final String VERSION_NAME = "1.0.0";
}
Copy the code
QIGSAW_ID returns the id of the base package, if not the current QigsawId.
processSplitApkDebug
Each feature needs to perform tasks to process its own APK and generate corresponding JSON files.
-
To extract feature pack apk files to app/build/intermediates/qigsaw/split – outputs/unzip/debug / $featureName files;
-
Iterate through the lib file directory in the decompressed APK to find the supported ABI;
-
If there are lib files and so files, generate an Androidmanifest.xml file in that directory.
-
Compress the lib file and generated Androidmanifest. XML into protoAbiApk;
-
ProtoAbiApk was translated into binaryAbiApk using aapT2 tools.
-
Will be signature generated binaryAbiApk app/build/intermediates/qigsaw/split – outputs/apks/debug / $feature – $abi. Apk.
-
Generate splitInfo.splitapkData;
{ "abi": "x86"."url": "assets://qigsaw/native-x86.zip"."md5": "03a29962b87c6ed2a7961b6dbe45f532"."size": 8539 } Copy the code
-
-
Unzip apk to $fearure-master-unsigned. Apk. Signature generated app/build/intermediates/qigsaw/split – outputs/apks/debug / $feature – master. Apk.
-
Generate splitInfo.splitapkData;
{ "abi": "master"."url": "assets://qigsaw/native-master.zip"."md5": "3b89066aeaf7d2c2a59b4f3a10fef345"."size": 12824 } Copy the code
-
Splitinfo.splitlibdata;
{ "abi": "arm64-v8a"."jniLibs": [{"name": "libhello-jni.so"."md5": "2938d8b40825e82715422dbdba479e4f"."size": 5896}}]Copy the code
-
Finally generate each feature SplitInfo data, write/app/build/intermediates/qigsaw/split – outputs/split – info/debug / $featureName. Json file;
public class SplitInfo implements Cloneable.GroovyObject { private String splitName;/ / feature package name private boolean builtIn;/ /! onDemand||! ReleaseSplitApk (releaseSplitApk is gradle configuration) private boolean onDemand;// From onDemand in androidmanifest.xml private String applicationName;/ / feature application name private String version;// versionname@versioncode in feature package private int minSdkVersion;// Feature minimum version private int dexNumber;// Number of dex in feature package private Set<String> dependencies;// Feature package dependencies; private Set<String> workProcesses;// The Activity, Service, Receiver, and provider processes configured in androidmanifest.xml. private List<SplitInfo.SplitApkData> apkData;/ / SplitInfo SplitApkData data private List<SplitInfo.SplitLibData> libData;/ / SplitInfo SplitLibData data } Copy the code
qigsawAssembleDebug
- will
build/intermediates/qigsaw/split-outputs/split-info/debug
Each of thefeature
Package of the generatedjson
Merger; - willAfter the mergerFile with the base package
Qigsaw
Compare configuration files to generate new onesIncremental Qigsaw
Configuration file;- The rule of comparison is
verisonName
Compare and contrast when equalsplit.version
, a difference means an update; - If there are updates, then
QigsawId
For the base packageQigsawId
And analyze and modifysplit
Information;- Modify the
split
Information when the samesplitName
contrastsplit.version
. If they are the same thensplit
Using base packagessplit
Information, ifdifferentThen thesplit
thebuiltIn=false
.onDemand=true
. And there will be updatessplit
Do record (updatesplits
Field value). At this timeupdateMode
A value ofVERSION_CHANGED=1; - Without any modifications, then
updateMode
A value ofVERSION_NO_CHANGED=2; - If there is no base package, then
updateMode
A value ofDEFAULT=0;
- Modify the
- The rule of comparison is
- Judge separately if
feature
The packagebuiltIn
isfalse;- Determine whether there is an upload service, if so upload
feature
The package. After successful upload will be correspondingurl
Change the address to downloadablehttp
Address. If the address is empty, or nothttp
It will run abnormally at the beginning. - If the upload service is not implemented then
builtIn
Set totrue;
- Determine whether there is an upload service, if so upload
- formatting
split
Content, write tobuild/intermediates/qigsaw/split-details/debug
File directory. - will
updateMode
Values tobuild/intermediates/qigsaw/split-details/debug/_update_record_.json
File. - if
updateMode
A value ofVERSION_NO_CHANGED, it will beintermediates/qigsaw/old-apk/target-files/debug/assets/qigsaw/qigsaw_*.json
Copy files toapp/build/intermediates/merged_assets/debug/out/qigsaw/qigsaw_*.json
;- Otherwise it would be
app/build/intermediates/qigsaw/split-details/debug/qigsaw_*.json
Copy files toapp/build/intermediates/merged_assets/debug/out/qigsaw/qigsaw_*.json
;
- Otherwise it would be
- to
app/build/intermediates/qigsaw/split-details/debug/base.app.cpu.abilist.properties
write-supportedabi
And copy it toapp/build/intermediates/merged_assets/debug/out/
The following; - traverse
feature
The generatedsplitinfo
Information, ifbuiltIn
istrue;- if
updateMode
A value ofDEFAULT=0, will beapp/build/intermediates/qigsaw/split-outputs/apks/debug/*.apk
Copy toapp/build/intermediates/merged_assets/debug/out/qigsaw/*.zip
; - if
updateMode
A value ofDEFAULT! = 0To determine thefeature
Is it inupdateSplits
;- If so
app/build/intermediates/qigsaw/split-outputs/apks/debug/*.apk
Copy toapp/build/intermediates/merged_assets/debug/out/qigsaw/*.zip
; - If not,
app/build/intermediates/qigsaw/old-apk/target-files/debug/assets/qigsaw/*.zip
Copy toapp/build/intermediates/merged_assets/debug/out/qigsaw/*.zip
;
- If so
- if
product
Qigsaw configuration file
{
"qigsawId": "1.0.0 _c40ab5d"."appVersionName": "1.0.0"."updateSplits": [
"java"]."splits": [{"splitName": "java"."builtIn": true."onDemand": false."applicationName": "com.iqiyi.qigsaw.sample.java.JavaSampleApplication"."version": "1.1 @ 1"."minSdkVersion": 14."dexNumber": 2."workProcesses": [
""]."apkData": [{"abi": "master"."url": "assets://qigsaw/java-master.zip"."md5": "658bc419a9d3c7812a36e61f6c5be4c4"."size": 12822} {}]"splitName": "native"."builtIn": true."onDemand": true."version": "1.0 @ 1"."minSdkVersion": 14."dexNumber": 2."apkData": [{"abi": "arm64-v8a"."url": "assets://qigsaw/native-arm64-v8a.zip"."md5": "b01ad63db38a4ec5fad3284c573a02d3"."size": 8545
},
{
"abi": "master"."url": "assets://qigsaw/native-master.zip"."md5": "3c41745a16a31e967cde8247009463f1"."size": 12824}]."libData": [{"abi": "arm64-v8a"."jniLibs": [{"name": "libhello-jni.so"."md5": "2938d8b40825e82715422dbdba479e4f"."size": 5896}]}]}Copy the code
Qigsaw loaded compression package
Next research knowledge points
- Confusing related operation;
Tinker
Hot modification related operation;
The article here is all about the end, if there are other need to exchange can leave a message oh ~! ~!