源碼閱讀--騰訊Tinker熱修復框架

先要配置這麼一段話:後面會談到爲什麼以及如何改變這些默認設置

    public static void installTinker(ApplicationLike appLike) {
        //or you can just use DefaultLoadReporter
        LoadReporter loadReporter = new DefaultLoadReporter(appLike.getApplication());
        //or you can just use DefaultPatchReporter
        PatchReporter patchReporter = new DefaultPatchReporter(appLike.getApplication());
        //or you can just use DefaultPatchListener
        PatchListener patchListener = new DefaultPatchListener(appLike.getApplication());
        //you can set your own upgrade patch if you need
        AbstractPatch upgradePatchProcessor = new UpgradePatch();

        TinkerInstaller.install(appLike,
                loadReporter, patchReporter, patchListener,
                TinkerResultService.class, upgradePatchProcessor);
        isInstalled = true;
    }

通過下面這句話啓動熱修復

TinkerInstaller.onReceiveUpgradePatch(mContext, saveFileName);

看一下接下來的函數調用:

public static void onReceiveUpgradePatch(Context context, String patchLocation) {
    Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}

public int onPatchReceived(String path) {
    int returnCode = patchCheck(path);
    if (returnCode == ShareConstants.ERROR_PATCH_OK) {
        TinkerPatchService.runPatchService(context, path);
    } else {
        Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
    }
    return returnCode;
}

    public static void runPatchService(Context context, String path) {
        try {
            Intent intent = new Intent(context, TinkerPatchService.class);//注意這裏,是一個IntentService,所以調用onHandleIntent
            intent.putExtra(PATCH_PATH_EXTRA, path);
            intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
            context.startService(intent);
        } catch (Throwable throwable) {
            TinkerLog.e(TAG, "start patch service fail, exception:" + throwable);
        }
    }

這裏的onHandleIntent是很重要的。所以拿出來解釋

    //代碼有縮減。重要的函數已經通過*標註
    protected void onHandleIntent(Intent intent) {
        final Context context = getApplicationContext();
        Tinker tinker = Tinker.with(context);
        tinker.getPatchReporter().onPatchServiceStart(intent);//*************************************

        String path = getPatchPathExtra(intent);//獲取存放在intent裏面的PATCH_PATH_EXTRA的值,說白了就是補丁的路徑

        File patchFile = new File(path);

        long begin = SystemClock.elapsedRealtime();
        boolean result;
        long cost;
        Throwable e = null;

        PatchResult patchResult = new PatchResult();
        try {
            result = upgradePatchProcessor.tryPatch(context, path, patchResult);//*************************************
        } catch (Throwable throwable) {
            e = throwable;
            result = false;
            tinker.getPatchReporter().onPatchException(patchFile, e);
        }

        cost = SystemClock.elapsedRealtime() - begin;
        tinker.getPatchReporter().onPatchResult(patchFile, result, cost);//*************************************

        patchResult.isSuccess = result;
        patchResult.rawPatchFilePath = path;
        patchResult.costTime = cost;
        patchResult.e = e;

        AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));//*************************************
    }

看到tinker.getPatchReporter()了麼?就是我們一開始配置的DefaultPatchReporter。但是會發現onPatchServiceStart,onPatchResult就打了點Log。如果覺得還不夠,我們可以實現自定義接口—只要繼承DefaultPatchReporter並且實現他的幾個接口(比如彈出提示語增加界面友好度)

好了,接下來看tryPatch和runResultService函數

(1)tryPatch

upgradePatchProcessor其實就是AbstractPatch。最終在UpgradePatch中實現

public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
        Tinker manager = Tinker.with(context);

        final File patchFile = new File(tempPatchPath);


        //檢查簽名
        ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context);

        int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck);
        if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchPackageCheckFail");
            manager.getPatchReporter().onPatchPackageCheckFail(patchFile, returnCode);
            return false;
        }

        //it is a new patch, so we should not find a exist
        SharePatchInfo oldInfo = manager.getTinkerLoadResultIfPresent().patchInfo;
        String patchMd5 = SharePatchFileUtil.getMD5(patchFile);

        if (patchMd5 == null) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:patch md5 is null, just return");
            return false;
        }

        //use md5 as version
        patchResult.patchVersion = patchMd5;

        SharePatchInfo newInfo;

        //already have patch
        if (oldInfo != null) {
            if (oldInfo.oldVersion == null || oldInfo.newVersion == null) {
                TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchInfoCorrupted");
                manager.getPatchReporter().onPatchInfoCorrupted(patchFile, oldInfo.oldVersion, oldInfo.newVersion);
                return false;
            }

            if (!SharePatchFileUtil.checkIfMd5Valid(patchMd5)) {
                TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail md5 %s is valid", patchMd5);
                manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5);
                return false;
            }
            newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, Build.FINGERPRINT);
        } else {
            newInfo = new SharePatchInfo("", patchMd5, Build.FINGERPRINT);
        }

        //check ok, we can real recover a new patch
        final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();

        TinkerLog.i(TAG, "UpgradePatch tryPatch:patchMd5:%s", patchMd5);

        final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5);

        final String patchVersionDirectory = patchDirectory + "/" + patchName;

        TinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory);

        //it is a new patch, we first delete if there is any files
        //don't delete dir for faster retry
//        SharePatchFileUtil.deleteDir(patchVersionDirectory);

        //copy file
        File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5));

        try {
            // 檢查MD5
            if (!patchMd5.equals(SharePatchFileUtil.getMD5(destPatchFile))) {
                SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);
                TinkerLog.w(TAG, "UpgradePatch copy patch file, src file: %s size: %d, dest file: %s size:%d", patchFile.getAbsolutePath(), patchFile.length(),
                    destPatchFile.getAbsolutePath(), destPatchFile.length());
            }
        } catch (IOException e) {
            ......
        }

        //----------------這裏真正開始修復補丁
        //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
        if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
            return false;
        }

        if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
            return false;
        }

        if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
            return false;
        }

        ......

        return true;
    }

所以熱修復的核心如下:拿到補丁apk,檢查簽名,MD5。然後對dex,library,resource文件分別進行解壓比較覆蓋更新。(至於如何解壓覆蓋安裝的,我只看到一個有zip字眼的類,具體怎麼處理的我也搞不清了)

(2)runResultService

AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
這句話中的getPatchResultExtra返回的是RESULT_CLASS_EXTRA對應的字符串

public static void runResultService(Context context, PatchResult result, String resultServiceClass) {
        try {
            Intent intent = new Intent();
            intent.setClassName(context, resultServiceClass);
            intent.putExtra(RESULT_EXTRA, result);
            context.startService(intent);
        } catch (Throwable throwable) {
            TinkerLog.e(TAG, "run result service fail, exception:" + throwable);
        }
    }

看一下resultServiceClass是啥?
Class

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) {
            TinkerLog.e(TAG, "AbstractResultService received a null intent, ignoring.");
            return;
        }
        PatchResult result = (PatchResult) ShareIntentUtil.getSerializableExtra(intent, RESULT_EXTRA);

        onPatchResult(result);
    }

    public abstract void onPatchResult(PatchResult result);

說白了就是可以寫個Service繼承AbstractResultService來實現onPatchResult。庫裏面有個默認的DefaultTinkerResultService,但是隻有刪除文件。我們可以自己寫一個Service繼承DefaultTinkerResultService來讓整個程序可以繼續進行(一般加載完補丁會有些界面上的調整)

發佈了223 篇原創文章 · 獲贊 23 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章