15

Qigsaw源码学习-Gradle插件解析

 3 years ago
source link: https://zhuanlan.zhihu.com/p/128019964
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Qigsaw源码学习-Gradle插件解析

58同城 Android工程师

Qigsaw项目提供了两个插件一个为application一个为DynamicFeaturePlugin,下面我们详细介绍一下,每个插件都做了什么操作。

v2-9a3b0ac1d52c21ba39c834bfd61532d5_720w.jpg

DynamicFeaturePlugin插件解析

首先我们看一下com.iqiyi.qigsaw.dynamicfeature这个插件,这个插件的使用需要在dynamic-feature的Moudle的build.gralde中添加

apply plugin: 'com.iqiyi.qigsaw.dynamicfeature'

该插件的实现类为QigsawDynamicFeaturePlugin

SplitResourcesLoaderTransform resourcesLoaderTransform
        if (hasQigsawTask(project)) {
            resourcesLoaderTransform = new SplitResourcesLoaderTransform(project)
            SplitLibraryLoaderTransform libraryLoaderTransform = new SplitLibraryLoaderTransform(project)
            android.registerTransform(resourcesLoaderTransform)
            android.registerTransform(libraryLoaderTransform)
        }

如上图所示SplitResourcesLoaderTransform与SplitLibraryLoaderTransform为关键操作类

SplitResourcesLoaderTransform字节码操作

SplitResourcesLoaderTransform类进行的操作是向dynamic-feature构建apk的过程中,向其继承自Activity,Servive,Receiver的类getResources()方法中注入一段代码 SplitInstallHelper.loadResources(this, super.getResources());

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

SplitInstallHelper.loadResources(this, super.getResources());的作用是将所有插件资源路径添加到AssetManager中,这样各个插件就可以访问所有的资源,关键实现代码如下

static Method getAddAssetPathMethod() throws NoSuchMethodException {
            if (addAssetPathMethod == null) {
                addAssetPathMethod = HiddenApiReflection.findMethod(AssetManager.class, "addAssetPath", String.class);
            }
            return addAssetPathMethod;
        }

SplitLibraryLoaderTransform字节码操作

SplitLibraryLoaderTransform类进行的操作是向dynamic-feature构建apk的过程中,创建以 "com.iqiyi.android.qigsaw.core.splitlib." + project.name + "SplitLibraryLoader"的类,具体如下

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

public class javaSplitLibraryLoader {
    public void loadSplitLibrary(String str) {
        System.loadLibrary(str);
    }
}

这个类的作用是啥呢?下面我们来解释一下,你会发现很有趣的。
首先我们要知道两点

  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加载问题
System.loadLibrary(str); 该方法会使用调用方的classloader从中获取so信息并加载,

public static void loadLibrary(String libname) {
       Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}

由于多classloader模式下,每个插件都要各自的classloader,so与dex都在各自的classloader中记录,所以在多classloader模式下, System.loadLibrary应由插件apk各自的classloader调用。具体实现可参考SplitLibraryLoaderHelper类

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

application插件解析

该插件的实现类为QigsawAppBasePlugin,主要是对base apk进行字节码操作和将插件apk信息拷贝到base apk的asset目录下

ComponentInfoCreatorTransform字节码操作

该Transform主要进行了两个操作 :

  1. 读取各个插件apk的Manifest文件,创建ComponentInfo类并将将各个插件apk的Application,Activity,Service,Recevier记录在该类的字段中,字段名称以工程名+组件类型命名,值为各个插件apk包含的组件,如过包含多个用逗号隔开。如下所示
package com.iqiyi.android.qigsaw.core.extension;

public class ComponentInfo {
    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 static final String native_ACTIVITIES = "com.iqiyi.qigsaw.sample.ccode.NativeSampleActivity";
}

2. 为每个provider创建代理类 类名为String providerClassName = 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 {
}

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

为啥这么做呢,因为在app启动时 provider的执行时机是比较靠前的,
Application->attachBaseContext ==>ContentProvider->onCreate ==>Application->onCreate ==>Activity->onCreate
在这个过程中我们的插件apk并没有加载进来,一定会报classnotfound。所以我们将插件apk的provider生成一个代理类,然后替换掉,如果插件没有加载进来,代理类什么也不执行就可以了。很好的解决了我们的问题。

Base APK asset目录文件操作

执行该步骤的task为QigsawAssembleTask,该task的作用是将各个Feature Moudle生成的插件apk转换为zip拷贝到assets目录下,如果插件apk不需要内置则可以上传到服务端。如下图所示。

同时创建json配置文件,该文件格式如下图所示,包含了所以的插件信息,最终base apk通过改配置文件,去加载各个插件apk.

{
    "qigsawId": "1.0.0_2e1a694",
    "appVersionName": "1.0.0",
    "splits": [
      {
            "splitName": "native",
            "url": "assets://native.zip",
            "builtIn": true,
            "size": 18528,
            "version": "1.0@1",
            "md5": "81a0bea018004931ad4c4404fefd5e48",
            "minSdkVersion": 14,
            "dexNumber": 3,
            "libInfo": {
                "abi": "armeabi-v7a",
                "libs": [
                    {
                        "name": "libhello-jni.so",
                        "md5": "4fb62a8855f357c7ba824472f5065029",
                        "size": 13996
                    }
                ]
            }
        }
    ]
}

https://github.com/iqiyi/Qigsaw/
https://www.jianshu.com/p/a4a6ed83483b
https://blog.csdn.net/zhoaya188/article/details/82355965


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK