【Android】使用Bugly快速接入Tinker热更新功能

前言

公司项目的Bug、Crash统计一直用的的腾讯的 Bugly ,今日发现多了一个热更新(HotFix)功能,使用的技术是微信的方案 Tinker ,再三考虑后决定接入这个功能。

Tinker介绍

Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。

Tinker与其他热更新插件的比较:

其他关于Tinker的介绍请看Tinker的 wiki

开始接入

我们选择Tinker的道理很简单:稳定、开发透明。
而我们选择通过Bugly来接入Tinker,因为它简单,不需要我们再提供补丁下载接口和补丁管理后台。

1、在项目的根build.gradle中添加依赖
// tinker gradle插件
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.5')
// tinkersupport插件
classpath "com.tencent.bugly:tinker-support:latest.release"复制代码
2、在项目主module的build.gradle中添加(可先原样复制)
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")

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-release.apk"
    // proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/mapping.txt"
    // resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/R.txt"
    // only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/app-1124-23-03-52"
}

def getOldApkPath() {
    return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
}

def getApplyMappingPath() {
    return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
}

def getApplyResourceMappingPath() {
    return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
}

def getTinkerIdValue() {
    return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
}

def buildWithTinker() {
    return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}

def getTinkerBuildFlavorDirectory() {
    return ext.tinkerBuildFlavorDirectory
}

// 注意:必须要配置tinker-support
tinkerSupport {
    enable = true
}

// 全局信息相关配置项
tinkerPatch {
    oldApk = getOldApkPath()  //打补丁包时必选, 基准包路径

    ignoreWarning = false // 可选,默认false
    useSign = true // 可选,默认true, 验证基准apk和patch签名是否一致

    // 编译相关配置项
    buildConfig {
        applyMapping = getApplyMappingPath() //  可选,设置mapping文件,建议保持旧apk的proguard混淆方式
        applyResourceMapping = getApplyResourceMappingPath()      // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
        tinkerId = "v1.0.0"  // 必选,当前版本的唯一标识,可以是git版本号、versionName
    }

    // dex相关配置项
    dex {
        dexMode = "jar" // 可选,默认为jar
        usePreGeneratedPatchDex = false // 可选,默认为false
        pattern = ["classes*.dex",
                   "assets/secondary-dex-?.jar"]
        loader = ["com.tencent.tinker.loader.*",  // 必选
                  "com.huidr.patient.HuiTinkerApplication",
        ]
    }

    // lib相关的配置项
    lib {
        pattern = ["lib/armeabi/*.so"]
    }

    // res相关的配置项
    res {
        pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = ["assets/sample_meta.txt"]
        largeModSize = 100
    }

    // 用于生成补丁包中的'package_meta.txt'文件
    packageConfig {
        configField("patchMessage", "tinker is sample to use")
        configField("platform", "all")
        configField("patchVersion", "1.0")
    }

    // 7zip路径配置项,执行前提是useSign为true
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10" // optional
    }
}复制代码
3、创建Application的代理类(可直接复制)

我们之前的自定义Application类现在是不能直接继承Application了,需要改成继承Tinker提供的DefaultApplicationLike类

/**
 * Application代理类
 */
public class ApplicationDelegate extends DefaultApplicationLike {

    public ApplicationDelegate(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent, resources, classLoader, assetManager);
    }

    @Override
    public void onCreate() {
        super.onCreate();

        if (BuildConfig.RELEASE) {
            Bugly.init(getApplication(), BuildConfig.buglyAppId, false);
        } else {
            Bugly.init(getApplication(), BuildConfig.buglyAppId, true);
        }
    }


    @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);
        // 安装tinker
        Beta.installTinker(this);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
        getApplication().registerActivityLifecycleCallbacks(callbacks);
    }
}复制代码

这个代理的Application类的时候方法和原来一样,只不过它的调用是由Tinker来调用了,而这个调用者就是我们即将要定义的Application。

定义TinkerApplication
/**
 * TinkerApplication.
 * <p>
 * 注意:这个类集成TinkerApplication类,这里面不做任何操作,所有Application的代码都会放到ApplicationLike继承类当中<br/>
 * <pre>
 * 参数解析:
 * 参数1:int tinkerFlags 表示Tinker支持的类型 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
 * 参数2:String delegateClassName Application代理类 这里填写你自定义的ApplicationLike
 * 参数3:String loaderClassName  Tinker的加载器,使用默认即可
 * 参数4:boolean tinkerLoadVerifyFlag  加载dex或者lib是否验证md5,默认为false
 * </pre>
 */
public class HuiTinkerApplication extends TinkerApplication {

    public HuiTinkerApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "com.huidr.patient.ApplicationDelegate",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }

}复制代码

这里构造方法里的"com.huidr.patient.ApplicationDelegate"改成刚刚定义的Application代理类。
同时在AndroidMenifest.xml中把Application改成新定义的Appcation。
到这里,配置已经完成了。

打包基线版本

回到项目主module的build.gradle中,找到:

// dex相关配置项
    dex {
        dexMode = "jar" // 可选,默认为jar
        usePreGeneratedPatchDex = false // 可选,默认为false
        pattern = ["classes*.dex",
                   "assets/secondary-dex-?.jar"]
        loader = ["com.tencent.tinker.loader.*",  // 必选
                  "com.huidr.patient.HuiTinkerApplication",
        ]
    }复制代码

将这里配置的Applicaiton改成你刚刚创建的。

现在可以打包出ralease版本,得到app-release.apk文件,将其运行起来。

生成热更新补丁

现在主要关注这部分配置就可以了

 // 编译相关配置项
    buildConfig {
        applyMapping = getApplyMappingPath() //  可选,设置mapping文件,建议保持旧apk的proguard混淆方式
        applyResourceMapping = getApplyResourceMappingPath()      // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
        tinkerId = "v1.0.0"  // 必选,当前版本的唯一标识,可以是git版本号、versionName
    }复制代码


在build文件夹下创建bakApk文件夹(文件夹的名称在配置中可以修改),并把app-release.apk、mapping.txt文件、R.txt文件复制进来。
Mapping文件位置:app\build\outputs\mapping\release\mapping.txt
R.txt文件位置:app\build\intermediates\symbols\release\R.txt

最后修改tinkerId(当前版本的唯一标识,可以是git版本号、versionName)

运行后得到补丁app\build\outputs\patch\release\patch_signed_7zip.apk

上传补丁并下发

这里最好把patch_signed_7zip.apk文件扩展名改成.zip,防止有些手机厂商对apk文件劫持。
打开Bugly热更新后台界面,选择发布补丁,并下发

现在将App退出重新打开2次,就可以看到更新后的结果了
第一次打开会监测补丁版本;
第二次打开会安装新补丁。

ps:如果发现补丁有问题,还可以使用撤回功能,让App回复补丁前的样子。

结尾

感谢在一线上为热更新奋战的程序员,这是一条跪着走完的道路,正是有了他们背后的默默付出,我们才能这么方便地用上热更新功能,致敬!

以上集成过程主要参考以下文档:
Bugly Android热更新使用指南
Tinker 接入指南

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值