🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
七、测试 因为布局简单且不是重点,这里就给出一张Demo 的运行图片,剩下的就靠想像了。 ![](https://img.kancloud.cn/1a/fc/1afcbc80abed29230f8344e68966ea49_347x573.jpg) 1、编译基础包 没有基础包,那要补丁有什么用?所以,第一步就是打包一个apk。 在Terminal 中使用命令行./gradlew assembleDebug。不会命令行无所谓,Android Studio 为我们提供了图形化操作,根据下图操作即可: ![](https://img.kancloud.cn/aa/3f/aa3f95f19bb25e89cc45a8b3513cda47_786x671.jpg) 如果你是要release 签名的打包,则双击assembleRelease,不过还要配置签名文件,这个后面再说。 编译完成后,可以在build 目录下会自动创建一个bakApk 文件夹,里面就有打包好的apk 文件,因为之后的所有生成的补丁包都以这个apk 会标准,所以这就是那个基础包文件(相当于应用市场上的app)。 ![](https://img.kancloud.cn/d1/f5/d1f573d7ac31aabba0cdce617f51b227_351x256.jpg) 如果这个apk 文件是release 签名且是要放到应用市场上的,那么你必须将apk 与R.txt(如果有使用混淆的话,还会有一个mapping.txt)这几个文件保存好,切记。 现在就把这个tinker-local-debug-1206-11-48-42.apk 安装到手机上(相当于是用户手机上的app)。 ![](https://img.kancloud.cn/58/0c/580c19a62b42fe0f2761f59739c38c50_342x567.jpg) 1.点击"say someting"按钮吐司"Hello"。 2.点击"get string from .so"按钮吐司"hello LQR"。 3.点击"show info"按钮显示"patch is not loaded",说明当前没有加载补丁。 2、修复java 代码 下面是"say someting"按钮点击时,调用的方法,使用Toast 显示Hello 字符串: ``` public void say(View view) { Toast.makeText(getApplicationContext(), "Hello", Toast.LENGTH_SHORT).show(); } ``` 1)修复代码 现在我想让它吐司Hello World,所以代码修改为: ``` public void say(View view) { Toast.makeText(getApplicationContext(), "Hello World", Toast.LENGTH_SHORT).show(); } ``` 2)制作补丁包 先将基础包(前面那个tinker-local-debug-1206-11-48-42.apk 文件)重命名为old-app.apk,然后双击tinkerPatchDebug,操作如下图所示: ![](https://img.kancloud.cn/0d/76/0d76a01db43903f75cfc6eee5a009cab_717x486.jpg) 编译完成后,build/outputs/apk/tinkerPatch 会产生3 个补丁包,我们要的就是patch_signed_7zip.apk。 ![](https://img.kancloud.cn/8f/ba/8fba6ebf917c9d97284fc6ccdcefbc6f_598x467.jpg) 对于build/outputs/apk/tinkerPatch 目录下文件及文件夹的详细说明,请参考:[Tinker 官方Wiki:输出文件详解](https://github.com/Tencent/tinker/wiki/Tinker-接入指南)。 3)下发补丁包 将patch_signed_7zip.apk 放到手机的SD 卡目录下:不一定是SD 卡目录,位置由我们开发者决定,Demo 中调用 TinkerInstaller.onReceiveUpgradePatch(context, 补丁包的本地路径)方法时,第二个参数指定了是SD 卡,故如此操作。 ![](https://img.kancloud.cn/cf/cb/cfcba2f77d698687e6e272eac157c3ff_426x382.jpg) 4)打补丁 可以看到,在install patch 之前,点击"say someting"按钮时,还是吐司"Hello"。点击"install patch"按钮后,会提示"patch success,please restart process",说明Tinker 已经打上补丁了。这时点击"show info",可以看到"patch is not loaded",说明当前补丁还没有生效。最后,点击"kill myself"按钮,杀死当前 app(进程)。 ![](https://img.kancloud.cn/9a/ad/9aad5a931a29c307e085a21ff7677b75_342x567.jpg) 在重新打开app,再点击"say someting"按钮,吐司"Hello World"。再点击"show info",可以看到"patch is loaded",说明app 重启后补丁生效了。 ![](https://img.kancloud.cn/58/8b/588bde2be58ab6b6c92b16f87860b535_342x567.jpg) 小结: Tinker 热修复无法让补丁实时生效,在重启进程后,补丁才会生效。Tinker 会在app 重启后自动应用补丁。 3、修复so 库 在一开始制作基础包时,工程中就已经加入了一些so 文件,存放在src/main/jniLibs 目录下,因为Android Studio 默认的库目录是libs(与src 同级),所以这里需要在app 的build.gradle 文件中进行配置,指定so 库所在文件夹。 ![](https://img.kancloud.cn/12/b1/12b13e5e131d300e73e43ca512ba55a2_910x332.jpg) 下面是"get string from .so"按钮点击时调用的方法: ``` public void string_from_so(View view) { String string = JniUtil.hello(); Toast.makeText(getApplicationContext(), string, Toast.LENGTH_SHORT).show(); } ``` 这个JniUtil 的代码如下: ``` public class JniUtil { public static String LIB_NAME = "LQRJni"; public JniUtil() {} static { System.loadLibrary(LIB_NAME); } public static native String hello(); } ``` 加载so 库有2 点需要注意: 1.System.loadLibrary(libname)加载固定目录下的库文件,而System.load(filename)加载指定目录下的库文件。 2.System.loadLibrary(libname)的参数libname 指的是库的模块名,不是so 文件的名字,如libLQRJni.so 文件的模块名实际上是LQRJni。 > so 文件的制作代码包含在Demo 中,有兴趣的朋友可以尝试自己制作。 1)替换so 文件 回归正题,现在so 库中得到的文字是"Hello LQR",现在变一下,我需要得到的文字是"Hello CSDN_LQR",将新的so 文件替换掉旧的so 文件即可。 ![](https://img.kancloud.cn/d6/b1/d6b1a818d5b7ace6e23201af5c30ba4a_879x187.jpg) 2)检查Tinker 的lib 匹配规则 在app 的build.gradle 文件中,我们前面在第三部分《Tinker 的配置及任务》的第2 节《配置Tinker 与任务》中,有如下一段配置: ``` lib { pattern = ["lib/*/*.so", "src/main/jniLibs/*/*.so"] } ``` 这就是Tinker 的lib 匹配规则,在生成补丁的过程中,它会去把符合这个规则的库文件拿出来与基础包中的库文件进行匹配,从而将有差异的库文件放入到补丁包中。而Tinker 官方Demo 的配置中是没有"src/main/jniLibs//.so"这一段的,这将导致Tinker 在产生补丁包时不会去检查src/main/jniLibs 目录下的文件变化,进而补丁包中不会包含修复好的so 文件,这很重要,切记。 3)生成补丁与下发补丁包 生成补丁与下发补丁包的过程与之前的操作一致,这里不再重覆,不过我们来看看tinkerPatch 跟之前有什么区别吧: ![](https://img.kancloud.cn/68/3d/683d4967488d5a18bc807fca9bd5191b_677x436.jpg) 最后记得将patch_signed_7zip.apk 放到手机的SD 卡目录下。 4)卸载补丁 补丁是可以打多个的,用补丁的版本号做区分,在卸载的时候,可以根据补丁的版本号来卸载,也可以把之前所有的补丁卸载掉,实际开发中,看项目需求来解决用哪种方式来卸载补丁,这里我选择清理之前所有的补丁,下面是"uninstall patch"按钮的点击事件: ``` public void uninstall_patch(View view) { ShareTinkerInternals.killAllOtherProcess(getApplicationContext()); Tinker.with(getApplicationContext()).cleanPatch(); } ``` 卸载补丁之前,要先杀死当前App 的其他进程。卸载补丁之后,App 是不安全的(因为此时Tinker 已经初始化完成),最好,需要重启一下App。 5)打补丁 现在我们再来打一次补丁,操作如下图所示: ![](https://img.kancloud.cn/de/67/de67b2cd6eef959d2381ade7852e3fd3_342x567.jpg) 可以看到,在install patch 之前,点击"get string from .so"按钮时,还是吐司"Hello LQR"。点击"install patch"按钮后,会提示"patch success,please restart process",说明Tinker 已经打上补丁了。这时点击"show info",可以看到"patch is not loaded",说明当前补丁还没有生效。最后,点击"kill myself"按钮,杀死当前app(进程)。现在重启app,理想状态下,当我们再次点击"get string from .so"按钮时,会吐司"Hello CSDN_LQR"。 然而,吐司还是"Hello LQR",并没有变化,而且点击"show info"按钮后,可以看到"patch is loaded",说明补丁已经加载了,这是为啥? 再来看看下面的操作: 重启app 后,先点击"load library(hack)"按钮,再点击"get string from .so"按钮,出现了,吐司变成了"Hello CSDN_LQR"。 上图是Tinker 官网Wiki 的文档部分截图,从红线部分可以知道,因为部分手机判断abi 并不准确(可能因为Android 碎片化比较严重吧),Tinker 没有区分abi,自然也不会在app 启动时,自动加载对应的so 库,这需要开发者自己判 断。 下面是"load library(hack)"按钮点击调用的方法: ``` public void load_library_hack(View view) { String CPU_ABI = android.os.Build.CPU_ABI; // 将tinker library 中的CPU_ABI 架构的so 注册到系统的library path 中。 TinkerLoadLibrary.installNavitveLibraryABI(this, CPU_ABI); } ``` 这是Tinker 提供的使用Hack 方式加载补丁中的so 库,只是一个方法调用而已,并没有什么特别。对于非Hack 方式加载补丁的方式,我本人是没有测试成功的,很奇怪,搞不明白问题的原因,官方的文档也写得不清不楚的,有知道本Demo加载不成功的原因的朋友请不吝赐教一下哈,thx。 > 小结: Tinker 虽然会在app 重启后自动加载补丁,但不会自动加载补丁中的so 文件,开发者需自己判定好abi 来加载so 文件。 4、修复资源文件 这部分跟前面的重合度极高,故不做演示了,你可以在补丁包中对本demo 中的头像进行替换试试,与修复java 文件的操作基本一致,这部分需要提醒的是,app 的build.gradle 文件中Tinker 配置有如下这一段: ``` res { pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] ... } ``` 不难理解,这就是Tinker 对资源文件的匹配规则,日常开发够用,如果你的项目中把资源文件放到了这里没有的目录下,需要修改这部分的配置。