Tinker 自定义扩展

2024-04-30 20:18
文章标签 自定义 扩展 tinker

本文主要是介绍Tinker 自定义扩展,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Tinker 自定义扩展

自定义Application类

程序启动时会加载默认的Application类,这导致我们补丁包是无法对它做修改了。如何规避?在这里我们并没有使用类似InstantRun hook Application的方式,而是通过代码框架的方式来避免,这也是为了尽量少的去反射,提升框架的兼容性。

这里我们要实现的是完全将原来的Application类隔离起来,即其他任何类都不能再引用我们自己的Application。我们需要做的其实是以下几个工作:

  1. 将我们自己Application类以及它的继承类的所有代码拷贝到自己的ApplicationLike继承类中,例如SampleApplicationLike。你也可以直接将自己的Application改为继承ApplicationLike;
  2. Application的attachBaseContext方法实现要单独移动到onBaseContextAttached中;
  3. 对ApplicationLike中,引用application的地方改成getApplication();
  4. 对其他引用Application或者它的静态对象与方法的地方,改成引用ApplicationLike的静态对象与方法;

更详细的事例,大家可以参考下面的一些例子以及SampleApplicationLike的做法。

Application代理类

为了使真正的Application实现可以在补丁包中修改,我们把Appliction类的所有逻辑移动到ApplicationLike代理类中。

-public class YourApplication extends Application {
+public class SampleApplicationLike extends DefaultApplicationLike 

同时我们需要将gradle的dex loader中的Application改为新的YourApplication。

dex {
loader = ["com.tencent.tinker.loader.*",//warning, you must change it with your application"tinker.sample.android.YourApplication",       
}

具体实现可参考SampleApplicationLike, 其中对Application类的调用可以修改成:

public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {application.registerActivityLifecycleCallbacks(callback);
}

若你的应用代理Application的ClassLoader、Resource以及AssetsManger,可以使用以下方法设置。

applicationLike.setResources(res);
applicationLike.setClassLoader(classloader);
applicationLike.setTAssets(assets);

事实上,你也可以在你的Application类加入代理,但是在Application中尽量不要引用自己的类,将真正的实现放在外面。

public class YourrApplication extends Application {ActivityLifecycleCallbacks activityLifecycleCallbacks;public void setTinkerActivityLifecycleCallbacks(ActivityLifecycleCallbacks activityLifecycleCallbacks) {this.activityLifecycleCallbacks = activityLifecycleCallbacks;}

修改你的Application类

然后将你的Application类继承TinkerApplication.java。除了构造方法之外,你最好不要引入其他的类,这将导致它们无法通过补丁修改。

public class SampleApplication extends TinkerApplication {public SampleApplication() {super(//tinkerFlags, tinker支持的类型,dex,library,还是全部都支持!ShareConstants.TINKER_ENABLE_ALL,//ApplicationLike的实现类,只能传递字符串 "tinker.sample.android.app.SampleApplicationLike",//Tinker的加载器,一般来说用默认的即可"com.tencent.tinker.loader.TinkerLoader",//tinkerLoadVerifyFlag, 运行加载时是否校验dex与,ib与res的Md5false);}  
}

具体的数值含义如下:

参数默认值描述
tinkerFlagsTINKER_DISABLEtinker运行时支持的补丁包中的文件类型:
1. ShareConstants.TINKER_DISABLE:不支持任何类型的文件;
2. ShareConstants.TINKER_DEX_ONLY:只支持dex文件;
3. ShareConstants.TINKER_LIBRARY_ONLY:只支持library文件;
4. ShareConstants.TINKER_DEX_AND_LIBRARY:只支持dex与res的修改;
5. ShareConstants.TINKER_ENABLE_ALL:支持任何类型的文件,也是我们通常的设置的模式。
delegateClassName"com.tencent.tinker.loader
.app.DefaultApplicationLike"
Application代理类的类名,这里只能使用字符串,不能使用class.getName()
loaderClassName"com.tencent.tinker.
loader.TinkerLoader"
加载Tinker的主类名,对于特殊需求可能需要使用自己的加载类。需要注意的是:
这个类以及它使用的类都是不能被补丁修改的,并且我们需要将它们加到dex.loader[]中
一般来说,我们使用默认即可。
tinkerLoadVerifyFlagfalse由于合成过程中我们已经校验了各个文件的Md5,并将它们存放在/data/data/..目录中。默认每次加载时我们并不会去校验tinker文件的Md5,但是你也可通过开启loadVerifyFlag强制每次加载时校验,但是这会带来一定的时间损耗。

Warning: 这里务必不能写成SampleApplicationLike.class.getName(),只能通过传递字符串的方式。为了减少错误的出现,推荐使用Annotation生成Application类

使用Annotation生成Application类

为了隐藏你的Application类,我们更加推荐你使用tinker-android-anno在运行时生成你的Application类。这样保证你无法修改你的Application类,不会因为错误操作导致引入更多无法修改的类。

@DefaultLifeCycle(
application = ".SampleApplication",                       //application类名
flags = ShareConstants.TINKER_ENABLE_ALL,                 //tinkerFlags
loaderClass = "com.tencent.tinker.loader.TinkerLoader",   //loaderClassName, 我们这里使用默认即可!
loadVerifyFlag = false)                                   //tinkerLoadVerifyFlag
public class SampleApplicationLike extends DefaultApplicationLike

若采用Annotation生成Application,需要将原来的Application类删掉。到此为止,Tinker初步的接入已真正的完成,你已经可以愉快的使用Tinker来实现补丁功能了。

可选的自定义类

在Tinker中你可以自定义一些类,它们需要在构造Tinker实例时作为参数传递,在TinkerManager的installTinker中,你可以根据自己的需要自定义其中的一些类:

//or you can just use DefaultLoadReporter
LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication());
//or you can just use DefaultPatchReporter
PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication());
//or you can just use DefaultPatchListener
PatchListener patchListener = new SamplePatchListener(appLike.getApplication());
//you can set your own upgrade patch if you need
AbstractPatch upgradePatchProcessor = new UpgradePatch();
//you can set your own repair patch if you need
AbstractPatch repairPatchProcessor = new RepairPatch();
TinkerInstaller.install(appLike,loadReporter, patchReporter, patchListener,SampleResultService.class, upgradePatchProcessor, repairPatchProcessor);

你也可以使用sampleInstallTinker,即全部使用默认参数。

各个类具体的功能与使用方法如下:

自定义LoadReporter类

LoadReporter类定义了Tinker在加载补丁时的一些回调,我们为你提供了默认实现DefaultLoadReporter.java.

一般来说, 你可以继承DefaultLoadReporter实现你自己感兴趣的事件回调,例如SampleLoadReporter.java.

我们在sample中也写了一个默认的回调上报例子,可参考SampleTinkerReport.

需要注意的有以下两点

  1. 回调运行在加载的进程,它有可能是各个不一样的进程。我们可以通过tinker.isMainProcess或者tinker.isPatchProcess知道当前是否是主进程,patch补丁合成进程。
  2. 回调发生的时机是我们调用installTinker之后,某些进程可能并不需要installTinker。

现对各个回调作简要的说明:

函数描述
onLoadResult这个是无论加载失败或者成功都会回调的接口,它返回了本次加载所用的时间、返回码等信息。默认我们只是简单的输出这个信息,你可以在这里加上监控上报逻辑。
onLoadPatchListenerReceiveFail所有的补丁合成请求都需要先通过PatchListener的检查过滤。这次检查不通过的回调,它运行在发起请求的进程。默认我们只是打印日志
onLoadPatchVersionChanged补丁包版本升级的回调,只会在主进程调用。默认我们会杀掉其他所有的进程(保证所有进程代码的一致性),并且删掉旧版本的补丁文件。
onLoadFileNotFound在加载过程中,发现部分文件丢失的回调。默认若是dex,dex优化文件或者lib文件丢失,我们将尝试从补丁包去修复这些丢失的文件。若补丁包或者版本文件丢失,将卸载补丁包。
onLoadFileMd5Mismatch部分文件的md5与meta中定义的不一致。默认我们为了安全考虑,依然会清空补丁。
onLoadPatchInfoCorruptedpatch.info是用来管理补丁包版本的文件,这是info文件损坏的回调。默认我们会卸载补丁包,因为此时我们已经无法恢复了。
onLoadPackageCheckFail加载过程补丁包的检查失败,这里可以通过错误码区分,例如签名校验失败、tinkerId不一致等原因。默认我们将会卸载补丁包
onLoadException在加载过程捕捉到异常,十分希望你可以把错误信息反馈给我们。默认我们会直接卸载补丁包

所有的错误码都定义在ShareConstants.java,onLoadPackageCheckFail 的相关错误码解析如下:

错误码数值描述
ERROR_PACKAGE_CHECK_SIGNATURE_FAIL-1签名校验失败
ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND-2找不到"assets/package_meta.txt"文件
ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED-3"assets/dex_meta.txt"信息损坏
ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED-4"assets/so_meta.txt"信息损坏
ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND-5找不到基准apk AndroidManifest中的TINKER_ID
ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND-6找不到补丁中"assets/package_meta.txt"中的TINKER_ID
ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL-7基准版本与补丁定义的TINKER_ID不相等
ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED-8"assets/res_meta.txt"信息损坏
ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT-9tinkerFlag不支持补丁中的某些类型的更改,例如补丁中存在资源更新,但是使用者指定不支持资源类型更新。

onLoadException的错误码具体如下:

错误码数值描述
ERROR_LOAD_EXCEPTION_UNKNOWN-1没有捕获到的java crash
ERROR_LOAD_EXCEPTION_DEX-2在加载dex过程中捕获到的crash
ERROR_LOAD_EXCEPTION_RESOURCE-3在加载res过程中捕获到的crash
ERROR_LOAD_EXCEPTION_UNCAUGHT-4没有捕获到的非java crash,这个是补丁机制的安全模式

回调中定义的fileType定义如下:

文件类型数值描述
TYPE_PATCH_FILE1补丁文件
TYPE_PATCH_INFO2"patch.info"补丁版本配置文件
TYPE_DEX3在Dalvik合成全量的Dex文件
TYPE_DEX_FOR_ART4在Art合成的小Dex文件
TYPE_DEX_OPT5odex文件
TYPE_LIBRARY6library文件
TYPE_RESOURCE7资源文件

加载过程的具体的错误类型与错误码可查看DefaultLoadReporter.java的注释。

对于onLoadPatchVersionChanged与onLoadFileNotFound的复写要较为谨慎,因为版本升级杀掉其他进程与文件丢失发起恢复任务,都是我认为比较重要的操作。

自定义PatchReporter类

PatchReporter类定义了Tinker在修复或者升级补丁时的一些回调,我们为你提供了默认实现DefaultPatchReporter.java.

一般来说, 你可以继承DefaultPatchReporter实现你自己感兴趣的事件回调,例如SamplePatchReporter.java.

需要注意的是:

isUpgrade:区分补丁合成的类型。是由于文件丢失而发起的RepariPatch, 还是收到新的补丁而发起的UpgradePatch。

函数描述
onPatchResult这个是无论补丁合成失败或者成功都会回调的接口,它返回了本次合成的类型,时间以及结果等。默认我们只是简单的输出这个信息,你可以在这里加上监控上报逻辑。
onPatchServiceStart这个是Patch进程启动时的回调,我们可以在这里进行一个统计的工作。
onPatchPackageCheckFail补丁合成过程对输入补丁包的检查失败,这里可以通过错误码区分,例如签名校验失败、tinkerId不一致等原因。默认我们会删除临时文件。
onPatchVersionCheckFail对patch.info的校验失败,对于RepairPatch, 当前的补丁版本应该等于输入补丁包的版本。反而,对于UpgradePatch, 输入补丁包版本不能等于当前的补丁版本。默认我们会删除临时文件。
onPatchTypeExtractFail从补丁包与原始安装包中合成某种类型的文件出现错误,默认我们会删除临时文件。
onPatchDexOptFail对合成的dex文件提前进行dexopt时出现异常,默认我们会删除临时文件。
onPatchInfoCorruptedpatch.info是用来管理补丁包版本的文件,这是在更新info文件时发生损坏的回调。默认我们会卸载补丁包,因为此时我们已经无法恢复了。
onPatchException在补丁合成过程捕捉到异常,十分希望你可以把错误信息反馈给我们。默认我们会删除临时文件,并且将tinkerFlag设为不可用。

PatchReporter中onPatchPackageCheckFail的错误码与LoadReporter的一致。

自定义PatchListener类

PatchListener类是用来过滤Tinker收到的补丁包的修复、升级请求,也就是决定我们是不是真的要唤起:patch进程去尝试补丁合成。我们为你提供了默认实现DefaultPatchListener.java。

一般来说, 你可以继承DefaultPatchListener并且加上自己的检查逻辑,例如SamplePatchListener.java。

若检查成功,我们会调用TinkerPatchService.runPatchService唤起:patch进程,去尝试完成补丁合成操作。反之,会回调检验失败的接口。事实上,你只需要复写patchCheck函数即可。若检查失败,会在LoadReporter的onLoadPatchListenerReceiveFail中回调。

public int patchCheck(String path, boolean isUpgrade)

以DefaultPatchListener为例,说明默认我们检查的条件,你可以定义自己的错误码,也可以沿用这里的错误码。

错误码数值描述
ERROR_PATCH_DISABLE-1当前tinkerFlag为不可用状态。
ERROR_PATCH_NOTEXIST-2输入的临时补丁包文件不存在。
ERROR_PATCH_RUNNING-3当前:patch补丁合成进程正在运行。
ERROR_PATCH_INSERVICE-4不能在:patch补丁合成进程,发起补丁的合成请求。
其他 在SamplePatchListener里面,我们还检查了当前Rom剩余空间,最大内存,是否是GooglePlay渠道等条件。

自定义AbstractResultService类

AbstractResultService类是:patch补丁合成进程将合成结果返回给主进程的类。我们为你提供了默认实现DefaultTinkerResultService.java。

一般来说, 你可以继承DefaultTinkerResultService实现自己的回调,例如SampleResultService.java。当然,你也需要在AndroidManifest上添加你的Service。

<serviceandroid:name=".service.SampleResultService"android:exported="false"
/>

默认我们在DefaultTinkerResultService会杀掉:patch进程,假设当前是补丁升级并且成功了,我们会杀掉当前进程,让补丁包更快的生效。若是修复类型的补丁包并且失败了,我们会卸载补丁包。下面对PatchResult的定义做详细说明:

函数描述
isUpgradePatch是否是补丁升级的类型。
isSuccess补丁合成操作是否成功。
rawPatchFilePath原始的补丁包路径,若isUpgradePatch为true, 即这个是外部临时的补丁包路径。反之对于rePairPatch这个是我们当前正常使用的补丁包路径,要谨慎对待。
costTime本次补丁合成的耗时。
e本次补丁合成是否出现异常,null为没有异常。
patchVersion补丁文件的md5, 有可能为空@Nullable。
baseTinkerID基础包的TinkerID, 有可能为空@Nullable。
patchTinkerID补丁包的TinkerID, 有可能为空@Nullable。

在SampleResultService.java中,我们没有立刻杀掉当前进程去应用补丁,而选择在当前应用在退入后台或手机锁屏时这两个时机。你也可以在自杀前,通过发送service或者broadcast inent来尽快重启进程。

自定义TinkerLoader类

TinkerLoader类是用来加载补丁的核心类,你可以实现自己的加载逻辑。但是一般不建议那么做,如果你一定要你需要保证以下两条规则:

  1. 将你的实现的类以及它用到的所有类都加入到dex.loader中;
  2. 保证上述的类都在main dex中。

只要简单的将loaderClass参数中的"com.tencent.tinker.loader.TinkerLoader",换成你的实现类的名称即可,这里只能传递字符串。

@DefaultLifeCycle(
application = ".SampleApplication",                       //application类名
flags = ShareConstants.TINKER_ENABLE_ALL,                 //tinkerFlags
loaderClass = "com.tencent.tinker.loader.TinkerLoader")   //loaderClassName, 我们这里使用默认即可!
public class SampleApplicationLike extends DefaultApplicationLike 

Tinker Notification id设置

为了提高TinkerPatchService的进程优先级,我们将它设置为Foreground。对于sdk>18的版本,使用innerService方式使通知栏不会显示。

Warning, 这里占用了id为-1119860829.若你的app存在与它相同的id, 可以使用以下API重新设置

TinkerPatchService.setTinkerNotificationId(id);
Tinker.with(context).setPatchServiceNotificationId(id);

自定义RepairPatch与UpgradePatch类

RepairPatch类是用来修复当前补丁包的处理类,也就是上面isUpgradePatch为false的情况。一般来说你不需要复写它,如果你复写了,只要把新的实例传递给TinkerInstaller即可。

UpgradePatch类是用来升级当前补丁包的处理类,也就是上面isUpgradePatch为true的情况,一般来说你也不需要复写它。

可以看到整个Tinker框架非常灵活,基本所有的逻辑都放在可复写的类或回调中,你可以轻松的完成自身需要的自定义工作。

你可以根据需要自定义以上的一些类,然后我们继续学习Tinker API概览。

这篇关于Tinker 自定义扩展的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/949822

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

PHP7扩展开发之数组处理

前言 这次,我们将演示如何在PHP扩展中如何对数组进行处理。要实现的PHP代码如下: <?phpfunction array_concat ($arr, $prefix) {foreach($arr as $key => $val) {if (isset($prefix[$key]) && is_string($val) && is_string($prefix[$key])) {$arr[

PHP7扩展开发之字符串处理

前言 这次,我们来看看字符串在PHP扩展里面如何处理。 示例代码如下: <?phpfunction str_concat($prefix, $string) {$len = strlen($prefix);$substr = substr($string, 0, $len);if ($substr != $prefix) {return $prefix." ".$string;} else