听说热修复已经很久了,但这是第一次尝试去应用它。所以我对其它各种热修复也没什么了解,这里仅仅记下如何使用Tinker热修复。
对于Tinker热修复的介绍和问题这里也不写了,因为官方文档已经有了,戳这里进入官方介绍文档 ,这里只记下如何在自己的项目中使用Tinker热修复。
步骤一、 通过gradle接入Tinker
在项目的build.gradle里添加gradle依赖:
buildscript {
repositories {
jcenter()
}
dependencies {
......
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.7')
}
}
在app的build.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'
步骤二、 拷贝sample项目中app/build.gradle配置文件相关内容
sample项目 app/build.gradle配置文件地址戳这里
里面的内容很多,有些内容也许用不上,可以删去。暂时可以把Tinker相关的配置内容全部拷贝。
这里有一个问题需要注意:build.gradle文件中有一个方法
def getTinkerIdValue() {
return "TestTinker" + android.defaultConfig.versionName + "_" + android.defaultConfig.versionCode;
//return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
}
这个方法生成TinkerId,TinkerId用来标识原始包和补丁包,也就是说给原始包打补丁的时候,原始包是通过这个TinkerId来找到补丁包的。所以这个值必须设置。
建议的设值方式是与app的版本号及版本名称关联起来。sample中原来的方法是通过git版本号生成,不太建议使用这种方式,因此配置中的getSha()方法也可以删除。
步骤三、 自定义Application类
自定义一个Application类,让他继承DefaultApplicationLike类,如下:别忘了顶部的注解哦
@DefaultLifeCycle(application = "com.fff.xiaoqiang.testtikner.theApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class MyApplication extends DefaultApplicationLike {
public MyApplication(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
//you must install multiDex whatever tinker is installed!
MultiDex.install(base);
TinkerInstaller.install(this,
new DefaultLoadReporter(base),
new DefaultPatchReporter(base),
new DefaultPatchListener(base),
TinkerResultService.class,
new UpgradePatch());
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
getApplication().registerActivityLifecycleCallbacks(callback);
}
}
其中,TinkerInstaller.install方法为初始化Tinker方法,它有两个构造方法,如下:
public static Tinker install(ApplicationLike applicationLike) {
......
}
public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,
AbstractPatch upgradePatchProcessor) {
......
}
最开始的时候我是使用第一个构造方法,只需要传入ApplicationLike这一个参数就可以了,也可以热修复成功,但是每次修复成功后app就会被杀掉。这是因为如果使用第一个构造方法,Tinker就会使用默认参数,从第二个构造方法可以看到除了ApplicationLike外初始化的时候还使用了其它五个参数,他们的五个默认类分别是:
DefaultLoadReporter、 DefaultPatchReporter、 DefaultPatchListener 、DefaultTinkerResultService 和 UpgradePatch
其中DefaultTinkerResultService中能接收到热修复的结果,查看这个类的源码可以发现,当修复成功后,就调用了杀掉进程的方法。因此如果不想把进程杀掉,就需要重写这个类,修改里面的方法。
步骤四、 重写DefaultTinkerResultService
因为我不希望修复成功后app立马闪退,所以重写了这个类,如下:
public class TinkerResultService extends DefaultTinkerResultService {
private static final String TAG = "TinkerResultService";
@Override
public void onPatchResult(PatchResult result) {
// super.onPatchResult(result); 把这行注释掉,屏蔽掉父类中的方法
if (result == null) {
Log.e(TAG, "TinkerResultService received null result!!!!");
return;
}
Log.e(TAG, "TinkerResultService receive result: %s" + result.toString());
//first, we want to kill the recover process
// TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
if(result.isSuccess){ //修复成功
Log.e(TAG,"修复成功");
deleteRawPatchFile(new File(result.rawPatchFilePath)); //删除补丁包
Log.e(TAG,"删除补丁");
}
}
}
步骤五、 别忽略了Manifest.xml文件
因为手动添加了Application和Service,所以需要在Manifest.xml文件中配置上去。注意application的 name别填错。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fff.xiaoqiang.testtikner">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:name=".theApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".TinkerResultService"
android:exported="false"/>
</application>
</manifest>
另外别忘了给app添加SD卡读写权限
代码配置完成之后,就可以开始打补丁包了
首先打基础包,也就是和我们平时打包一样,打包完成之后,会在项目文件的build文件夹下生成一个bakApk文件夹,里面有apk、txt等文件。把这些文件的全名称填入build.gradle配置文件中,如下:
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.apk"
//proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath}/app-debug-mapping.txt"
//resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath}/app-debug-R.txt"
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = "${bakPath}/app-0209-19-39-45"
}
tinkerOldApkPath 表示原始包路径,
tinkerApplyMappingPath 表示原始包mapping文件路径,没有的话可以不要管
tinkerApplyResourcePath 表示原始包资源文件路径
tinkerBuildFlavorDirectory 表示使用flavor多渠道打包时,原始包文件夹路径。如果使用了flavor多渠道打包,只需要填这一个路径就行,上面三个可以不要管。如果没有使用flavor多渠道打包,这个路径也可以不要管。
基础包打完,配置好build.gradle之后。可以试着去修改java文件中的代码,或者修改layout资源文件等,模拟修复原始包中的bug。修改完之后,便可以使用gradle打补丁包。
因为我使用了flavor多渠道打包,所以会有这么多内容。双击tinker下你需要的打补丁包版本(注意补丁包要和原始包版本一样),就会生成补丁包了。补丁包在项目文件中的路径为 app\build\outputs\tinkerPatch。 默认生成的补丁包扩展名是.apk,也可以改为其他类型。把补丁包放入手机sd卡下。
然后在app中触发热修复代码,如下:
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
其中第二个参数为补丁包的文件路径。注意,此方法必须在Tinker初始化完成之后调用。
一个思考: 在正式环境中,热修复应该是这样进行的。 后台应该提供一个接口,每次app启动的时候,访问这个接口看需不需要热修复,如果需要,后台要返回补丁包的下载地址。app获取到补丁包地址后开始下载补丁包到手机中,下载完成之后启动热修复,也就是执行上面那句代码。 修复完成之后,app需要重新启动,修复才会生效。
这是第一次使用热修复,对于Tinker也还有许多东西没弄清楚。要投入真正的使用也许还会遇到其它许多问题,比如apk混淆等。欢迎一起交流。
幸好官方文档已经算比较详细了,还有sample项目可以参考。