热修复技术就是不重新启动APP和用户无感知的情况下,对应用进行增量的补丁更新。好处就不说了,好处太多了。即便有些功能无法做到不重启应用,但是这种增量只更新补丁包的方式还是很赞,只需要应用冷启动一下就自动完成了更新包的加载。至少不用再升级发布一次了。
Android的热修复框架有很多。如阿里的andfix,hotfix,微信的tinker和美团的Robust等。
要说哪个最简单最好用,阿里的Hotfix简直是傻瓜式接入,文档和教程也很详细,连打包工具都给你可视化了。还配备了开发提调试工具,让你本机就能验证下补丁包。不得不服阿里系对开发者也考虑的真周到啊。
以下以AndroidStudio3.2的集成为例:
阿里热修复Sophix已全面升级至3.0,可以用原先的-百川账号-直接登录阿里云移动热修复
Sophix即阿里的hotfix,最新版改名字了,改为Sophix了。
Sophix提供了一套更加完美的客户端服务端一体的热更新方案,做到了图形界面一键打包、加密传输、签名校验和服务端控制发布与灰度功能,让你用最少的时间实现最强大可靠的全方位热更新。
新版本的功能更强大,更好用。以下介绍下最新版本3.2的接入方式:
android studio集成方式也很简单:
在项目的gradle文件里,添加emas-services插件和maven仓库地址。
例如:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/releases/'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
// 添加emas-services插件
classpath 'com.aliyun.ams:emas-services:1.0.1'
//classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/releases/'
}
flatDir {
dirs 'libs'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
如:
然后在App目录的gradle文件里,添加依赖和插件:
apply plugin: 'com.aliyun.ams.emas-services'
//阿里hotfix热补丁更新
implementation 'com.aliyun.ams:alicloud-android-hotfix:3.2.12'
例如:
apply plugin: 'com.android.application'
apply plugin: 'com.aliyun.ams.emas-services'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.xxx.xxxx"
minSdkVersion 22
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 22
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
java {
srcDir 'src/main/java'
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
ext {
rxjavaVersion = '2.0.1'
}
repositories {
maven {
url "http://maven.aliyun.com/nexus/content/repositories/releases"
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//RxJava
implementation "io.reactivex.rxjava2:rxjava:$rxjavaVersion"
implementation "io.reactivex.rxjava2:rxandroid:$rxjavaVersion"
//json
implementation 'com.google.code.gson:gson:2.8.0'
api 'com.alibaba:fastjson:1.2.31'
//日志框架 自动写入文件
implementation 'com.github.hijesse:android-logger:2.0.0'
//protobuf
// implementation "com.google.protobuf:protobuf-java:3.1.0"
// implementation ('com.squareup.retrofit2:converter-protobuf:2.2.0') {
// exclude group: 'com.google.protobuf', module: 'protobuf-java'
// }
//屏幕自动适配
//implementation 'me.yatoooon:screenadaptation:1.0.3'
//sys
implementation(name: 'b601sys', ext: 'aar')
implementation files('libs/b601auxlib.jar')
//阿里hotfix热补丁更新
implementation 'com.aliyun.ams:alicloud-android-hotfix:3.2.12'
}
//构建task
//protobuf {
// protoc {
// artifact = 'com.google.protobuf:protoc:3.1.0'
// }
//
// generateProtoTasks {
// all().each { task ->
// task.builtins {
// remove java
// }
// task.builtins {
// java {}
// // Add cpp output without any option.
// // DO NOT omit the braces if you want this builtin to be added.
// }
// }
// }
// //生成目录
// generatedFilesBaseDir = "$projectDir/src/generated"
//}
以上完成了gradle的配置。gradle就这些了。
接着是AndroidManifest.xml文件中,增加几个meta属性和指定初始的Application.
在Application的标签下增加以下属性:
<meta-data
android:name="com.taobao.android.hotfix.IDSECRET"
android:value="AppID" />
<meta-data
android:name="com.taobao.android.hotfix.APPSECRET"
android:value="AppSecret" />
<meta-data
android:name="com.taobao.android.hotfix.RSASECRET"
android:value="RSAkey" />
Sophix SDK使用到以下权限
<! -- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<! -- 外部存储读权限,调试工具加载本地补丁需要 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
READ_EXTERNAL_STORAGE权限属于Dangerous Permissions,仅调试工具获取外部补丁需要,不影响线上发布的补丁加载,调试时请自行做好android6.0以上的运行时权限获取。
然后指定初始的Application为阿里hotfix指定的入口类:
这个SophixStubApplication类,直接拷贝进去即可。几乎零改动。这里可以看出来,阿里的hotfix对你的代码几乎是零侵入的。
这个类加进来,里面只改一个地方,把你的applicatin类指定进去即可,就完成了所有的集成了。
注意混淆配置,如果代码里启用的有混淆。需配置下混淆配置。混淆的配置,参照最新文档。
需要注意的是,这个类里面的
setSecretMetaData为设置AndroidManifest.xml中的那几项保密的参数的。
分别是idSecret, appSecret, rsaSecret。
这三个参数从哪来呢?需要你注册并登陆上阿里云。从上面创建应用,会给你分配的。记下来即可,保存好。
应用版本要和app的版本一致。
这个虽说是收费的,但是每个账号每个月可以有5万台设备接入的免费使用额度,超过了才收费的。
完成了以上这些,算是集成和接入成功了。接下来说下如何使用吧,来验证下热更新的功能。
patch补丁包生成需要使用到打补丁工具SophixPatchTool, 如还未下载打包工具,请前往下载Android打包工具。
下载打包工具:
patch补丁包生成需要使用到打补丁工具SophixPatchTool, 如还未下载打包工具,请前往下载Android打包工具。
Mac版本打包工具地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/SophixPatchTool_macos.zip
Windows版本打包工具地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/SophixPatchTool_windows.zip
Linux版本打包工具地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/SophixPatchTool_linux.zip
调试工具地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/hotfix_debug_tool-release.apk
调试工具也是有的,调试工具是可以把你的补丁包以本地的方式,或扫码下载的方式下载到你的应用所在的机器上,然后本地验证。
调试工具地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/hotfix_debug_tool-release.apk
下载下来安装到你的应用需要运行的设备上。
打包工具的使用还是很简单的,如下图,那么多key都咋填?如果你的应用只是debug模式,没签名,那么那几项可忽略。
如果你的应用签名了,那么那些key你自然是知道的。
点击生成,即生成了补丁包,补丁包不大,跟你的改动有关系吧。
我这个生成的sophix-patch.jar有9k。即便改动了一行代码也可能有这么大。因为有一些其它信心包含在内的。
补丁生成了,接下来验证一下吧,可以先不用把补丁包上送到阿里云,用那个调试工具就能验证。
先用adb push sophix-patch.jar /sdcard/ 把补丁包传到设备上去。
然后在设备上打开那个调试工具:
补丁工具生成的补丁包sophix-patch.jar推送到本地的/sdcard/Download目录, 然后输入该补丁包的绝对路径, 点击应用本地补丁按钮;
Mode:2 表示本地补丁模式, Code:1 表示加载成功 HandlePatchVersion:-1 表示本地补
扫描二维码示例
补丁工具生成的补丁包sophix-patch.jar上传到hotfix控制台;
点击扫描二维码按钮扫后台补丁二维码;
Mode:1 表示扫码模式, Code:12 表示应用当前已经有一个补丁, 所以新补丁不会立刻加载需要等下一次重启加载, HandlePatchVersion:89 表示后台拉取下来的补丁版本89。
注意的是,调试工具用于patch正式发布前的调试环节,目前有两种测试方式:
扫码二维码方式,将刚刚上传到后台的补丁通过扫描二维码下载到本地,尝试加载补丁;
应用本地补丁方式,传入本地补丁的绝对路径,尝试加载补丁。此方式必须确保Sophix初始化时setEnableDebug为true。出于安全考虑,我们禁止在setEnableDebug为false的包上加载本地补丁,因此setEnableDebug为false的包需要以二维码方式进行验证。
最后说下接口说明
initialize方法
initialize(): <必选>
该方法主要做些必要的初始化工作以及如果本地有补丁的话会加载补丁, 但不会自动请求补丁。因此需要自行调用queryAndLoadNewPatch方法拉取补丁。这个方法调用需要尽可能的早, 必须在Application的attachBaseContext方法的最前面调用(在super.attachBaseContext之后,如果有Multidex,也需要在Multidex.install之后), initialize()方法调用之前你需要先调用如下几个方法进行一些必要的参数设置, 方法调用说明如下:
setContext(application): <必选>
传入入口Application即可
setAppVersion(appVersion): <必选>
应用的版本号和创建补丁的版本号要一致,不然补丁没有用
setSecretMetaData(idSecret, appSecret, rsaSecret): <可选,推荐使用>
三个Secret分别对应AndroidManifest里面的三个,可以不在AndroidManifest设置而是用此函数来设置Secret。放到代码里面进行设置可以自定义混淆代码,更加安全,此函数的设置会覆盖AndroidManifest里面的设置,如果对应的值设为null,默认会在使用AndroidManifest里面的。
setEnableDebug(isEnabled): <可选>
isEnabled默认为false, 是否调试模式, 调试模式下会输出日志以及不进行补丁签名校验. 线下调试此参数可以设置为true, 查看日志过滤TAG:Sophix, 同时强制不对补丁进行签名校验, 所有就算补丁未签名或者签名失败也发现可以加载成功. 但是正式发布该参数必须为false, false会对补丁做签名校验, 否则就可能存在安全漏洞风险
setAesKey(aesKey): <可选>
用户自定义aes秘钥, 会对补丁包采用对称加密。这个参数值必须是16位数字或字母的组合,是和补丁工具设置里面AES Key保持完全一致, 补丁才能正确被解密进而加载。此时平台无感知这个秘钥, 所以不用担心阿里云移动平台会利用你们的补丁做一些非法的事情。
setPatchLoadStatusStub(new PatchLoadStatusListener()): <可选>
设置patch加载状态监听器, 该方法参数需要实现PatchLoadStatusListener接口, 接口说明见1.3.2.说明
setUnsupportedModel(modelName, sdkVersionInt):<可选>
把不支持的设备加入黑名单,加入后不会进行热修复。modelName为该机型上Build.MODEL的值,这个值也可以通过adb shell getprop | grep ro.product.model取得。sdkVersionInt就是该机型的Android版本,也就是Build.VERSION.SDK_INT,若设为0,则对应该机型所有安卓版本。目前控制台也可以直接设置机型黑名单,更加灵活。
queryAndLoadNewPatch方法
该方法主要用于查询服务器是否有新的可用补丁. SDK内部限制连续两次queryAndLoadNewPatch()方法调用不能短于3s, 否则的话就会报code:19的错误码. 如果查询到可用的话, 首先下载补丁到本地, 然后应用原本没有补丁, 那么如果当前应用的补丁是热补丁, 那么会立刻加载(不管是冷补丁还是热补丁). 如果当前应用的补丁是冷补丁, 那么需要重启生效。
应用原本没有补丁, 那么如果当前应用的补丁是热补丁, 那么会立刻加载(不管是冷补丁还是热补丁). 如果当前应用的补丁是冷补丁, 那么需要重启生效。
应用已经存在一个补丁, 请求发现有新补丁后,本次不受影响。并且在下次启动时补丁文件删除, 下载并预加载新补丁。在下下次启动时应用新补丁。
补丁在后台发布之后, 并不会主动下行推送到客户端, 需要手动调用queryAndLoadNewPatch方法查询后台补丁是否可用.
只会下载补丁版本号比当前应用存在的补丁版本号高的补丁, 比如当前应用已经下载了补丁版本号为5的补丁, 那么只有后台发布的补丁版本号>5才会重新下载.
同时1.4.0以上版本服务后台上线了“一键清除”补丁的功能, 所以如果后台点击了“一键清除”那么这个方法将会返回code:18的状态码. 此时本地补丁将会被强制清除, 同时不清除本地补丁版本号。
killProcessSafely方法
可以在PatchLoadStatusListener监听到CODE_LOAD_RELAUNCH后在合适的时机,调用此方法杀死进程。注意,不可以直接Process.killProcess(Process.myPid())来杀进程,这样会扰乱Sophix的内部状态。因此如果需要杀死进程,建议使用这个方法,它在内部做一些适当处理后才杀死本进程。
cleanPatches()方法
清空本地补丁,并且不再拉取被清空的版本的补丁。正常情况下不需要开发者自己调用,因为Sophix内部会判断对补丁引发崩溃的情况进行自动清空。
PatchLoadStatusListener接口
该接口需要自行实现并传入initialize方法中, 补丁加载状态会回调给该接口, 参数说明如下:
mode: 无实际意义, 为了兼容老版本, 默认始终为0
code: 补丁加载状态码, 详情查看PatchStatus类说明
info: 补丁加载详细说明
handlePatchVersion: 当前处理的补丁版本号, 0:无 -1:本地补丁 其它:后台补丁
更详细的参见博文:https://blog.csdn.net/ffhelly/article/details/80696202
https://www.jianshu.com/p/6ae1e09ebbf5
https://www.cnblogs.com/popfisher/p/8543973.html
以上就是对阿里的hotfix的试用。接下来会再试用下美团的Robust。据说也是很不错,开源免费。
Robust优缺点:
优点
- 高兼容性(Robust只是在正常的使用DexClassLoader)、高稳定性,修复成功率高达99.9%
- 补丁实时生效,不需要重新启动
- 支持方法级别的修复,包括静态方法
- 支持增加方法和类
- 支持ProGuard的混淆、内联、优化等操作
缺点
- 代码是侵入式的,会在原有的类中加入相关代码
- so和资源的替换暂时不支持
- 会增大apk的体积,平均一个函数会比原来增加17.47个字节,10万个函数会增加1.67M
摘录一篇网络总结,链接地址:https://www.cnblogs.com/popfisher/p/8543973.html
如果不考虑增大apk的体积,只是简单的修复代码,不修复so和资源,选择Robust是最稳定的,否则的话选择Tinker是一个不错的方案。虽然阿里Sophix横空出世,但是它不开源,而且商业收费,所以一般不是很赚钱的app选择收费的可能就很小了。不过它确实各方面都做了大量的优化,本文中的很多知识点也来源于阿里的《Android热修复技术原理.pdf》一书,本书值得一读,里面就是基于Sophix框架来编排的。
GitHub文章地址
https://www.jianshu.com/p/f2d8f7d11854