Tinker的集成和多渠道打包

来源:互联网 发布:sql中to_date 编辑:程序博客网 时间:2024/09/21 09:25

Tinker是什么

Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。

 

它主要包括以下几个部分:

1.gradle编译插件: tinker-patch-gradle-plugin

2.核心sdk库: tinker-android-lib

3.非gradle编译用户的命令行版本: tinker-patch-cli.jar

为什么使用Tinker`

当前市面的热补丁方案有很多,其中比较出名的有阿里的AndFix、美团的Robust以及QZone的超级补丁方案。但它们都存在无法解决的问题,这也是正是我们推出Tinker的原因。

总的来说:

AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的;

Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案;

Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。

特别是在Android N之后,由于混合编译的inline策略修改,对于市面上的各种方案都不太容易解决。而Tinker热补丁方案不仅支持类、So以及资源的替换,它还是2.X-7.X的全平台支持。利用Tinker我们不仅可以用做bugfix,甚至可以替代功能的发布。Tinker已运行在微信的数亿Android设备上,那么为什么你不使用Tinker呢?

Tinker的已知问题

由于原理与系统限制,Tinker有以下已知问题:

1.Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件;

2.由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;

3.在Android N上,补丁对应用启动时间有轻微的影响;

4.不支持部分三星android-21机型,加载补丁时会主动抛出   "TinkerRuntimeException:checkDexInstall failed";

5.由于各个厂商的加固实现并不一致,在1.7.6以及之后的版本,tinker不再支持加固的动态更新;

6.对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。

Tinker的demo

1.先去Github上下载Tinker源码,里面有tinker-sample-Android.

 

下好demo后,可能大家会迫不及待(其实就是我)跑起来,然后就回报这个错Error:Execution failed for task ':app:tinkerProcessDebugManifest'.

> tinkerId is not set!!!

 

wtf!然后赶紧百度,去查找wiki,哦哦,原来问题出在这里

tinker的官方文档这么写,当时我是懵圈的。

tinkerId is not set:这是因为没有正确的配置IDEgit路径若不是通clone方式下tinker,需要本地手commit一次里你也可以使用其他字符作tinkerId;

不过简单,网上肯定给出了答案:

String gitRec =”自己定义一个字符串”,不过建议这样写:(用你项目的versionName,也可以配置git路径)

 

好了,这个问题搞定了,那我们再来跑一次,现在是见证奇迹的时。。。wtf,什么鬼啊。。。我要不要面子的啊。

 

 

Error:A problem occurred configuringproject ':app'.

> Failed to notify project evaluationlistener.

  > Tinker does not support instant run mode, please trigger build byassembleTestDebug or disable instant run in 'File->Settings...'.

  > Task with name 'tinkerPatchTestRelease' not found in project':app'.

奔溃中。。

这个错误是说tinket不支持install run 模式 ,请手动build assembleDebug或者把 install run模式禁用掉。

其实只要把 install run模式关闭了,然后就可以运行成功了,如下图:(什么?install run怎么关?看这里:http://blog.csdn.net/u011663865/article/details/5310403

 

 

 

哇,终于成功了。

 

2验证热修复功能

 

我在MainActivity的布局中加了一个Button:

 

<Button
   
android:id="@+id/btn_cool"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"
   
android:layout_alignParentLeft="true"
   
android:layout_alignParentStart="true"
   
android:layout_below="@+id/killSelf"
   
android:text="我很帅吧?"/>

 

findViewById(R.id.btn_cool).setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View view) {        Toast.makeText(view.getContext(), "不存在的", Toast.LENGTH_LONG).show();    }});

 

点击按钮,弹出toast。嗯,很明显这个是很严重的bug(纯娱乐,这里只是为了做演示)

 

假设这个是已经发布的版本,用户体验非常差,老板要你在不更新app下,修复这个bug,那改怎么办?来用tinker搞定

 

首先,我们需要生存一个基础包(也就是old apk)

在Android Studio右侧Gradle的Gradle projects下点击assembieDebug,等待编译debug包(如图)

编包成功后,会在app/build/bakApk文件下生成基础包和R文件(如图)

app-debug-0324-10-50-29.apk就相当于当前用户使用的APK(old apk),后面生成patch apk都是基于old apk和new apk的不同(diff)生成的。

app-debug-0324-10-50-29-R.txt当前APK的R文件,也是用于生成patch apk的

 

然后需要在app的gradle下配置基础包和R文件(如图),把我们需要的tinkerOldApkPath设置成上面介绍的app-debug-0324-10-50-29.apk,tinkerApplyResourcePath设置成app-debug-0324-10-50-29-R.txt

而tinkerApplyMappingPath指的是mapping 文件,也就是你项目的混淆文件。如果你项目开了混淆,那就把生存的mapping文件配置上,这样不仅能减少你补丁包的大小,也能加快你补丁包打到主程序的速度

tinkerBuildFlavorDirectory指的是多渠道打包,这里先不做解释,下面有详细说明。这两个地方可以先忽略,不去配置。

 

到这里,基本的配置就介绍完了,现在我们需要去修复我们的bug,把toast中的文字改成“嗯,帅炸了!”(代码就不贴了)

 

要想热修复成功,首先要有patch apk,这个patch apk是根据old apknew apk通过diff算法得出的,老版本(old apk)bug,然后我们我们通过代码把这个bug修复了(new apk),通过Tinker diff算法就得出了patch apk,然后oldapk(也就是用户正在使用的版本)从远程服务器下载这个patch apk然后通过TinkeronReceiveUpgradePatch方法实现热修复,如下所示:

TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");    }});

 

既然知道修复的方式,那就来生成下patch包吧,如下图所示,双击tinkerPatchDebug,等待编译成功。

 

 

build成功后会在app/outpus/tinkerPatch/debug/下生成patch_signed_7zip.apk文件,如图所示:

 

然后我们只要把patch_signed_7zip.apk文件放到手机的根目录下,然后点击按钮LOAD PATCH,不出意外的话,过一会儿就会弹Toast(patchsuccess,please restart process),然后杀死进程,重新进入app,点击Button(我很帅吧?),弹出Toast:

到这里,通过Tinker实现了热修复功能

 

 

Tinker的接入

gradle接入

gradle是推荐的接入方式,在gradle插件tinker-patch-gradle-plugin中我们帮你完成proguard、multiDex以及Manifest处理等工作。

添加gradle依赖

在项目的build.gradle中,添加tinker-patch-gradle-plugin的依赖

buildscript {

    dependencies {

        classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.7')

    }

}

然后在app的gradle文件app/build.gradle,我们需要添加tinker的库依赖以及apply tinker的gradle插件.

dependencies {

    //可选,用于生成application

    provided('com.tencent.tinker:tinker-android-anno:1.7.7')

    //tinker的核心库

    compile('com.tencent.tinker:tinker-android-lib:1.7.7')

}

...

...

//apply tinker插件

apply plugin: 'com.tencent.tinker.patch'

 

Pactch生成

Patch生成有两种方式:

*基于命令行的方式。

*gradle编译的方式。

1.gradle编译生成patch

 微信Tinker的gradle配置可以参照demo,具体参数请先浏览Tinker接入指南,点击查看,同时这里附上一份相对完整的gradle配置。

 

Api引入

自定义Application类

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

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

将我们自己Application类以及它的继承类的所有代码拷贝到自己的ApplicationLike继承类中,例如SampleApplicationLike。你也可以直接将自己的Application改为继承ApplicationLike;

Application的attachBaseContext方法实现要单独移动到onBaseContextAttached中;

对ApplicationLike中,引用application的地方改成getApplication();

对其他引用Application或者它的静态对象与方法的地方,改成引用ApplicationLike的静态对象与方法;

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

这个是官方文档说的。具体的做法可以参照如下几步:

第一步:

       可以把demo中一些需要的类拷贝到自己的工程中,放在一个文件夹中如图:

      

第二步:修改SampleApplicationLike中生成Application的名字或者包名。

build后会在build/generated/souce/apt/文件下生成你定义的Application,如图:

第三步:如果我们项目中已经存在了自己定义的Application了,该怎么办呢?

直接让你自己定义的Application继承自Tinker生成的O2oInApplication即可,如果找不到这个类,请rebuild下。

 

第四步:

 我们需要在AndroidManifest.xml清单文件中配置Tinker生成的Application(O2oInApplication)

 

同时需要配置Tinker Service:

写完后如果报红,此时只需要Build下即可解决报红。Application配置就到此结束。接下来生成patch文件。因为patch文件是写入到SDCrad的,所以我们需要在AndroidManifest中添加如下权限(注: 6.0及已上系统请动态设置权限或者手动在设置中为项目设置):

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

 

 

具体生成patch步骤和demo中演示是一样的,这里不重复演示。

 

多渠道打包

官方文档也对这个进行了说明:如下

 

关于渠道包的问题,若使用flavor编译渠道包,会导致不同的渠道包由于BuildConfig变化导致classes.dex差异。这里建议的方式有:

将渠道信息写在AndroidManifest.xml或文件中,例如channel.ini

将渠道信息写在apk文件的zip comment中,种是建方式,例如可以使用packer-ng-plugin或者可使用V2 Schemewalle

若不同渠道存在功能上的差异,建将差异部分放于独的dex或采用相同代不同配置方式实现

事实上,tinker也支持多flavor直接编译多个补丁包,具体可参考Flavor打包

这里主要演示tinker支持的多flavor直接编译多个补丁包

通过flavor编译,这个时候我们可以看到bakApk路径是一个按照flavor名称区分的目录;

我这里只是做了一个渠道,大家可以测试多个。

将编译目录路径填写到gradletinkerBuildFlavorDirectory,其他的几个字段不需要填写,这里会自动根据路径拼接;

3.运行tinkerPatchAllFlavorDebug或者tinkerPatchAllFlavorRelease即可得到所有flavor的补丁包。

最后会生成多了补丁包,根据你渠道名称新建文件夹,放在其中

到这里多渠道打包就完成了。虽然简单,但是大家可以了解下多渠道打包的过程,以及我打包中遇到的问题

我在打多渠道补丁包的时候出现了这样的一个问题

导致这个错误的我在release中重新定义了apk打包后的名称:

所以在bakApk和outputs/apk下apk的名称会变成我定义的包命TosO2O_v2.5.0.apk

在多渠道编包的时候会执行下面的代码

这里会判断是否存在多渠道,然后根据不同渠道的名称在bakApk下输出不同的渠道的R文件和apk包,其中apk的名称会根据outputs文件下apk的名称进行替换。所以我们可以在bakApk下看到Tos020_v2.5.0.apk的。重点来了,既然替换了,为什么还是会报错呢?好了,让我们看下面的代码

运行tinkerPatchAllFlavorDebug或者tinkerPatchAllFlavorRelease时候查询文件其实是走了这两段代码。其中红框中的代码就是那到你要编译补丁包的old-Apk,R文件,mapping文件。但是这里的这行代码:

 

project.tinkerPatch.oldApk ="${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"

 

规定了old-Apk的名称和路径,和我们自己定义的apk名称不一样,所以报了

的错误。到这里我们了解了多渠道编包的流程,也就找到了问题所在,只要我们把上面那行代码修改成

 

project.tinkerPatch.oldApk ="${originOldPath}/${flavorName}/TosO2O_v2.5.0.apk"

 

其实也就是报apk名称改成我们自己的。就ok

重新编译,问题解决。

这里主要阐述tinker的集成和编译,下篇文章将对tinker的源码进行浅析


0 0
原创粉丝点击