Tinker热修复初探

听说热修复已经很久了,但这是第一次尝试去应用它。所以我对其它各种热修复也没什么了解,这里仅仅记下如何使用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项目地址

官方接入指南

官方介绍以及问题汇总

猜你喜欢

转载自blog.csdn.net/chenrenxiang/article/details/54962624