Tinker源码解析

Tinker

Git项目地址:https://github.com/Tencent/tinker

本例解析tag为v1.9.14.19

一、概述

Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstalling apk.

在这里插入图片描述
tinker作为Android一款热修复框架,实践应用在微信上,其稳定性,兼容性不言而喻;看下官方说明
在这里插入图片描述
上图来自tinker官方,我截了个图,可以看到tinker相比其他热修复框架优势还是非常明显的,作为Android开发有必要探究下起内部实现机制,今天就来扒一扒tinker皮

tinker基本接入步骤直接忽略,下面以tinker-sample-android为例进行逐步解析,既然Tinker是一款免安装针对Android的热补丁修复框架,热修复 必定绕过不二部分

  1. class修复,在Android中也就是dex修复
  2. 资源修复

二、Tinker在启动做了什么?

先看下SampleApplication

image-20220314113732420

这里的SampleApplication是由tinker-android-anno自动生成的,可以看到它且继承了TinkerApplication,且在构造器中传入了一个参数给父类,里面包含了代理类自定义加载器类名

在这里插入图片描述

我们直接看TinkerApplication.attachBaseContext好了

@Override
    protected void attachBaseContext(Context base) {
   
        super.attachBaseContext(base);
        final long applicationStartElapsedTime = SystemClock.elapsedRealtime();
        final long applicationStartMillisTime = System.currentTimeMillis();
        Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this));
        onBaseContextAttached(base, applicationStartElapsedTime, applicationStartMillisTime);
    }
  1. 设置了Tinker异常处理器,当有异常出现时记录堆栈信息并持久化到本地
  2. 调用onBaseContextAttached
    1. loadTinker
    2. 调用delegateClassName对象即SampleApplicationLike.onBaseContextAttached方法
// TinkerApplication
protected void onBaseContextAttached(Context base, 
                                     long applicationStartElapsedTime,
                                     long applicationStartMillisTime) {
   
        try {
   
            loadTinker();
            mCurrentClassLoader = base.getClassLoader();
            mInlineFence = createInlineFence(this, tinkerFlags, delegateClassName,
                    tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime,
                    tinkerResultIntent);
            TinkerInlineFenceAction.callOnBaseContextAttached(mInlineFence, base);
            //reset save mode
            if (useSafeMode) {
   
                ShareTinkerInternals.setSafeModeCount(this, 0);
            }
        } catch (TinkerRuntimeException e) {
   
            throw e;
        } catch (Throwable thr) {
   
            throw new TinkerRuntimeException(thr.getMessage(), thr);
        }
    }

loadTinker是其tinker启动加载核心;内部其实就是寻找并解压补丁包,并动态加载

三、loadTinker

image-20220314115707431

我们看到loadTinker代码实现非常简单,是调用loaderClassName即com.tencent.tinker.loader.TinkerLoader.tryLoad方法,当然这里的tinkerLoader对象也是反射搞出来的

tryLoad内部实现了啥?可以大胆猜测是对下发的补丁包进行解压,解压后有二类资源一类是dex文件,一类是资源文件;对于dex文件可以直接使用DexClassLoader实现动态加载,对于资源文件可以通过反射调用addAssetPath将资源告知给系统;接下来看下tinker内部实现是不是这样做的?

@Override
    public Intent tryLoad(TinkerApplication app) {
   
        ShareTinkerLog.d(TAG, "tryLoad test test");
        Intent resultIntent = new Intent();

        long begin = SystemClock.elapsedRealtime();
      	// 1. 核心代码
        tryLoadPatchFilesInternal(app, resultIntent);
        long cost = SystemClock.elapsedRealtime() - begin;
      
      	// 2. 记录补丁包耗时
        ShareIntentUtil.setIntentPatchCostTime(resultIntent, cost);
        return resultIntent;
    }

tryLoadPatchFilesInternal内部实现有330行代码左右,但主流程非常清晰,我们以功能来划分逐部拆解即可,主要分为四个小块

  1. 读取补丁信息
  2. patch版本相关处理
  3. dex,res,so等校验处理
  4. load patch dex

1. 解析补丁信息

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
   
		 final int tinkerFlag = app.getTinkerFlags();
		// 一些校验处理
        if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles: tinker is disable, just return");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
            return;
        }
        if (ShareTinkerInternals.isInPatchProcess(app)) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles: we don't load patch with :patch process itself, just return");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
            return;
        }
  
  			// 1. 获取补丁包位置
        //tinker
        File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app);
        if (patchDirectoryFile == null) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:getPatchDirectory == null");
            //treat as not exist
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST);
            return;
        }
        String patchDirectoryPath = patchDirectoryFile.getAbsolutePath();
				
  			// 2. 补丁目录校验
        //check patch directory whether exist
        if (!patchDirectoryFile.exists()) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:patch dir not exist:" + patchDirectoryPath);
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST);
            return;
        }
				
        //tinker/patch.info  3. 读取补丁相关信息
        File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);

        //check patch info file whether exist
        if (!patchInfoFile.exists()) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:patch info not exist:" + patchInfoFile.getAbsolutePath());
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST);
            return;
        }
        //old = 641e634c5b8f1649c75caf73794acbdf
        //new = 2c150d8560334966952678930ba67fa8
        File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath);
				
  			// 4. 解析补丁信息
        patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
        if (patchInfo == null) {
   
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
            return;
        }	
}

解析补丁信息(readAndCheckPropertyWithLock)字段包括如下

old --> oldVer
new --> newVer
is_protected_app --> 加固app
is_remove_new_version
print --> finger print
dir --> oat dir
is_remove_interpret_oat_dir

2. patch版本处理

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
   				
				// ...
				final boolean isProtectedApp = patchInfo.isProtectedApp;
        resultIntent.putExtra(ShareIntentUtil.INTENT_IS_PROTECTED_APP, isProtectedApp);

        String oldVersion = patchInfo.oldVersion;
        String newVersion = patchInfo.newVersion;
        String oatDex = patchInfo.oatDir;

        if (oldVersion == null || newVersion == null || oatDex == null) {
   
            //it is nice to clean patch
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchInfoCorrupted");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
            return;
        }

        boolean mainProcess = ShareTinkerInternals.isInMainProcess(app);
        boolean isRemoveNewVersion = patchInfo.isRemoveNewVersion;

        if (mainProcess) {
   
            final String patchName = SharePatchFileUtil.getPatchVersionDirectory(newVersion);
            // So far new version is not loaded in main process and other processes.
            // We can remove new version directory safely.
            if (isRemoveNewVersion) {
   
                ShareTinkerLog.w(TAG, "found clean patch mark and we are in main process, delete patch file now.");
                if (patchName != null) {
   
                    // oldVersion.equals(newVersion) means the new version has been loaded at least once
                    // after it was applied.
                    final boolean isNewVersionLoadedBefore = oldVersion.equals(newVersion);
                    if (isNewVersionLoadedBefore) {
   
                        // Set oldVersion and newVersion to empty string to clean patch
                        // if current patch has been loaded before.
                        oldVersion = "";
                    }
                    newVersion = oldVersion;
                    patchInfo.oldVersion = oldVersion;
                    patchInfo.newVersion = newVersion;
                    patchInfo.isRemoveNewVersion = false;
                    SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile);

                    String patchVersionDirFullPath = patchDirectoryPath + "/" + patchName;
                    SharePatchFileUtil.deleteDir(patchVersionDirFullPath);

                    if (isNewVersionLoadedBefore) {
   
                        ShareTinkerInternals.killProcessExceptMain(app);
                        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST);
                        return;
                    }
                }
            }
            if (patchInfo.isRemoveInterpretOATDir) {
   
                // delete interpret odex
                // for android o, directory change. Fortunately, we don't need to support android o interpret mode any more
                ShareTinkerLog.i(TAG, "tryLoadPatchFiles: isRemoveInterpretOATDir is true, try to delete interpret optimize files");

                patchInfo.isRemoveInterpretOATDir = false;
                SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile);
                ShareTinkerInternals.killProcessExceptMain(app);
                String patchVersionDirFullPath = patchDirectoryPath + "/" + patchName;
                SharePatchFileUtil.deleteDir(patchVersionDirFullPath + "/" + ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH);
            }
        }

        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OLD_VERSION, oldVersion);
        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_NEW_VERSION, newVersion);

        boolean versionChanged = !(oldVersion.equals(newVersion));
        boolean oatModeChanged = oatDex.equals(ShareConstants.CHANING_DEX_OPTIMIZE_PATH);
        oatDex = ShareTinkerInternals.getCurrentOatMode(app, oatDex);
        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, oatDex);

        String version = oldVersion;
        if (versionChanged && mainProcess) {
   
            version = newVersion;
        }
        if (ShareTinkerInternals.isNullOrNil(version)) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:version is blank, wait main process to restart");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK);
            return;
        }

        //patch-641e634c
        String patchName = SharePatchFileUtil.getPatchVersionDirectory(version);
        if (patchName == null) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:patchName is null");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
            return;
        }
        //tinker/patch.info/patch-641e634c
        String patchVersionDirectory = patchDirectoryPath + "/" + patchName;

        File patchVersionDirectoryFile = new File(patchVersionDirectory);

        if (!patchVersionDirectoryFile.exists()) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchVersionDirectoryNotFound");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
            return;
        }

        //tinker/patch.info/patch-641e634c/patch-641e634c.apk
        final String patchVersionFileRelPath = SharePatchFileUtil.getPatchVersionFile(version);
        File patchVersionFile = (patchVersionFileRelPath != null ? new File(patchVersionDirectoryFile.getAbsolutePath(), patchVersionFileRelPath) : null);

        if (!SharePatchFileUtil.isLegalFile(patchVersionFile)) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchVersionFileNotFound");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST);
            return;
        }

        ShareSecurityCheck securityCheck = new ShareSecurityCheck(app);

        int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck);
        if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {
   
            ShareTinkerLog.w(TAG, "tryLoadPatchFiles:checkTinkerPackage");
            resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, returnCode);
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL);
            return;
        }

        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent());
 				// ... 
}

3. 校验

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
   
  		// ..
        //tinker/patch.info/patch-641e63
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值