听说热修复已经很久了,但这是第一次尝试去应用它。所以我对其它各种热修复也没什么了解,这里仅仅记下如何使用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项目可以参考。
官方sample项目地址
官方接入指南
官方介绍以及问题汇总
新闻热点
疑难解答