基于tinker的bugly的集成之路和总结

bugly的简单介绍

1、热更新能力是Bugly为解决开发者紧急修复线上bug,而无需重新发版让用户无感知就能把问题修复的一项能力。Bugly目前采用微信Tinker的开源方案,开发者只需要集成我们提供的SDK就可以实现自动下载补丁包、合成、并应用补丁的功能,我们也提供了热更新管理后台让开发者对每个版本补丁进行管理。
详细文档:https://bugly.qq.com/docs/user-guide/instruction-manual-android-hotfix/?v=20170912151050

image.png

2、bugly的有点:
无需关注Tinker是如何合成补丁的
无需自己搭建补丁管理后台
无需考虑后台下发补丁策略的任何事情
无需考虑补丁下载合成的时机,处理后台下发的策略
我们提供了更加方便集成Tinker的方式
我们通过HTTPS及签名校验等机制保障补丁下发的安全性
丰富的下发维度控制,有效控制补丁影响范围
我们提供了应用升级一站式解决方案

3、bugly的缺点:
无法即时生效,一定要冷启动
只支持部分加固(360)
升级和补丁同时下发,优先处理升级

接入流程

1、首先登陆地址:https://bugly.qq.com/v2/index, 注册登陆bugly控制台,创建自己的产品,获取appid (这个id后面会用到)。根据官方文档接入,基本上没什么大问题,我接入的是改造application,因为官方介绍说这种兼容性比较好,如果文档看的迷迷糊糊的话,里面也贴出视频地址,可以看着视频来接入更直观。

image.png

2、在app目录下创建tinker-support.gradle,里面需要注意的是打补丁包需要改动的是def baseApkDir = “基准包的目录”, tinkerId = “”;不过初试者的话建议看下视频,会更了解里面的参数含义。

apply plugin: 'com.tencent.bugly.tinker-support'

// 基准包目录
def bakPath = file("${buildDir}/bakApk/")

/**
 * 此处填写每次构建生成的基准包目录
 */
def baseApkDir = "app-1102-17-29-08"

/**
 * 对于插件各参数的详细解析请参考
 */
tinkerSupport {

    // 开启tinker-support插件,默认值true
    enable = true

    // 自动生成tinkerId, 你无须关注tinkerId,默认为false
    autoGenerateTinkerId = false

    // 指定归档目录,默认值当前module的子目录tinker
    autoBackupApkDir = "${bakPath}"

    // 是否启用覆盖tinkerPatch配置功能,默认值false
    // 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
    overrideTinkerPatchConfiguration = true

    // 编译补丁包时,必需指定基线版本的apk,默认值为空
    // 如果为空,则表示不是进行补丁包的编译,指定是debug apk 还是release apk
    // @{link tinkerPatch.oldApk }
    baseApk = "${bakPath}/${baseApkDir}/app-debug.apk"

    // 对应tinker插件applyMapping, 映射文件需要开启混淆才能生成
    // app gradle 里面的 minifyEnabled 设置true才能生成mapping混淆
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-debug-mapping.txt"

    // 对应tinker插件applyResourceMapping
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-debug-R.txt"

    // 构建基准包跟补丁包都要修改tinkerId,主要用于区分, 基准包:1.0.1-base, 补丁包:1.0.1-patch
    tinkerId = "patch-1.0.1"

    // 打多渠道补丁时指定目录
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

    // 是否使用加固模式,默认为false
    // isProtectedApp = true

    // 是否采用反射Application的方式集成,无须改造Application, 反射兼容性没有改造的好
    // 改造设置false, 试用反射设置true
    enableProxyApplication = false

}

/**
 * 一般来说,我们无需对下面的参数做任何的修改
 * tinkerPatch 主要是用来当enableProxyApplication = true的时候反射集成,如果false此段则不调用
 * 对于各参数的详细介绍请参考:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
    tinkerEnable = true
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }

    res {
        pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = []
        largeModSize = 100
    }

    packageConfig {
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
//        path = "/usr/local/bin/7za"
    }
    buildConfig {
        keepDexApply = false
//      tinkerId = "base-2.0.1"
    }
}

3、配置项目下的gradle 和 app 目录下的gradle
项目下的gradle, 以来 classpath 'com.tencent.bugly:tinker-support:1.0.8',这个版本可以根据自己需要选择一个稳定版本,也可以设置latest.release版本

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        // 只需配置tinker-support插件依赖,无需再依赖tinker插件
        classpath 'com.tencent.bugly:tinker-support:1.0.8'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app gradle: 这里面主要是添加依赖 compile 'com.tencent.bugly:crashreport_upgrade:latest.release'(应用升级和热更新都是用这个sdk), 不要忘了依赖下插件脚本 apply from: 'tinker-support.gradle', 至于其他的配置ndk里一般设置 abiFilters 'armeabi' , 'x86'这两个兼容够用了。我这里的是debug测试的,所以没有配release版,一般上线都会配置release版的。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.2"
    defaultConfig {
        applicationId "com.example.administrator.buglyupdatedemo"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        ndk {
            //设置支持的SO库架构
            abiFilters 'armeabi' , 'x86' //, 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        }

        multiDexEnabled true  // 分dex包
    }
    buildTypes {
        release {
            minifyEnabled false  // 开启混淆,生成mapping文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            debuggable true
            minifyEnabled false
            signingConfig signingConfigs.debug
        }
    }

    // 编译选项
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    // recommend
    dexOptions {
        jumboMode = true
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

    repositories {
        flatDir {
            dirs 'libs'
        }
    }

    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }

}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    //注释掉原有bugly的仓库
    //compile 'com.tencent.bugly:crashreport:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.3.2
    compile 'com.tencent.bugly:crashreport_upgrade:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.2.0
//    compile 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.2.0
    // 多dex配置
    compile "com.android.support:multidex:1.0.1"
}

// 依赖插件脚本
apply from: 'tinker-support.gradle'

4、改造application:新建一个MyApplicationLike 集成 DefaultApplicationLike oncreate方法中初始化bugly, onBaseContextAttached方法中安装MultiDex和tinker

package com.example.administrator.buglyupdatedemo;

import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.multidex.MultiDex;
import android.widget.Toast;

import com.tencent.bugly.Bugly;
import com.tencent.bugly.beta.Beta;
import com.tencent.bugly.beta.interfaces.BetaPatchListener;
import com.tencent.tinker.loader.app.DefaultApplicationLike;

import java.util.Locale;

public class MyApplicationLike extends DefaultApplicationLike{

    public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Beta.autoCheckUpgrade  = true;
        // 设置是否开启热更新能力,默认为true
        Beta.enableHotfix = true;
        // 设置是否自动下载补丁,默认为true
        Beta.canAutoDownloadPatch = true;
        // 设置是否自动合成补丁,默认为true
        Beta.canAutoPatch = true;
        // 设置是否提示用户重启,默认为false
        Beta.canNotifyUserRestart = true;
        // 补丁回调接口
        Beta.betaPatchListener = new BetaPatchListener() {
            @Override
            public void onPatchReceived(String patchFile) {
                Toast.makeText(getApplication(), "补丁下载地址" + patchFile, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadReceived(long savedLength, long totalLength) {
                Toast.makeText(getApplication(),
                        String.format(Locale.getDefault(), "%s %d%%",
                                Beta.strNotificationDownloading,
                                (int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)),
                        Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadSuccess(String msg) {
                Toast.makeText(getApplication(), "补丁下载成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadFailure(String msg) {
                Toast.makeText(getApplication(), "补丁下载失败", Toast.LENGTH_SHORT).show();

            }

            @Override
            public void onApplySuccess(String msg) {
                Toast.makeText(getApplication(), "补丁应用成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onApplyFailure(String msg) {
                Toast.makeText(getApplication(), "补丁应用失败", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onPatchRollback() {

            }
        };

        // 设置开发设备,默认为false,上传补丁如果下发范围指定为“开发设备”,需要调用此接口来标识开发设备
//        Bugly.setIsDevelopmentDevice(getApplication(), true);
        // 多渠道需求塞入
        // String channel = WalleChannelReader.getChannel(getApplication());
        // Bugly.setAppChannel(getApplication(), channel);
        // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
        // 调试时,将第三个参数改为true
        Bugly.init(getApplication(), "65663b32e8", true);  // 第二个参数就是上面提到的APPID, 第三个参数是是否开启debuglog模式
    }


    @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
        // TinkerManager.installTinker(this); 替换成下面Bugly提供的方法
        Beta.installTinker(this);
    }

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

}

然后在我们自己的application中集成TinkerApplication, 主要是第二个参数上面刚刚创建的MyApplicationLike

package com.example.administrator.buglyupdatedemo;

import com.tencent.tinker.loader.app.TinkerApplication;
import com.tencent.tinker.loader.shareutil.ShareConstants;

/**
 * Created by Administrator on 2017/10/30.
 * 自定义Application.
 *
 * 注意:这个类集成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
 */

public class MyApplication extends TinkerApplication{

    public MyApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "com.example.administrator.buglyupdatedemo.MyApplicationLike",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }

}

最后把自己的application引用到我们的配置文件里, 配置下权限和bugly sdk,整个改造流程和配置就完成了

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.administrator.buglyupdatedemo">

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <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_LOGS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|locale"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Bugly升级SDK配置开始  -->
        <activity
            android:name="com.tencent.bugly.beta.ui.BetaActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|locale"
            android:theme="@android:style/Theme.Translucent"/>

        <!-- Bugly升级SDK配置结束-->

    </application>

</manifest>

5、打基准包(base包)相关配置和步骤(打base包时候ef baseApkDir = "app-1103-13-41-46"这个目录值是可以任意的,用到的是打出来后的目录值)


image.png

打出来的基准包,我打的是debug包


image.png

6、接下来就是打补丁包了,这个补丁包是在patch目录下的,不是在tinkerpatch目录下的
a.先修改好你的bug(四大组件是无法更新的)
b.先将def baseApkDir = "app-1103-13-41-46"这个改为基准包目录
c.tinkerId = "patch-1.0.1"这个id改为patch-xxx, xxx对应基准包的xxx


image.png

7、最后就是在后台控制器上传补丁了


image.png

下发成功(要下载合成补丁的前提是,基准包要上报联网成功后才能去下载合成的)


image.png
image.png

坑点1:

我在试的时候踩了一个坑,下发成功后,app一直下载不了补丁包,后来发现是全量升级开启导致的,在文档里有提到,全量更新的下发的优先级是高于热更新的下发的,也就是说同时有全量更新和热更新的下发,app会优先去提示下载升级版本,如果用户一直不升级,那么补丁就一直无法下载合并的,如果用户选择升级了,在升级后会自动下载合并补丁的。


image.png

坑点二: 全量更新和热更新都是需要(冷启动)用户重新启动app才会触发的,而且热更新下载合并完补丁后还需要重新启动app才会显示修复后的状态。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容