Android 热修复技术 Tinker 在 Android 中的实践
欢迎转载,转载请标明出处:
http://blog.csdn.net/johnny901114/article/details/54934782
本文出自:【余志强的博客】
Tinker初体验
先到Github上下载Tinker源码,里面包含了tinker-sample-android
,使用AndroidStudio导入该例子工程即可。
导入工程后,运行程序 ,出现如下错误:
Error:A problem occurred configuring project ':app'.
> Tinker does not support instant run mode, please trigger build by assembleDebug or disable instant run in 'File->Settings...'.
意思是说Tinker不支持 install run 模式,请手动 build assembleDebug 或者把 install run 模式禁用掉。
我把 install run 模式关闭了,然后运行成功了。如下图所示:
为了验证热修复功能
,我在MainActivity的不布局里加一个Button:
<Button
android:id="@+id/division"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/showInfo"
android:text="division"/>
然后监听Button的点击事件,在onClick方法里做除法运算(1/0),当我们点击Button的时候肯定会闪退,因为除数不能为0
:
findViewById(R.id.division).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "" + (1 / 0), Toast.LENGTH_LONG).show();
}
});
在MainActivity添加完代码后,重新运行,效果图如下:
点击我们新添加的Button(DIVISION),闪退报错:
java.lang.ArithmeticException: divide by zero
at tinker.sample.android.app.MainActivity$6.onClick(MainActivity.java:114)
at android.view.View.performClick(View.java:5207)
at android.view.View$PerformClick.run(View.java:21177)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5438)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:629)
假设这个版本已经发布到了市场,用户安装使用了,我们如何不更新App的情况下,修复这个Bug呢?接下来Tinker就闪亮登场了。
在我们你运行该工程的时候,Tinker会在app/build/bakAPK
目录下生成类似下面的文件:
- app-debug-0207-15-28-17.apk
- app-debug-0207-15-28-17-R.txt
如下图所示:
app-debug-0207-15-28-17.apk
就相当于用户正在使用的APK(old apk),后面用到的patch apk都是基于old apk和new apk的不同(diff)生成的。
app-debug-0207-15-28-17-R.txt
R.txt 也是用于生成patch apk的。
介绍完了app/build/bakAPK
下的两个文件,还需要配置app/gradle.build
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 = "${bakPath}/app-debug-0207-15-28-17.apk"
//proguard mapping file to build patch apk
tinkerApplyMappingPath = "${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-0207-15-28-17-R.txt"
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
如上面的配置所示,我们需要把tinkerOldApkPath
设置为上面介绍的app-debug-0207-15-28-17.apk
tinkerApplyResourcePath
设置为app-debug-0207-15-28-17-R.txt
好了,最基本的配置就介绍到这里了,现在我们来修复一下上面除数为0
的bug,很简单,直接让出一个不为零的除数就可,代码如下所示:
findViewById(R.id.division).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "" + (1 / 1), Toast.LENGTH_LONG).show();
}
});
如果热修复成功,不出意外的话,当我们点击按钮的的时候 ,应该会弹出Toast显示1。
要想热修复成功,首先要有patch apk,这个patch apk是根据old apk和new apk通过diff算法得出的,老版本(old apk)有bug,然后我们我们通过代码把这个bug修复了(new apk),通过Tinker diff算法就得出了patch apk,然后old apk(也就是用户正在使用的版本)从远程服务器下载这个patch apk然后通过Tinker的onReceiveUpgradePatch方法实现热修复,如下所示:
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
为了简单起见,这个patch apk我就不真的从服务器下载了,就直接放到本地了。
接下来点击Android Studio右侧的gradle
菜单,找到tinkerPatchDebug
双击它,如下图所示:
build完成之后会在app/outpus/tinkerPatch/debug/
下生成patch_signed_7zip.apk
文件,如下所示:
根据上面的代码
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
我们只要把patch_signed_7zip.apk
文件放到SD卡的根目录就可以了,然后点击按钮LOAD PATCH
,如果 不出意外的话,一会就会弹出Toast(patch success,please restart process
)
然后杀死进程,重新进入app,点击Button(DIVISION),弹出Toast:
至此,通过Tinker实现了热修复功能。
Tinker实战
上面是我们直接运行Tinker官方的sample对Tinker的使用有了个了解,现在如何在一个全新的工程使用Tinker。就以我以前做过的一个项目为例,该项目是我最近一个公司的产品,主要是做艺术品电商这块的(出现了一个小bug),当我进入艺术品详情后界面出弹出拒绝访
问的Toast,如下图所示:
通过查看代码发现犯了一个低级错误,因为在界面里需要访问购物车的数量,然后显示在界面购车图标的右上角,忘记了判断用户是否登录,竟然犯了这种低级错误,真是老脸一红啊,但是毕竟是自己的错误,就要大胆的承认,知耻而后勇,哈哈。有bug的代码如下(再调用API之前应该判断用户是否登录):
@Override
public void onResume() {
super.onResume();
//if (UserManagerControl.isLogin()) {应该加上判断
orderController.getShoppingCartCount();
//}
}
要想加入热修复功能,
第一步,加入Tinker的依赖包,我直接参考tinker-sample-android
app下的build.gralde配置,把主要的拷贝到我们的build.gradle. 这个非常简单,配置比较多,我就不再这里贴出来了。注意一定要记得修改ext下的 tinkerOldApkPath
tinkerApplyResourcePath
等属性。
第二步,把工程目录下的build.gradle加入Tinker plugin:
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
其中TINKER_VERSION
是在gradle.properties文件里配置的:
TINKER_VERSION=1.7.7
第三步,把一些需要的类拷贝到自己的工程,我新建了一个tinker package专门放tinker相关的类,如下图所示:
第四步,通过上面的tinker-sample-android
的例子,我们知道我们需要在AndroidManifest.xml
清单文件中配置Tinker生成的Application(SampleApplication),如下所示:
<application
android:name=".app.SampleApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"/>
如果我们要修改这个生成的Application的名字或者报名,怎么修改呢?找到SampleApplicationLike把他的注解修改下即可:
@SuppressWarnings("unused")
@DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class SampleApplicationLike extends DefaultApplicationLike
现在是在自己的项目中使用,首先Application的包名肯定要换的,我改成了com.hoolay.app.SampleApplication
我的项目中本来就存在了自己定义的Application(HoolayApplication)了,那怎么办呢?
直接让HoolayApplication继承自Tinker生成的SampleApplication即可。可能找不到SampleApplication这个类,需要重新rebuild一下,这个类就自动生成了。
配置完了AndroidManifest.xml
里的Application,记得配置Tinker Service:
<!--Tinker -->
<service
android:name="com.hoolay.tinker.SampleResultService"
android:exported="false"/>
到这里为止,我们的项目就已经把Tinker热修复的功能集成进来了。就下来就是测试了。
现在有bug的版本,已经上线了,用户正在使用,现在把这个bug修复一下(很简单加上判断即可):
if (UserManagerControl.isLogin()) {
orderController.getShoppingCartCount();
}
为了不修改布局,我直接在点击购物车
的时候加载patch了,便于测试用Toast提示:
String patchPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
File patchFile = new File(patchPath);
if (patchFile.exists() && patchFile.length() > 0) {
ToastUtils.showLongToast(getContext(),"正在合并热修复文件,请耐心等待");
TinkerInstaller.onReceiveUpgradePatch(getContext().getApplicationContext(),
patchPath);
}
要生成一个热修复的包(patch apk),怎么生成上面已经介绍了。然后把生成的patch_signed_7zip.apk
放到SD 卡根目录去。
进入艺术品详情提示拒绝访问
,然后点击底部的购物车
按钮,提示正在合并热修复文件,请耐心等待
,等待一段时间后,提示patch success,please restart process
,流程如下图所示:
最后呢,我们重启App 进程,进入艺术品详情
发现不会弹出拒绝访问
提示,这说明热修复成功了。
至此,我们就把一个没有Tinker热更新的项目,改造成支持热更新功能了。
需要注意的是,一般正式的项目不会这样去需要用户点击某个按钮来加载修复包(patch apk),而是用户一开始进入我们app的时候,第一个界面就应该去服务器访问(一般一个App中都会有一个接口,用来获取服务器给客户端的全局配置)。
假设服务器返回有更新的包,应该就应该弹框
去下载patch包,这个时候用户是什么都做不了的,最好加个进度条。当下载完成后,然后合并patch包,最后在跳到主界面。
最好使用HTTPS保证数据的安全,当我们访问服务器是否有修复包的时候,如果有,服务器应该返回修复包patch的下载地址和文件的MD5值,然后客户端下载完成后对文件进行MD5,然后和服务器的返回的MD5值进行对比看是否一致,因为文件可能被篡改。
如果你觉得本文帮助到你,给我个关注和赞呗!
另外,我为 Android 程序员编写了一份:超详细的 Android 程序员所需要的技术栈思维导图。
如果有需要可以移步我的 GitHub -> AndroidAll,里面包含了最全的目录和对应知识点链接,帮你扫除 Android 知识点盲区。 由于篇幅原因只展示了 Android 思维导图:
更多推荐
所有评论(0)