腾讯面试合集:热修复连环炮(热修复是什么-有接触过tinker吗,tinker原理是什么)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文


需要注意的是在debug出包测试过程中需要修改gradle的参数

ext {
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = true

//for normal build
//old apk file to build patch apk
tinkerOldApkPath = “ b a k P a t h / a p p − d e b u g − 1018 − 17 − 58 − 54. a p k " / / p r o g u a r d m a p p i n g f i l e t o b u i l d p a t c h a p k t i n k e r A p p l y M a p p i n g P a t h = " {bakPath}/app-debug-1018-17-58-54.apk" //proguard mapping file to build patch apk tinkerApplyMappingPath = " bakPath/appdebug1018175854.apk"//proguardmappingfiletobuildpatchapktinkerApplyMappingPath="{bakPath}/app-debug-1018-17-32-47-mapping.txt”
//resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = “${bakPath}/app-debug-1018-17-32-47-R.txt”

//使用buildvariants修改此处app信息作为基准包
tinkerBuildFlavorDirectory = “${bakPath}/app-1020-11-52-37”
}

而release出包可以直接在gradle命令带上后缀-POLD_APK= -PAPPLY_MAPPING= -PAPPLY_RESOURCE=

1.Application改造
Tinker采用了代码框架的方案来解决应用启动加载默认Application导致patch无法修复它。原理就是使用一个ApplicationLike代理类来完成原Application的功能,把所有原理Application中的代码逻辑移动到ApplicationLike中,然后删除原来的Application类通过注解让Tinker自动生成默认Application。

@DefaultLifeCycle(application = “com.*.Application”,
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class ApplicationLike extends DefaultApplicationLike {
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
//you must install multiDex whatever tinker is installed!
MultiDex.install(base);

TinkerManager.setTinkerApplicationLike(this);

TinkerManager.initFastCrashProtect();
//should set before tinker is installed
TinkerManager.setUpgradeRetryEnable(true);

//installTinker after load multiDex
//or you can put com.tencent.tinker.** to main dex
TinkerManager.installTinker(this);
}

}

TinkerManager.java

public static void installTinker(ApplicationLike appLike) {
if (isInstalled) {
TinkerLog.w(TAG, “install tinker, but has installed, ignore”);
return;
}
//or you can just use DefaultLoadReporter
LoadReporter loadReporter = new TinkerLoadReporter(appLike.getApplication());
//or you can just use DefaultPatchReporter
PatchReporter patchReporter = new TinkerPatchReporter(appLike.getApplication());
//or you can just use DefaultPatchListener
PatchListener patchListener = new TinkerPatchListener(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;
}

其中参数application代表自动生成的application包名路径,flags代表tinker作用域包括res、so、dex,loadVerifyFlag代表是否开启加载patch前各个文件进行md5校验,还有一个loaderClass默认是"com.tencent.tinker.loader.TinkerLoader"表示加载Tinker的主类名。

在onBaseContextAttached方法里需要初始化一些Tinker相关回调(在installTinker方法中)PatchReporter是对patch进程中合成过程的回调接口实现,LoadReporter是对主进程加载patch dex补丁过程的回调接口实现。PatchListener可以对接收到patch补丁后做自定义的check操作比如渠道检查和存储空间检查。

设置AbstractResultService的实现类TinkerResultService作为合成补丁完成后的处理重启逻辑的IntentService。

设置AbstractPatch的实现类UpgradePatch类作为合成patch方法tryPatch实现类。
###5.Tinker原理
先上github官方首页的图

BaseApk就是我们的基准包,也就是渠道上线的包。

NewApk就是我们的hotfix包,包括修复的代码资源以及so文件。

Tinker做了对应的DexDiff、ResDiff、BsDiff来产出一个patch.apk,里面具体内容也是由lib、res和dex文件组成,assets中还有对应的dex、res和so信息

然后Tinker通过找到基准包data/app/packagename/base.apk通过DexPatch合成新的dex,并且合成一个tinker_classN.apk(其实就是包含了所有合成dex的zip包)接着在运行时通过反射把这个合成dex文件插入到PathClassLoader中的dexElements数组的前面,保证类加载时优先加载补丁dex中的class。

接下来我们就从加载patch和合成patch来弄清Tinker的整个工作流程。
###6.Tinker源码分析之加载补丁Patch流程
默认情况如果使用了Tinker注解产生Application可以看到它继承了TinkerApplication

/**
*

  • Generated application for tinker life cycle

*/
public class Application extends TinkerApplication {

public Application() {
super(7, “com.jiuyan.infashion.ApplicationLike”, “com.tencent.tinker.loader.TinkerLoader”, false);
}

}

跟踪到TinkerApplication在方法attachBaseContext中找到最终会调用loadTinker方法来,最后反射调用了变量loaderClassName定义类中的tryLoad方法,默认是com.tencent.tinker.loader.TinkerLoader这个类中的tryLoad方法。该方法调用tryLoadPatchFilesInternal来执行相关代码逻辑。

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
//…省略一大段校验相关逻辑代码

//now we can load patch jar
if (isEnabledForDex) {
boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA);
if (isSystemOTA) {
// update fingerprint after load success
patchInfo.fingerPrint = Build.FINGERPRINT;
patchInfo.oatDir = loadTinkerJars ? ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH : ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH;
// reset to false
oatModeChanged = false;

if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);
Log.w(TAG, “tryLoadPatchFiles:onReWritePatchInfoCorrupted”);
return;
}
// update oat dir
resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, patchInfo.oatDir);
}
if (!loadTinkerJars) {
Log.w(TAG, “tryLoadPatchFiles:onPatchLoadDexesFail”);
return;
}
}

//now we can load patch resource
if (isEnabledForResource) {
boolean loadTinkerResources = TinkerResourceLoader.loadTinkerResources(app, patchVersionDirectory, resultIntent);
if (!loadTinkerResources) {
Log.w(TAG, “tryLoadPatchFiles:onPatchLoadResourcesFail”);
return;
}
}
// kill all other process if oat mode change
if (oatModeChanged) {
ShareTinkerInternals.killAllOtherProcess(app);
Log.i(TAG, “tryLoadPatchFiles:oatModeChanged, try to kill all other process”);
}
//all is ok!
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
Log.i(TAG, “tryLoadPatchFiles: load end, ok!”);
return;
}

这里省略了非常多的Tinker校验,一共有包括tinker自身enable属性以及md5和文件存在等相关检查。

先看加载dex部分,TinkerDexLoader.loadTinkerJars传入四个参数,分别为application,patchVersionDirectory当前patch文件目录,oatDir当前patch的oat文件目录,intent,当前patch是否需要进行oat(由于系统OTA更新需要dex oat重新生成缓存)。

/**

  • Load tinker JARs and add them to
  • the Application ClassLoader.
  • @param application The application.
    */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA) {
    if (loadDexList.isEmpty() && classNDexInfo.isEmpty()) {
    Log.w(TAG, “there is no dex to load”);
    return true;
    }

PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader();
if (classLoader != null) {
Log.i(TAG, "classloader: " + classLoader.toString());
} else {
Log.e(TAG, “classloader is null”);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL);
return false;
}
String dexPath = directory + “/” + DEX_PATH + “/”;

ArrayList legalFiles = new ArrayList<>();

for (ShareDexDiffPatchInfo info : loadDexList) {
//for dalvik, ignore art support dex
if (isJustArtSupportDex(info)) {
continue;
}

String path = dexPath + info.realName;
File file = new File(path);

//…check md5
legalFiles.add(file);
}
//… verify merge classN.apk

File optimizeDir = new File(directory + “/” + oatDir);

if (isSystemOTA) {
final boolean[] parallelOTAResult = {true};
final Throwable[] parallelOTAThrowable = new Throwable[1];
String targetISA;
try {
targetISA = ShareTinkerInternals.getCurrentInstructionSet();
} catch (Throwable throwable) {
Log.i(TAG, “getCurrentInstructionSet fail:” + throwable);
// try {
// targetISA = ShareOatUtil.getOatFileInstructionSet(testOptDexFile);
// } catch (Throwable throwable) {
// don’t ota on the front
deleteOutOfDateOATFile(directory);

intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_INTERPRET_EXCEPTION, throwable);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION);
return false;
// }
}

deleteOutOfDateOATFile(directory);

Log.w(TAG, “systemOTA, try parallel oat dexes, targetISA:” + targetISA);
// change dir
optimizeDir = new File(directory + “/” + INTERPRET_DEX_OPTIMIZE_PATH);

TinkerDexOptimizer.optimizeAll(
legalFiles, optimizeDir, true, targetISA,
new TinkerDexOptimizer.ResultCallback() {
//… callback
}
);

if (!parallelOTAResult[0]) {
Log.e(TAG, “parallel oat dexes failed”);
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_INTERPRET_EXCEPTION, parallelOTAThrowable[0]);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION);
return false;
}
}
try {
SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
} catch (Throwable e) {
Log.e(TAG, “install dexes failed”);
// e.printStackTrace();
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
return false;
}

return true;
}

省略了几处md5校验代码,首先获取到PathClassLoader并且通过判断系统是否art过滤出对应legalFiles,如果发现系统进行过OTA升级则通过ProcessBuilder命令行执行dex2oat进行并行的oat优化dex,最后调用installDexes来安装dex。

@SuppressLint(“NewApi”)
public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List files)
throws Throwable {
Log.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + “, dex size:” + files.size());

if (!files.isEmpty()) {
files = createSortedAdditionalPathEntries(files);
ClassLoader classLoader = loader;
if (Build.VERSION.SDK_INT >= 24 && !checkIsProtectedApp(files)) {
classLoader = AndroidNClassLoader.inject(loader, application);
}
//because in dalvik, if inner class is not the same classloader with it wrapper class.
//it won’t fail at dex2opt
if (Build.VERSION.SDK_INT >= 23) {
V23.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 19) {
V19.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(classLoader, files, dexOptDir);
} else {
V4.install(classLoader, files, dexOptDir);
}
//install done
sPatchDexCount = files.size();
Log.i(TAG, "after loaded classloader: " + classLoader + “, dex size:” + sPatchDexCount);

if (!checkDexInstall(classLoader)) {
//reset patch dex
SystemClassLoaderAdder.uninstallPatchDex(classLoader);
throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
}
}
}

针对不同的Android版本需要对DexPathList中的dexElements生成方法makeDexElements进行适配。

主要做的事情就是获取当前app运行时PathClassLoader的父类BaseDexClassLoader中的pathList对象,通过反射它的makePathElements方法传入对应的path参数构造出Element[]数组对象,然后拿到pathList中的Element[]数组对象dexElements两者进行合并排序,把patch的相关dex信息放在数组前端,最后合并数组结果赋值给pathList保证classloader优先到patch中查找加载。

###7.Tinker源码分析之合成补丁Patch流程
合并代码入口

Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);

传入patch文件所在位置即可,推荐通过服务端下发下载到对应的/data/data/应用目录下防止被三方软件清理,onPatchReceived方法在DefaultPatchListener.java中。

@Override
public int onPatchReceived(String path) {
File patchFile = new File(path);

int returnCode = patchCheck(path, SharePatchFileUtil.getMD5(patchFile));

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

先进行tinker的一些初始化配置检查还有patch文件的md5校验。如果check通过returnCode为0则执行runPatchService启动一个IntentService的子类TinkerPatchService来处理patch的合成。接下来看Service执行任务代码:

@Override
protected void onHandleIntent(Intent intent) {
final Context context = getApplicationContext();
Tinker tinker = Tinker.with(context);
tinker.getPatchReporter().onPatchServiceStart(intent);

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;

increasingPriority();
PatchResult patchResult = new PatchResult();
try {
if (upgradePatchProcessor == null) {
throw new TinkerRuntimeException(“upgradePatchProcessor is null.”);
}
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));

}

回调PatchReporter接口的onPatchServiceStart方法,然后取到patch文件同时调用increasingPriority启动一个不可见前台Service保活这个TinkerPatchService,最后开始合成patchupgradePatchProcessor.tryPatch。同样省略一些常规check代码:

@Override
public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
Tinker manager = Tinker.with(context);
final File patchFile = new File(tempPatchPath);
//…省略

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

File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory);

总结:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • BAT大厂面试题、独家面试工具包,

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,


网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,

    [外链图片转存中…(img-O3QqMUKW-1713358592374)]
    [外链图片转存中…(img-uzMQTpgn-1713358592375)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-KkNkTYP0-1713358592375)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值