Android——Qigsaw 源码分析 加载过程

Posted 化作孤岛的瓜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android——Qigsaw 源码分析 加载过程相关的知识,希望对你有一定的参考价值。

前言

在初始化流程结束之后,本章节主要是继续按执行顺序,分析加载流程。

目录

前言

加载过程

1. 预加载 (SplitLoadManagerImpl)

1.1 SplitLoadManagerImpl.loadInstalledSplitsInternal

1.2 SplitLoadManagerImpl.createInstalledSplitFileIntents

1.3 SplitLoadManagerImpl.createLastInstalledSplitFileIntent

1.4 预加载过程总结

2. 加载 (SplitLoadManagerImpl)

2.1 SplitLoadManagerImpl.createSplitLoadTask

2.2 SplitLoadTask

2.3 SplitLoadHandler.loadSplits()

2.4 加载过程总结


加载过程

加载过程依靠SplitLoadManager插件加载管理器实现,SplitLoadManagerService维护了实现加载功能的SplitLoadManager实例,其真正实现类为SplitLoadManagerImpl。

1. 预加载 (SplitLoadManagerImpl)

上一节在Qigsaw.onApplicationCreated()的最后,

调用了Qigsaw.preloadInstalledSplits(Arrays.asList(QigsawConfig.DYNAMIC_FEATURES));

SplitLoadManagerService.getInstance().preloadInstalledSplits(splitNames),这里传入的splitNames就是QigsawConfig里配置的Split工程名。

调用了Split加载服务管理类的预加载方法,然后看到SplitLoadManagerImpl的preloadInstalledSplits方法:

    @Override
    public void preloadInstalledSplits(Collection<String> splitNames) 
        if (!qigsawMode) 
            return;
        
        if (isProcessAllowedToWork()) 
            loadInstalledSplitsInternal(splitNames);
        
    

判断了是否是Qigsaw工程,以及包名验证,然后调用loadInstalledSplitsInternal:

1.1 SplitLoadManagerImpl.loadInstalledSplitsInternal

private void loadInstalledSplitsInternal(Collection<String> splitNames) 
        SplitInfoManager manager = SplitInfoManagerService.getInstance();
        if (manager == null) 
            SplitLog.w(TAG, "Failed to get SplitInfoManager instance, have you invoke Qigsaw#install(...) method?");
            return;
        
        Collection<SplitInfo> splitInfoList;
        if (splitNames == null) 
            splitInfoList = manager.getAllSplitInfo(getContext());
         else 
            splitInfoList = manager.getSplitInfos(getContext(), splitNames);
        
        if (splitInfoList == null || splitInfoList.isEmpty()) 
            SplitLog.w(TAG, "Failed to get Split-Info list!");
            return;
        
        //main process start to uninstall splits, other processes don't load pending uninstall splits.
        List<Intent> splitFileIntents = createInstalledSplitFileIntents(splitInfoList);
        if (splitFileIntents.isEmpty()) 
            SplitLog.w(TAG, "There are no installed splits!");
            return;
        
        createSplitLoadTask(splitFileIntents, null).run();
    

首先通过SplitInfoManagerService的实现类获取插件信息SplitInfo的集合splitInfoList.

(SplitInfoManagerService 获取插件信息主要是通过读取qigsaw/qigsaw_1.0.0_1.0.0.json中的内容获取插件信息。)

然后 通过createInstalledSplitFileIntents创建需要执行加载的任务Intent集合:splitFileIntents

1.2 SplitLoadManagerImpl.createInstalledSplitFileIntents

private List<Intent> createInstalledSplitFileIntents(@NonNull Collection<SplitInfo> splitInfoList) 
        List<Intent> splitFileIntents = new ArrayList<>();
        for (SplitInfo splitInfo : splitInfoList) 
            if (canBeWorkedInThisProcessForSplit(splitInfo)) 
                if (getLoadedSplitNames().contains(splitInfo.getSplitName())) 
                    SplitLog.i(TAG, "Split %s has been loaded, ignore it!", splitInfo.getSplitName());
                    continue;
                
                try 
                    SplitInfo.ApkData masterApkData = splitInfo.getApkDataForMaster();
                    SplitInfo.LibData libData = splitInfo.getPrimaryLibData(getContext());
                    String installedMark = splitInfo.obtainInstalledMark(getContext());
                    File splitLibDir = null;
                    if (libData != null) 
                        splitLibDir = SplitPathManager.require().getSplitLibDir(splitInfo, libData.getAbi());
                    
                    boolean libBuiltIn = splitInfo.isBuiltIn() && masterApkData.getUrl().startsWith(SplitConstants.URL_NATIVE);
                    Intent splitFileIntent = createLastInstalledSplitFileIntent(libBuiltIn, installedMark, splitLibDir, splitInfo);
                    if (splitFileIntent != null) 
                        splitFileIntents.add(splitFileIntent);
                    
                    SplitLog.i(TAG, "Split %s will work in process %s, %s it is %s",
                            splitInfo.getSplitName(), currentProcessName,
                            splitFileIntent == null ? "but" : "and",
                            splitFileIntent == null ? "not installed" : "installed");
                 catch (IOException ignored) 

                
             else 
                SplitLog.i(TAG, "Split %s do not need work in process %s", splitInfo.getSplitName(), currentProcessName);
            
        
        return splitFileIntents;
    

首先校验当前包名是否符合Split的workProcess,然后再遍历需要加载的slitInfo,过滤已经加载过的。

获取masterApkData,json文件中定义的apk信息:

"apkData": [
			"abi": "master",
			"url": "assets://qigsaw/java-master.zip",
			"md5": "53a9fa31a54cddaf6dbc196be2255acd",
			"size": 12822
		]

libData(lib包信息)

"libData": [
			"abi": "x86",
			"jniLibs": [
				"name": "libhello-jni.so",
				"md5": "960b507b9dd9d82b9b17b7912d0b7529",
				"size": 5644
			]
		

libBuiltIn:是否为native工程。

然后把所有的参数都传给createLastInstalledSplitFileIntent方法(具体进一步生成Intent)

1.3 SplitLoadManagerImpl.createLastInstalledSplitFileIntent

该方法代码比较长就不贴了。可以边看代码边看本文。

首先获取部分File类型参数splitName,splitDir

markFile(分包标记文件/data/user/0/com.iqiyi.qigsaw.sample/app_qigsaw/1.0.0_a8414bd/java/1.1@1/53a9fa31a54cddaf6dbc196be2255acd.0)

specialMarkFile(特殊分包标记文件/data/user/0/com.iqiyi.qigsaw.sample/app_qigsaw/1.0.0_a8414bd/java/1.1@1/53a9fa31a54cddaf6dbc196be2255acd.0.ov,后面多了ov后缀,为了特殊兼容oppo,vivo手机)

以及splitApk,如果是native工程(libBuiltIn == true),则路径为:

/data/app/~~1qu9MQhn4Y836FdrHVHEqA==/com.iqiyi.qigsaw.sample-w-kwmFjt3MHzOHuoR5EMEA==/lib/arm64/libsplit_java.so

是普通工程则路径为:

/data/user/0/com.iqiyi.qigsaw.sample/app_qigsaw/1.0.0_a8414bd/java/1.1@1/java-master.apk

然后对ov手机做了特殊处理,检查oat文件是否为ELF格式,如果是的话尝试去创建markFile文件,否则删掉oat文件。(存疑)

接下来判断标记文件是否存在,如果存在则开始构建分包Intent。依次赋值Intent参数:

  • SplitConstants.KET_NAME:插件Split名字
  • SplitConstants.KEY_APK:插件apk路径
  • SplitConstants.KEY_DEX_OPT_DIR:插件optimized dex路径
  • SplitConstants.KEY_NATIVE_LIB_DIR:插件native lib路径
  • SplitConstants.KEY_ADDED_DEX:插件已添加的 dev 路径

拿到Intent之后,回到loadInstalledSplitsInternal方法中,会判断splitFileIntents,如果splitFileIntents不为空,则会直接执行SplitLoadManagerImpl的加载方法:

createSplitLoadTask(splitFileIntents, null).run()。

1.4 预加载过程总结

预加载的过程主要是创建一个加载的Intent,里面包含了所需的各种参数。

2. 加载 (SplitLoadManagerImpl)

从createSplitLoadTask(splitFileIntents, null).run()开始分析真正的加载过程

2.1 SplitLoadManagerImpl.createSplitLoadTask

createSplitLoadTask方法有两个调用路径:

第一个是前文中的,QigsawApplication初始化的时候调用preloadInstalledSplits,最终执行加载任务

第二个是在SplitLoadSessionTask的run方法中,执行加载,调用链路为SplitLoadSessionTask ← SplitSessionLoaderImpl.load() ← SplitInstallListenerRegistry.onReceived()

通过SplitInstallListenerRegistry接收action为"com.iqiyi.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService"的动态广播触发加载任务,

该广播有两个发出链路,一个是SplitInstallService.onBinderDied()在安装服务的bundler销毁的时候触发,

另一个是SplitInstallSessionManagerImp.emitSessionState(),在安装器SplitApkInstaller中响应时机调用

然后回到createSplitLoadTask方法本身:

 @Override
    public Runnable createSplitLoadTask(List<Intent> splitFileIntents, @Nullable OnSplitLoadListener loadListener) 
        List<Intent> filterSplitFileIntentList = filterIntentsCanWorkInThisProcess(splitFileIntents);
        if (filterSplitFileIntentList.isEmpty()) 
            return new SkipSplitLoadTaskImpl();
        
        if (splitLoadMode() == SplitLoad.MULTIPLE_CLASSLOADER) 
            return new SplitLoadTaskImpl(this, filterSplitFileIntentList, loadListener);
         else 
            return new SplitLoadTaskImpl2(this, filterSplitFileIntentList, loadListener);
        
    

首先根据WorkProcess做了过滤,然后判断是否是单classLoader加载模式执行不同的Task,那么SplitLoadTaskImpl和SplitLoadTaskImpl2有什么区别呢?

多classLoader模式下,SplitLoadTaskImpl是为每一个插件都创建了一个SplitDexClassLoader,并暂存在SplitApplicationLoaders中。

SplitLoadTaskImpl2是直接使用同一个SplitDexClassLoader进行加载。它们都是继承自SplitLoadTask

2.2 SplitLoadTask

SplitLoadTask是具体执行加载任务的task。

SplitLoadTask.run():

判断当前线程,如果是主线程,直接执行loadHandler.loadSplitsSync(this),否则post到主线程执行,并阻塞当前异步线程。

SplitLoadHandler.loadSplitsSync() 然后执行SplitLoadHandler.loadSplits()。

2.3 SplitLoadHandler.loadSplits()

具体的加载逻辑方法。

1.创建一个临时变量集合,Set<Split> loadedSpits = new HashSet<>() 用来储存等会加载出来的插件,Split类顾名思义就是插件类:

final class Split 

    final String splitName;

    final String splitApkPath;

    Split(String splitName, String splitApkPath) 
        this.splitName = splitName;
        this.splitApkPath = splitApkPath;
    

    @NonNull
    @Override
    public String toString() 
        return "" + splitName + "," + splitApkPath + "";
    

包含了插件名和插件apk路径

2.遍历传入的intent集合splitFileIntents

3.分别解析前文1.3中提到的各种intent参数,apkpath,nativeLibPath,addedDexPaths等

结合前文Intent传递过来的各种参数,创建classLoader:

classLoader = splitLoader.loadCode(splitName,
                        addedDexPaths, dexOptPath == null ? null : new File(dexOptPath),
                        nativeLibPath == null ? null : new File(nativeLibPath),
                        info.getDependencies()

4.创建classLoader → SplitLoader.load() → SplitLoaderImpl.load() → SplitDexClassLoader.create()

SplitApplicationLoaders会存储所有split中的cl(classloader),其中单个的cl为SplitDexClassLoader

SplitDexClassLoader创建的时候会收集所有其他split中合格的cl,然后在自身findClass(或者lib,res)方法内部找不到的时候遍历它们,实现了兜底机制。

创建的时候会执行SplitUnKnownFileTypeDexLoader.loadDex(this, dexPaths, optimizedDirectory);兼容api21以下版本的so处理。

5.使用activator.createSplitApplication(classLoader, splitName)生成application

调用链路:SplitActivator.createSplitApplication → AABExtension.createApplication → AABExtensionManagerImpl.createApplication()

@Override
    @SuppressLint("PrivateApi")
    public Application createApplication(ClassLoader classLoader, String splitName) throws AABExtensionException 
        Throwable error = null;
        String applicationName = infoProvider.getSplitApplicationName(splitName);
        if (!TextUtils.isEmpty(applicationName)) 
            try 
                Class<?> appClass = classLoader.loadClass(applicationName);
                return (Application) appClass.newInstance();
             catch (ClassNotFoundException e) 
                error = e;
             catch (InstantiationException e) 
                error = e;
             catch (IllegalAccessException e) 
                error = e;
            
        
        if (error != null) 
            throw new AABExtensionException(error);
        
        return null;
    

通过反射私有api生成application实例。

6.activateSplit(splitName, splitApkPath, application, classLoader) 激活Split插件

splitLoader.loadResources(splitApkPath) : 加载资源

activator.attachSplitApplication(application); 通过aabExtension调用插件application的attach方法

activator.createAndActivateSplitContentProviders(classLoader, splitName) 通过aabExtension创建并激活ContentProviders

这里通过代理的cp:ContentProviderProxy创建真正的cp,ContentProviderProxy重写了attachInfo,保存了原本的名字,并存储到aabExtension中,

在加载的时候createAndActivateSplitContentProviders,通过cl反射原名来实现激活插件中原有的cp。

activator.invokeOnCreateForSplitApplication(application); 通过aabExtension调用插件application的onCreate方法

7.将加载成功的插件加到SplitLoadManager,并回调加载成功。

2.4 加载过程总结

执行加载的过程主要在SplitLoadHandler.loadSplits()中。前面预加载的过程主要是做一些参数的准备工作,并生成一个Intent丢过来,在SplitLoadHandle中进行真正的加载过程,首先是构建application,然后构建插件Split,加载资源并调用插件Application的attach和onCreate方法。

 链接:

Android——Qigsaw 源码分析(一) 编译过程_化身孤岛的瓜-CSDN博客

Android——Qigsaw 源码分析(二) 初始化过程_化身孤岛的瓜-CSDN博客

Android——Qigsaw 源码分析(三) 加载过程_化身孤岛的瓜-CSDN博客

Android——Qigsaw 源码分析(四) 安装过程_化身孤岛的瓜-CSDN博客

以上是关于Android——Qigsaw 源码分析 加载过程的主要内容,如果未能解决你的问题,请参考以下文章

Android——Qigsaw 源码分析 加载过程

Android——Qigsaw 源码分析 加载过程

Android——Qigsaw 源码分析 加载过程

Android——Qigsaw 源码分析 编译过程

Android——Qigsaw 源码分析 编译过程

Android——Qigsaw 源码分析 初始化过程