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-insapktheManifestFile, createComponentInfoClass and will integrate individual plug-insapktheApplication.Activity.Service.RecevierRecord in the field of the class, field nameProject name + componentType name with values for individual plug-insapkContains 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 eachproviderCreate the proxy class nameString providerClassName=providerName+"Decorated"+splitName, includingproviderNameAs the originalproviderThe name of the class,splitNameFor the plug-inapkThe 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.

  • QigsawIs based oncom.google.android.play.coreExternal exposure method, a custom implementation. becauseaabFor the time being, onlygoogle playReleasing the app worked, so the developers re-implemented itcom.google.android.play.coreThird party library of package name, so that can be achieved in the domestic market, with foreign application market seamless migration.
  • QigsawTwo loading methods are provided to load plug-insapkAlone,ClassloaderAnd moreClassloaderModel, singleClassloaderInvolved in privateapiVisit, but moreClassloaderNo private involvementapiAccess.

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

  • willbuild/intermediates/qigsaw/split-outputs/split-info/debugEach of thefeaturePackage of the generatedjsonMerger;
  • willAfter the mergerFile with the base packageQigsawCompare configuration files to generate new onesIncremental QigsawConfiguration file;
    • The rule of comparison isverisonNameCompare and contrast when equalsplit.version, a difference means an update;
    • If there are updates, thenQigsawIdFor the base packageQigsawIdAnd analyze and modifysplitInformation;
      • Modify thesplitInformation when the samesplitNamecontrastsplit.version. If they are the same thensplitUsing base packagessplitInformation, ifdifferentThen thesplitthebuiltIn=false.onDemand=true. And there will be updatessplitDo record (updatesplitsField value). At this timeupdateModeA value ofVERSION_CHANGED=1;
      • Without any modifications, thenupdateModeA value ofVERSION_NO_CHANGED=2;
      • If there is no base package, thenupdateModeA value ofDEFAULT=0;
  • Judge separately iffeatureThe packagebuiltInisfalse;
    • Determine whether there is an upload service, if so uploadfeatureThe package. After successful upload will be correspondingurlChange the address to downloadablehttpAddress. If the address is empty, or nothttpIt will run abnormally at the beginning.
    • If the upload service is not implemented thenbuiltInSet totrue;
  • formattingsplitContent, write tobuild/intermediates/qigsaw/split-details/debugFile directory.
  • willupdateModeValues tobuild/intermediates/qigsaw/split-details/debug/_update_record_.jsonFile.
  • ifupdateModeA value ofVERSION_NO_CHANGED, it will beintermediates/qigsaw/old-apk/target-files/debug/assets/qigsaw/qigsaw_*.jsonCopy files toapp/build/intermediates/merged_assets/debug/out/qigsaw/qigsaw_*.json;
    • Otherwise it would beapp/build/intermediates/qigsaw/split-details/debug/qigsaw_*.jsonCopy files toapp/build/intermediates/merged_assets/debug/out/qigsaw/qigsaw_*.json;
  • toapp/build/intermediates/qigsaw/split-details/debug/base.app.cpu.abilist.properties write-supportedabiAnd copy it toapp/build/intermediates/merged_assets/debug/out/The following;
  • traversefeatureThe generatedsplitinfoInformation, ifbuiltInistrue;
    • ifupdateModeA value ofDEFAULT=0, will beapp/build/intermediates/qigsaw/split-outputs/apks/debug/*.apkCopy toapp/build/intermediates/merged_assets/debug/out/qigsaw/*.zip;
    • ifupdateModeA value ofDEFAULT! = 0To determine thefeatureIs it inupdateSplits;
      • If soapp/build/intermediates/qigsaw/split-outputs/apks/debug/*.apkCopy toapp/build/intermediates/merged_assets/debug/out/qigsaw/*.zip;
      • If not,app/build/intermediates/qigsaw/old-apk/target-files/debug/assets/qigsaw/*.zipCopy toapp/build/intermediates/merged_assets/debug/out/qigsaw/*.zip;

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;
  • TinkerHot modification related operation;

The article here is all about the end, if there are other need to exchange can leave a message oh ~! ~!