(1) 发起安装在哪个进程,就在哪个进程安装
Qigsaw 通过 AIDL 进行安装服务,从哪个进程发起,就在哪个进程安装
SplitInstallManagerImpl:
@Override
public Task startInstall(SplitInstallRequest request) {
if (getInstalledModules().containsAll(request.getModuleNames())) {
mMainHandler.post(new SplitInstalledDisposer(this, request));
return Tasks.createTaskAndSetResult(0);
} else {
return mInstallService.startInstall(request.getModuleNames());
}
}
SplitInstallService:
Task startInstall(List moduleNames) {
TaskWrapper taskWrapper = new TaskWrapper<>();
mSplitRemoteManager.bindService(new StartInstallTask(this, taskWrapper, moduleNames, taskWrapper));
return taskWrapper.getTask();
}
StartInstallTask:
@Override
protected void execute() {
try {
mSplitInstallService.mSplitRemoteManager.getIInterface().startInstall(
this.mSplitInstallService.mPackageName,
SplitInstallService.wrapModuleNames(moduleNames),
SplitInstallService.wrapVersionCode(),
new StartInstallCallback(mSplitInstallService, mTask)
);
} catch (RemoteException e) {
this.mTask.setException(new RuntimeException(e));
}
}
AIDL -> SplitInstallService (哪个进程发起就在哪个进程):
@RestrictTo(LIBRARY_GROUP)
public final class SplitInstallService extends Service {
private static final Map sHandlerMap = Collections.synchronizedMap(new HashMap());
ISplitInstallService.Stub mBinder = new ISplitInstallService.Stub() {
@Override
public void startInstall(String packageName, List moduleNames, Bundle versionCode, ISplitInstallServiceCallback callback) {
getHandler(packageName).post(new OnStartInstallTask(callback, moduleNames));
}
@Override
public void cancelInstall(String packageName, int sessionId, Bundle versionCode, ISplitInstallServiceCallback callback) {
getHandler(packageName).post(new OnCancelInstallTask(callback, sessionId));
}
// ...
};
}
OnStartInstallTask:
final class OnStartInstallTask extends DefaultTask {
private final List mModuleNames;
OnStartInstallTask(ISplitInstallServiceCallback callback, List moduleNames) {
super(callback);
this.mModuleNames = moduleNames;
}
@Override
void execute(@NonNull SplitInstallSupervisor supervisor) throws RemoteException {
supervisor.startInstall(mModuleNames, this);
}
@Override
public void onStartInstall(int sessionId, Bundle data) {
super.onStartInstall(sessionId, data);
try {
mCallback.onStartInstall(sessionId, data);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
(2) 下载、内置拷贝逻辑
SplitInstallSupervisorImpl:
@Override
public void startInstall(List moduleNames, Callback callback) {
List moduleNameList = unBundleModuleNames(moduleNames);
int errorCode = onPreInstallSplits(moduleNameList);
if (errorCode != SplitInstallInternalErrorCode.NO_ERROR) {
callback.onError(bundleErrorCode(errorCode));
} else {
List needInstallSplits = getNeed2BeInstalledSplits(moduleNameList);
//check network status (网络不可用时,如果安装的 splits 不全是内置,则回调失败
if (!isAllSplitsBuiltIn(needInstallSplits) && !isNetworkAvailable(appContext)) {
callback.onError(bundleErrorCode(SplitInstallInternalErrorCode.NETWORK_ERROR));
return;
}
startDownloadSplits(moduleNameList, needInstallSplits, callback);
}
}
private void startDownloadSplits(final List moduleNames,
final List needInstallSplits,
final Callback callback) {
// 当前活跃的安装 session 数超过限制,回调失败
if (sessionManager.isActiveSessionsLimitExceeded()) {
callback.onError(bundleErrorCode(SplitInstallInternalErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED));
return;
}
// 创建 session id, 管理全局安装状态
int sessionId = createSessionId(needInstallSplits);
// ...
try {
//1.如果有内置的 splits 则先拷贝
//2.校验签名
//3.创建需要下载的 request
long[] result = onPreDownloadSplits(needInstallSplits);
//wait util builtin splits are copied completely.
callback.onStartInstall(sessionId, null);
sessionManager.setSessionState(sessionId, sessionState);
//calculate bytes to download
long totalBytesToDownload = result[0];
long realTotalBytesNeedToDownload = result[1];
sessionState.setTotalBytesToDownload(totalBytesToDownload);
StartDownloadCallback downloadCallback = new StartDownloadCallback(splitInstaller, sessionId, sessionManager, needInstallSplits);
// ...
} catch (IOException e) {
//copy local split file failed!
callback.onError(bundleErrorCode(SplitInstallInternalErrorCode.BUILTIN_SPLIT_APK_COPIED_FAILED));
}
}
private long[] onPreDownloadSplits(Collection splitInfoList) throws IOException {
long totalBytesToDownload = 0L;
long realTotalBytesNeedToDownload = 0L;
for (SplitInfo splitInfo : splitInfoList) {
File splitDir = SplitPathManager.require().getSplitDir(splitInfo);
String fileName = splitInfo.getSplitName() + SplitConstants.DOT_APK;
File splitApk;
if (splitInfo.getUrl().startsWith(SplitConstants.URL_NATIVE)) {
splitApk = new File(appContext.getApplicationInfo().nativeLibraryDir, System.mapLibraryName(SplitConstants.SPLIT_PREFIX + splitInfo.getSplitName()));
} else {
splitApk = new File(splitDir, fileName);
}
SplitDownloadPreprocessor processor = new SplitDownloadPreprocessor(splitDir, splitApk);
try {
processor.load(appContext, splitInfo, verifySignature);
} finally {
FileUtil.closeQuietly(processor);
}
//calculate splits total download size.
totalBytesToDownload = totalBytesToDownload + splitInfo.getSize();
if (!splitApk.exists()) {
realTotalBytesNeedToDownload = realTotalBytesNeedToDownload + splitInfo.getSize();
}
}
return new long[]{totalBytesToDownload, realTotalBytesNeedToDownload};
}
}
SplitDownloadPreprocessor:
void load(Context context, SplitInfo info, boolean verifySignature) throws IOException {
if (!cacheLock.isValid()) {
throw new IllegalStateException("FileCheckerAndCopier was closed");
} else {
String splitName = info.getSplitName();
// 内置的 split 拷贝、校验
if (info.isBuiltIn()) {
boolean builtInSplitInAssets = info.getUrl().startsWith(SplitConstants.URL_ASSETS);
if (!splitApk.exists()) {
//copy build in spilt apk file to splitDir
if (builtInSplitInAssets) {
copyBuiltInSplit(context, info);
}
//check size
if (!verifySplitApk(context, info, verifySignature)) {
throw new IOException(String.format("Failed to check built-in split %s, it may be corrupted", splitName));
}
} else {
if (!verifySplitApk(context, info, verifySignature)) {
if (builtInSplitInAssets) {
copyBuiltInSplit(context, info);
}
if (!verifySplitApk(context, info, verifySignature)) {
throw new IOException(String.format("Failed to check built-in split %s, it may be corrupted", splitApk.getAbsolutePath()));
}
}
}
} else {
if (splitApk.exists()) {
verifySplitApk(context, info, verifySignature);
} else {
SplitLog.v(TAG, " split %s is not downloaded", splitName);
}
}
}
}
如果是需要下载的 split, 则调用接入方配置的下载器下载,下载完成后进入 StartDownloadCallback#onCompleted() (注意:内置的 split 拷贝完成也会进入此回调)
final class StartDownloadCallback implements DownloadCallback {
// ...
@Override
public void onCompleted() {
sessionManager.changeSessionState(sessionId, SplitInstallInternalSessionStatus.DOWNLOADED);
broadcastSessionStatusChange();
onInstall();
}
private void onInstall() {
installer.install(sessionId, splitInfoList);
}
}
(3) 核心安装逻辑
SplitInstaller:
@Override
public InstallResult install(boolean startInstall, SplitInfo info) throws InstallException {
File splitDir = SplitPathManager.require().getSplitDir(info);
File sourceApk;
if (info.isBuiltIn() && info.getUrl().startsWith(SplitConstants.URL_NATIVE)) {
sourceApk = new File(appContext.getApplicationInfo().nativeLibraryDir, System.mapLibraryName(SplitConstants.SPLIT_PREFIX + info.getSplitName()));
} else {
sourceApk = new File(splitDir, info.getSplitName() + SplitConstants.DOT_APK);
}
// ...
// 验证签名
if (verifySignature) {
SplitLog.d(TAG, "Need to verify split %s signature!", sourceApk.getAbsolutePath());
verifySignature(sourceApk);
}
// 校验 md5
checkSplitMD5(sourceApk, info.getMd5());
File splitLibDir = null;
if (isLibExtractNeeded(info)) {
extractLib(info, sourceApk);
splitLibDir = SplitPathManager.require().getSplitLibDir(info);
}
List addedDexPaths = null;
if (info.hasDex()) {
addedDexPaths = new ArrayList<>();
addedDexPaths.add(sourceApk.getAbsolutePath());
if (!isVMMultiDexCapable()) {
if (isMultiDexExtractNeeded(info)) {
addedDexPaths.addAll(extractMultiDex(info, sourceApk));
}
}
}
// 安装标记文件
File markFile = SplitPathManager.require().getSplitMarkFile(info);
if (addedDexPaths != null) {
String dexPath = TextUtils.join(File.pathSeparator, addedDexPaths);
File optimizedDirectory = SplitPathManager.require().getSplitOptDir(info);
String librarySearchPath = splitLibDir == null ? null : splitLibDir.getAbsolutePath();
// oat 优化
if (!markFile.exists()) {
try {
new DexClassLoader(dexPath, optimizedDirectory.getAbsolutePath(), librarySearchPath, SplitInstallerImpl.class.getClassLoader());
} catch (Throwable error) {
throw new InstallException(
SplitInstallError.CLASSLOADER_CREATE_FAILED,
error);
}
}
// ...
// 第一次安装创建标记文件
boolean firstInstalled = createInstalledMark(markFile);
return new InstallResult(info.getSplitName(), sourceApk, addedDexPaths, firstInstalled);
}
安装后,data 文件目录如下:
/data/$pkg/app_qigsaw/$qigsaw_id/$splitName/$version目录:
- splitName.apk
- /oat
- /arm64
- splitName.odex
- splitName.vdex
- splitName.apk.cur.prof
- splitName.apk.prof
- markFile(md5, 安装标记文件, 安装后才会创建)
eg:
(4) 发送安装成功广播,进行 split 加载
安装成功后会发送安装成功广播 SplitInstallSessionManagerImpl:
@Override
public void emitSessionState(SplitInstallInternalSessionState sessionState) {
Bundle bundle = SplitInstallInternalSessionState.transform2Bundle(sessionState);
Intent intent = new Intent();
intent.putExtra("session_state", bundle);
intent.setPackage(mPackageName);
intent.setAction("com.iqiyi.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService");
mContext.sendBroadcast(intent);
}
收到广播,进行加载:
final class SplitInstallListenerRegistry extends StateUpdateListenerRegister {
final Handler mMainHandler;
private final SplitSessionLoader mLoader;
SplitInstallListenerRegistry(Context context) {
this(context, SplitSessionLoaderSingleton.get());
}
private SplitInstallListenerRegistry(Context context, SplitSessionLoader loader) {
super(new PlayCore("SplitInstallListenerRegistry"), new IntentFilter("com.iqiyi.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService"), context);
this.mMainHandler = new Handler(Looper.getMainLooper());
this.mLoader = loader;
}
@Override
protected void onReceived(Intent intent) {
SplitInstallSessionState sessionState = SplitInstallSessionState.createFrom(intent.getBundleExtra("session_state"));
this.playCore.info("ListenerRegistryBroadcastReceiver.onReceive: %s", sessionState);
if (sessionState.status() == 10 && mLoader != null) {
mLoader.load(sessionState.splitFileIntents, new SplitSessionStatusChanger(this, sessionState));
} else {
notifyListeners(sessionState);
}
}
}
这边细节代码就不贴了,大体流程是,更新配置文件后,App 第二次启动时,版本信息会读取到内存中,然后删除之前的旧版本缓存数据。进入更新的 split 页面时,重新下载安装最新版本的 split.