熱修復(fù)框架 - Tinker patch合成流程

經(jīng)過(guò)TinkerInstaller.install 安裝之后泌枪,Tinker相關(guān)初始化工作也都做好了塑陵,萬(wàn)事俱備等待手動(dòng)執(zhí)行合成patch包垫桂。

一咙冗、合成patch包

主動(dòng)觸發(fā):

TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), patch);//patchs是diff patch路徑

TinkerInstaller.java

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

DefaultPatchListener.java

public int onPatchReceived(String path) {
    final File patchFile = new File(path);
   final String patchMD5 = SharePatchFileUtil.getMD5(patchFile);
   final int returnCode = patchCheck(path, patchMD5);
   if (returnCode == ShareConstants.ERROR_PATCH_OK) {
        runForgService();
        //補(bǔ)丁校驗(yàn)成功孵滞,則啟動(dòng)服務(wù)來(lái)處理patch合成中捆,這個(gè)服務(wù)是單獨(dú)起了patch進(jìn)程的
       TinkerPatchService.runPatchService(context, path);
   } else {
        Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
   }
    return returnCode;
}

TinkerPatchService.java

public static void runPatchService(final Context context, final String path) {
    TinkerLog.i(TAG, "run patch service...");
   Intent intent = new Intent(context, TinkerPatchService.class);
   intent.putExtra(PATCH_PATH_EXTRA, path);
   intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
   try {
        context.startService(intent);//啟動(dòng)當(dāng)前服務(wù)
   } catch (Throwable thr) {
        TinkerLog.e(TAG, "run patch service fail, exception:" + thr);
   }
}

@Override
protected void onHandleIntent(Intent intent) {
    increasingPriority();//提升服務(wù)優(yōu)先級(jí)為前臺(tái),減少內(nèi)存緊張時(shí)候被lmk kill的風(fēng)險(xiǎn)
   doApplyPatch(this, intent);//服務(wù)執(zhí)行
}

private static void doApplyPatch(Context context, Intent intent) {
    // Since we may retry with IntentService, we should prevent
   // racing here again.
   if (!sIsPatchApplying.compareAndSet(false, true)) {
        TinkerLog.w(TAG, "TinkerPatchService doApplyPatch is running by another runner.");
       return;
   }
    Tinker tinker = Tinker.with(context);
   tinker.getPatchReporter().onPatchServiceStart(intent);//初始化重試相關(guān)信息配置
   if (intent == null) {
        TinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring.");
       return;
   }
    String path = getPatchPathExtra(intent);
   if (path == null) {
        TinkerLog.e(TAG, "TinkerPatchService can't get the path extra, ignoring.");
       return;
   }
    File patchFile = new File(path);
   long begin = SystemClock.elapsedRealtime();
   boolean result;
   long cost;
   Throwable e = null;
   PatchResult patchResult = new PatchResult();
   try {
        if (upgradePatchProcessor == null) {
            throw new TinkerRuntimeException("upgradePatchProcessor is null.");
       }
       //合成patch
        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;
   //通過(guò)AbstractResultService實(shí)現(xiàn)類來(lái)處理返回結(jié)果坊饶,比如定制一些合成完成之后的退出機(jī)制泄伪。
   AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
   sIsPatchApplying.set(false);
}

合成patch看upgradePatchProcessor.tryPatch

UpgradePatch.java

@Override
public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
   Tinker manager = Tinker.with(context);
   final File patchFile = new File(tempPatchPath);
   //tinker熱修復(fù)enbale
   if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) {
       TinkerLog.e(TAG, "UpgradePatch tryPatch:patch is disabled, just return");
       return false;
   }
   //patch文件合法
   if (!SharePatchFileUtil.isLegalFile(patchFile)) {
       TinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found, just return");
       return false;
   }
   //check the signature, we should create a new checker
   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;
   }
   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;
   TinkerLog.i(TAG, "UpgradePatch tryPatch:patchMd5:%s", patchMd5);
   //check ok, we can real recover a new patch
   final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();
   //info.lock 文件
   File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);
   //patch.info 文件
   File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory);
   final Map<String, String> pkgProps = signatureCheck.getPackagePropertiesIfPresent();
   if (pkgProps == null) {
       TinkerLog.e(TAG, "UpgradePatch packageProperties is null, do we process a valid patch apk ?");
       return false;
   }
   final String isProtectedAppStr = pkgProps.get(ShareConstants.PKGMETA_KEY_IS_PROTECTED_APP);
   final boolean isProtectedApp = (isProtectedAppStr != null && !isProtectedAppStr.isEmpty() && !"0".equals(isProtectedAppStr));
   SharePatchInfo oldInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
   //it is a new patch, so we should not find a exist
   SharePatchInfo newInfo;
   //already have patch
   if (oldInfo != null) {
       if (oldInfo.oldVersion == null || oldInfo.newVersion == null || oldInfo.oatDir == 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;
       }
       final boolean usingInterpret = oldInfo.oatDir.equals(ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH);
       if (!usingInterpret && !ShareTinkerInternals.isNullOrNil(oldInfo.newVersion) && oldInfo.newVersion.equals(patchMd5) && !oldInfo.isRemoveNewVersion) {
           TinkerLog.e(TAG, "patch already applied, md5: %s", patchMd5);
           // Reset patch apply retry count to let us be able to reapply without triggering
           // patch apply disable when we apply it successfully previously.
           UpgradePatchRetry.getInstance(context).onPatchResetMaxCheck(patchMd5);
           return true;
       }
       // if it is interpret now, use changing flag to wait main process
       final String finalOatDir = usingInterpret ? ShareConstants.CHANING_DEX_OPTIMIZE_PATH : oldInfo.oatDir;
       newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, isProtectedApp, false, Build.FINGERPRINT, finalOatDir, false);
   } else {
       newInfo = new SharePatchInfo("", patchMd5, isProtectedApp, false, Build.FINGERPRINT, ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH, false);
   }
   // it is a new patch, we first delete if there is any files
   // don't delete dir for faster retry
   // SharePatchFileUtil.deleteDir(patchVersionDirectory);
   final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5);
   final String patchVersionDirectory = patchDirectory + "/" + patchName;
   TinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory);
   //copy file
   //把補(bǔ)丁包復(fù)制到/data/data/com.stan.tinkersdkdemo/tinker/patch-8b79c8cc/patch-8b79c8cc.apk
   File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5));
   try {
       // check md5 first
       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) {
       TinkerLog.e(TAG, "UpgradePatch tryPatch:copy patch file fail from %s to %s", patchFile.getPath(), destPatchFile.getPath());
       manager.getPatchReporter().onPatchTypeExtractFail(patchFile, destPatchFile, patchFile.getName(), ShareConstants.TYPE_PATCH_FILE);
       return false;
   }
   //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
   //合成dex
   if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
       TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
       return false;
   }

   //合成arkhot 針對(duì)方舟os
   if (!ArkHotDiffPatchInternal.tryRecoverArkHotLibrary(manager, signatureCheck,
           context, patchVersionDirectory, destPatchFile)) {
       return false;
   }
   //合成so
   if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
       TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
       return false;
   }

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

   //對(duì)dex進(jìn)行opt優(yōu)化
   // check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oat to interpreted
   if (!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) {
       TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, check dex opt file failed");
       return false;
   }

   //把結(jié)果重新寫入到 patch.info
   if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, patchInfoLockFile)) {
       TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed");
       manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion);
       return false;
   }

   // Reset patch apply retry count to let us be able to reapply without triggering
   // patch apply disable when we apply it successfully previously.
   UpgradePatchRetry.getInstance(context).onPatchResetMaxCheck(patchMd5);
   TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok");
   return true;
}

這里很簡(jiǎn)單,就是把你存放的patch差分包復(fù)制到/data/data/com.stan.tinkersdkdemo/tinker/patch-8b79c8cc/patch-8b79c8cc.apk中匿级,然后分別執(zhí)行dex蟋滴、so 、resources與基準(zhǔn)包的合成痘绎。

dex脓杉、so 、resources的差分與合成有時(shí)候的話后續(xù)再單獨(dú)開(kāi)篇分析下简逮。
參考:

https://blog.csdn.net/lmj623565791/article/details/60874334

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載球散,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末散庶,一起剝皮案震驚了整個(gè)濱河市蕉堰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悲龟,老刑警劉巖屋讶,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異须教,居然都是意外死亡皿渗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門轻腺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)乐疆,“玉大人,你說(shuō)我怎么就攤上這事贬养〖吠粒” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵误算,是天一觀的道長(zhǎng)仰美。 經(jīng)常有香客問(wèn)我,道長(zhǎng)儿礼,這世上最難降的妖魔是什么咖杂? 我笑而不...
    開(kāi)封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮蚊夫,結(jié)果婚禮上诉字,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好奏窑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布导披。 她就那樣靜靜地躺著屈扎,像睡著了一般埃唯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹰晨,一...
    開(kāi)封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天墨叛,我揣著相機(jī)與錄音,去河邊找鬼模蜡。 笑死漠趁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的忍疾。 我是一名探鬼主播闯传,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼卤妒!你這毒婦竟也來(lái)了甥绿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤则披,失蹤者是張志新(化名)和其女友劉穎共缕,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體士复,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡图谷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了阱洪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片便贵。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖冗荸,靈堂內(nèi)的尸體忽然破棺而出嫉沽,到底是詐尸還是另有隱情,我是刑警寧澤俏竞,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布绸硕,位于F島的核電站,受9級(jí)特大地震影響魂毁,放射性物質(zhì)發(fā)生泄漏玻佩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一席楚、第九天 我趴在偏房一處隱蔽的房頂上張望咬崔。 院中可真熱鬧,春花似錦、人聲如沸垮斯。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)兜蠕。三九已至扰肌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熊杨,已是汗流浹背曙旭。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晶府,地道東北人桂躏。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像川陆,于是被迫代替她去往敵國(guó)和親剂习。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345