Android热修复之Tinker使用初探

from :

http://blog.csdn.net/y97524027/article/details/52678428



前几天,微信团队的Android热修复框架在GitHub上开源了,6天之内,已经2700+star了,我只能说太6了


地址:https://github.com/Tencent/tinker


官方介绍:https://my.oschina.net/shwenzhang/blog/751618


接入指南:https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97


今天拿下来集成使用了一下,发现md上对集成使用的过程介绍的比较精简(后来发现wiki上面倒是很详细,需要的同学可以自己去看),这里记录一下我集成使用的过程。


一、集成

我这里直接使用的是他自带的sample,导入到studio中。


二、初始化配置

首先我们需要在app/bulid.gradle中,设置tinkerId的值,很多人开始编译就报错,提示“tinkerId is not set!!!”,就是因为这个值没有设置。获取tinkerId走的

[java]  view plain  copy
  1. def gitSha() {  
  2.     return 'git rev-parse --short HEAD'.execute().text.trim()  
  3. }  

这个方法,也就是获取git的版本号,所以要是你没有安装配置git,或者git没有加入到环境变量中,会得不到git的版本号了。知道了原理,那解决方式就自己想了,我这里就直接写死,上面这个方法直接返回固定字符串。

---------------------------------------------------------------------------

补充:关于获取git提交版本号

[java]  view plain  copy
  1. git rev-parse --short HEAD  
这段代码主要是用来显示最近一次提交到HEAD上的记录编号(类似于“b03b0c4”的字符串,每次提交,字符串都不一样。个人对git命令行了解不多,如果有知道的大神麻烦指教一下)。

所以前面说的,除了环境变量要配置git(可以在命令行输入 git --version ,显示出了版本号,便是配置成功),还要把你的项目与git关联起来,并且保证有一次提交记录,才能获取到该字符串。

具体使用可以看我的另一篇文章:关于git命令“git rev-parse --short HEAD”在android studio中使用与配置的个人探究

个人觉得,加入这段代码,显得更麻烦了,还不如直接写死,或者获取其他的版本号。

---------------------------------------------------------------------------------------------------

之后,我们会看到Manifest.xml中,SampleApplication.java这个类报红找不到。这个并不影响,因为到时候我们在编译的时候,tinker会为我们生成SampleApplication.java这个类,而起作用的代码就是SampleApplicationLike.java类声明上面的

[java]  view plain  copy
  1. @DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",  
  2.                   flags = ShareConstants.TINKER_ENABLE_ALL,  
  3.                   loadVerifyFlag = false)  
这段注解,我们也可以把这段注解注释了,

自定定义一个SampleApplication类继承 TinkerApplication类,然后加入无参构造方法,得到的类为:

[java]  view plain  copy
  1. /** 
  2.  * Created by anzyhui on 2016/9/26. 
  3.  */  
  4. public class SampleApplication extends TinkerApplication {  
  5.   
  6.   
  7.     public SampleApplication(){  
  8.         super(  
  9.                 //tinkerFlags, which types is supported  
  10.                 //dex only, library only, all support  
  11.                 ShareConstants.TINKER_ENABLE_ALL,  
  12.                 // This is passed as a string so the shell application does not  
  13.                 // have a binary dependency on your ApplicationLifeCycle class.  
  14.                 "tinker.sample.android.app.SampleApplicationLike");  
  15.     }  
  16. }  
这些都是Tinker这边定好的规矩,咱们得照着来(第二个参数中的".app"不要忘了,也就是路径不要搞错了),其中第一个参数表示支持修复的类型,有以下类型可以选:

[java]  view plain  copy
  1. public static final int TINKER_DISABLE             = 0x00;    
  2. public static final int TINKER_DEX_MASK            = 0x01;    
  3. public static final int TINKER_NATIVE_LIBRARY_MASK = 0x02;  
  4. public static final int TINKER_RESOURCE_MASK       = 0x04;  
  5. public static final int TINKER_DEX_AND_LIBRARY     = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK;  
  6. public static final int TINKER_ENABLE_ALL          = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK | TINKER_RESOURCE_MASK;  


从字面理解就知道什么意思了。


重新编译一下,一般也不会有问题,如果有问题的话,看看你是不是之前已经生成过了SampleApplication了,在\build\intermediates\classes\debug\tinker路径下对应的目录里,有的话,删掉再编译试下。


三、功能测试

(1)debug版本

先尝试debug版本

现在我们模拟打包一个出现bug的版本。


再activity_main.xml中加入俩控件:

[html]  view plain  copy
  1. <TextView  
  2.     android:id="@+id/tvMessage"  
  3.     android:layout_width="wrap_content"  
  4.     android:layout_height="wrap_content"  
  5.     android:layout_below="@+id/showInfo"  
  6.     android:text="一切正常"    />  
  7. <Button      
  8.     android:id="@+id/btnBug"      
  9.     android:layout_width="wrap_content"      
  10.     android:layout_height="wrap_content"      
  11.     android:layout_alignParentLeft="true"      
  12.     android:layout_alignParentStart="true"      
  13.     android:layout_below="@+id/tvMessage"      
  14.     android:text="点击出现bug"/>  


MainActivity中设置点击事件,点击btnBug时,tvMessage显示"出现bug了"文字。
 
 
打包生成apk
这时,在build/bakApk目录中,会生成一个apk和一个R.txt文件,R.txt的作用后面再讲。
 
apk运行至手机中,
点击点击btnBug按钮,显示:

 
好,现在来修复这个bug。
 
我们在activity_main.xml中删除该“点击出现bug”按钮,增加“点击修复bug”按钮:
(PS:也就是修改了res文件
当然了MainActivity中的代码中,也修改了点击事件,删除了“点击出现bug”按钮的点击事件,增加了“点击修复bug”按钮的点击事件,触发后,上方文本要显示“bug已经修复了”。
 
代码改好后,我们需要配置一下前面提到了R.txt文件,这里面保存了每次编译得到的每个res文件的id值。
 
具体配置就是,在app/build.gradle的ext部分,添加oldApk(也就是出现bug的那个apk)的信息:
 
 
[java] view plain copy
  1. ext {  
  2.     //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?  
  3.     tinkerEnabled = true  
  4.     //you should bak the following files  
  5.     //old apk file to build patch apk  
  6.     tinkerOldApkPath = "${bakPath}/app-debug-0927-11-47-21.apk"  //这个是你出现bug的apk完整名称,app/build/bakApk目录下  
  7.     //proguard mapping file to build patch apk  
  8.     tinkerApplyMappingPath = "${bakPath}/"  //暂未开启混淆,不用管  
  9.     //resource R.txt to build patch apk, must input if there is resource changed  
  10.     tinkerApplyResourcePath = "${bakPath}/app-debug-0927-11-47-21-R.txt"  //如果你修复了res文件,需要指定你bug版本的R.txt文件  
  11.   
  12. }  
然后,在studio的右上角,打开gradle,找到tinkerPatchDebug(我一直用的是debug签名,这个根据自己实际情况来)这个task,点击运行。就会在output目录下生成一个tinkerpatch目录,
在里面找到patch_signed_7zip.apk和patch_signed.apk,这就是我们的差分包。
 
因为代码中指定的差分包路径为:
 
 
[java] view plain copy
  1. loadPatchButton.setOnClickListener(new View.OnClickListener() {  
  2.             @Override  
  3.             public void onClick(View v) {  
  4.                 TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");  
  5.             }  
  6.         });  
我们就直接使用patch_signed_7zip.apk,放到我们的手机根目录,再打开app,点击LOAD PATCH,

 
提示我们补丁加载成功,重启后生效。
 
------------------------------
补充:返回键退出后进入,并没有执行修复。
(当时以为是我手机的原因,就没太在意),现在有朋友评论说自己也加载成功但没法修复,是不是跟我一样按得返回键退出。
杀进程后再进入 ,应该就可以修复成功了,如果不成功,把补丁包逆向一下,看看自己修复的部分有没有在里面。
 
 
------------------------------------------------------
 
重启后,注意看界面上的按钮是不是已经被替换,


按钮已经替换了,这里我已经点击了按钮,所以上方文本内容也提示修复成功。


(2)release版本+混淆

release版本总体使用方式与的bug版本类似,

每次打出正式包时,我们应该备份好。


在打包前,修改app/build.gradle里的

[plain]  view plain  copy
  1. /**  
  2.  * task type, you want to bak  
  3.  */  
  4.     //def taskName = "debug"  
  5.     def taskName = "release"  

tastName为release,minifyEnabled设置为true。

这样release打包时才会在bakApk下生成对应的apk,R.txt以及mapping文件。


之后的打包是一样的。


打差分包(补丁)的时候,我们要做的,同样是,替换里面的属性:

[plain]  view plain  copy
  1. ext {  
  2.     //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?  
  3.     tinkerEnabled = true  
  4.     //you should bak the following files  
  5.     //old apk file to build patch apk  
  6.     tinkerOldApkPath = "${bakPath}/app-release-0929-11-28-36.apk"  
  7.     //proguard mapping file to build patch apk  
  8.     tinkerApplyMappingPath = "${bakPath}/app-release-0929-11-28-36-mapping.txt"  
  9.     //resource R.txt to build patch apk, must input if there is resource changed  
  10.     tinkerApplyResourcePath = "${bakPath}/app-release-0929-11-28-36-R.txt"  
  11.       
  12. }  
开启了混淆的话,就要设置bug版本release包的mapping文件,以保证两次映射一致。


同样,右侧gradle,点击tinkerPatchRelease。

我这次Gradle任务进行了好几分钟,当时还以为是出错了,后来看了一下下图,


因为我是第一次打release包,所以在下载必要的jar包。

有运行慢的可以点进这里看看,是不是一样的情况。


差分包打包成功后,跟之前一样的操作,我这边测试成功没问题呢。


四、总结

之前研究过AndFix,主要是使用native方法,来修改出现bug的方法,虽然支持的系统版本也很广(2.3~7.0),但是具有一定的局限性,只能修改方法的内部实现,不支持新增方法,新增、修改成员变量等;而基于ClassLoader的各种实现,由于采用在字节码中在构造方法注入一段代码,防止被打上CLASS_ISPREVERIFIED标记,在dalvik中比较影响性能,而且支持系统也不全面,所以都不是特别的完美。而且他们都有一个缺点,不能修改资源文件。

这次Tinker的发布,打破了原有的性能问题,功能局限,实现了,支持对library、java类、res文件的修复,并且除了小部分版本的设备(wiki上说是部分三星api19版本),其他基本都可以覆盖到(yunOS另说)。目前来看 ,一个不足是,需要重启后热修复才能生效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值