Android——Qigsaw 源码分析 加载过程
Posted 化作孤岛的瓜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android——Qigsaw 源码分析 加载过程相关的知识,希望对你有一定的参考价值。
前言
在初始化流程结束之后,本章节主要是继续按执行顺序,分析加载流程。
目录
1.1 SplitLoadManagerImpl.loadInstalledSplitsInternal
1.2 SplitLoadManagerImpl.createInstalledSplitFileIntents
1.3 SplitLoadManagerImpl.createLastInstalledSplitFileIntent
2.1 SplitLoadManagerImpl.createSplitLoadTask
2.3 SplitLoadHandler.loadSplits()
加载过程
加载过程依靠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 源码分析 加载过程的主要内容,如果未能解决你的问题,请参考以下文章