【Qigsaw系列02】Qigsaw如何安装、更新插件

目录

  • 1. 插件安装过程
  • 2. 插件更新过程

 

1. 插件安装过程

【Qigsaw系列02】Qigsaw如何安装、更新插件_第1张图片

(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:

【Qigsaw系列02】Qigsaw如何安装、更新插件_第2张图片

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

 

2. 插件更新过程

【Qigsaw系列02】Qigsaw如何安装、更新插件_第3张图片

这边细节代码就不贴了,大体流程是,更新配置文件后,App 第二次启动时,版本信息会读取到内存中,然后删除之前的旧版本缓存数据。进入更新的 split 页面时,重新下载安装最新版本的 split.

 

 

你可能感兴趣的:(分享篇,android,qigsaw,AAB)