如何使用Bugly进行热修复

如何使用Bugly进行热修复

首先介绍一下Bugly热修复

热更新能力是Bugly为解决开发者紧急修复线上bug,而无需重新发版让用户无感知就能把问题修复的一项能力。Bugly目前采用微信Tinker的开源方案,开发者只需要集成腾讯提供的SDK就可以实现自动下载补丁包、合成、并应用补丁的功能,腾讯也提供了热更新管理后台让开发者对每个版本补丁进行管理。


为什么我要使用Bugly热修复呢?请看下面这张来自互联网的图片

热修复对比图

OK 下面我们切入正题

第一步:

需要使用Bugly首先我们必须引入官方依赖
在工程根目录下“build.gradle”文件中添加:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // tinker gradle插件
        classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.5')

        // tinkersupport插件
        classpath "com.tencent.bugly:tinker-support:latest.release"
    }
}

在app module目录下“build.gradle”文件中添加:

compile 'com.tencent.bugly:crashreport_upgrade:1.2.0'

配置到这一步我们build一下项目,如果没有提示任何错误,说明我们的依赖引入成功。博主在build项目时候出现了一下错误:
配置出错
我不知的为什么添加其他依赖的时候就没问题,然后和腾讯客服反映这个问题,他们说他们也没遇到过这个情况。然后博主就build了一下午还种错误,然后第二天到公司继续build,然而这一次居然成功了,好兴奋可以愉快的进行下一步搭建了。
PS:估计还是博主这边的网络原因造成的,所以说有必要进行“科学上网”。

第二步:

AndroidManifest.xml配置
权限配置:

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

在res目录新建xml文件夹,创建provider_paths.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path name="beta_external_path" path="Download/"/>
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path name="beta_external_files_path" path="Android/data/"/>
</paths>
第三步:

1.自定义ApplicationLike

public class SampleApplicationLike extends DefaultApplicationLike {

    public static final String TAG = "Tinker.SampleApplicationLike";

    public SampleApplicationLike(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();
        // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
        Bugly.init(getApplication(), "900029763", 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
        // TinkerManager.installTinker(this); 替换成下面Bugly提供的方法
        Beta.installTinker(this);
    }

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

}

2.自定义Application

public class SampleApplication extends TinkerApplication {
    public SampleApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "xxx.xxx.SampleApplicationLike",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }
}

不要忘了在AndroidManifest.xml中修改
项目情况

第四步:

在app module的“build.gradle”文件中添加配置:

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    useLibrary('org.apache.http.legacy')
    // 编译选项
//    compileOptions {
//        sourceCompatibility JavaVersion.VERSION_1_7
//        targetCompatibility JavaVersion.VERSION_1_7
//    }
    // recommend
    dexOptions {
        jumboMode = true
    }
    signingConfigs {
        release {
            keyAlias KEY_ALIAS
            keyPassword KEY_PASSWORD
            storeFile file(STORE_FILE)
            storePassword STORE_PASSWORD

        }
        debug {
            storeFile file('C:/Users/Administrator/.android/debug.keystore')
        }
    }
    defaultConfig {
        applicationId "com.example.linkmirror2"
        minSdkVersion 18
        targetSdkVersion 24
        versionCode 1
        versionName "1.0.1"

        // 开启multidex
        multiDexEnabled true
        // 以Proguard的方式手动加入要放到Main.dex中的类
        multiDexKeepProguard file("keep_in_main_dex.txt")
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
        debug {
            debuggable true
            minifyEnabled false
            signingConfig signingConfigs.debug
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
    repositories {
        flatDir {
            dirs 'libs'
        }
    }
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }
}
def gitSha() {
    try {
        String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
        if (gitRev == null) {
            throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
        }
        return gitRev
    } catch (Exception e) {
        throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
    }
}


def bakPath = file("${buildDir}/bakApk/")

/**
 * you can use assembleRelease to build you base apk
 * use tinkerPatchRelease -POLD_APK=  -PAPPLY_MAPPING=  -PAPPLY_RESOURCE= to build patch
 * add apk from the build/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-1216-10-31-59.apk"
    // proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-release-1216-10-31-59-mapping.txt"
    // resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-release-1216-10-31-59-R.txt"

    // only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/app-release-1216-10-31-59"
}

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插件详细的配置,参考:https://github.com/Tencent/tinker/wiki
 */
if (buildWithTinker()) {
    // 依赖tinker插件
    apply plugin: 'com.tencent.tinker.patch'
    apply plugin: 'com.tencent.bugly.tinker-support'

    tinkerSupport {
    }

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

        ignoreWarning = false // 可选,默认false

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

        // 编译相关配置项
        buildConfig {
            applyMapping = getApplyMappingPath() //  可选,设置mapping文件,建议保持旧apk的proguard混淆方式
            applyResourceMapping = getApplyResourceMappingPath() // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
            tinkerId = "1.0.1.a" // 必选,我将此设为“版本号+字母”随修复次数字母逐渐递升
        }

        // dex相关配置项
        dex {
            dexMode = "jar" // 可选,默认为jar
            usePreGeneratedPatchDex = true // 可选,默认为false
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
            // 必选
            loader = ["com.tencent.tinker.loader.*",
                      "com.example.linkmirror2.app.MyApplication",//你自己的路径
            ]
        }

        // 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
            //  path = "/usr/local/bin/7za" // optional
        }
    }

    List<String> flavors = new ArrayList<>();
    project.android.productFlavors.each { flavor ->
        flavors.add(flavor.name)
    }
    boolean hasFlavors = flavors.size() > 0
    /**
     * bak apk and mapping
     */
    android.applicationVariants.all { variant ->
        /**
         * task type, you want to bak
         */
        def taskName = variant.name
        def date = new Date().format("MMdd-HH-mm-ss")

        tasks.all {
            if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {

                it.doLast {
                    copy {
                        def fileNamePrefix = "${project.name}-${variant.baseName}"
                        def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"

                        def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
                        from variant.outputs.outputFile
                        into destPath
                        rename { String fileName ->
                            fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                        }

                        from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
                        }

                        from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
                        }
                    }
                }
            }
        }
    }
    project.afterEvaluate {
        //sample use for build all flavor for one time
        if (hasFlavors) {
            task(tinkerPatchAllFlavorRelease) {
                group = 'tinker'
                def originOldPath = getTinkerBuildFlavorDirectory()
                for (String flavor : flavors) {
                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
                    dependsOn tinkerTask
                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
                    preAssembleTask.doFirst {
                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"

                    }

                }
            }

            task(tinkerPatchAllFlavorDebug) {
                group = 'tinker'
                def originOldPath = getTinkerBuildFlavorDirectory()
                for (String flavor : flavors) {
                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
                    dependsOn tinkerTask
                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
                    preAssembleTask.doFirst {
                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
                    }

                }
            }
        }
    }

OK,现在我们可以生成基准包了

1.执行assembleRelease编译生成基准包:

运行基准包
2.根据基线版本生成补丁包
修改基线版本apk路径
这里写图片描述

第五步:
修改你项目中的bug,修改完成后执行下面的操作

1.执行构建补丁包的task
构建补丁包
如果你要生成不同编译环境的补丁包,只需要执行Tinker插件生成的task,比如tinkerPatchRelease就能生成release编译环境的补丁包。
生成的补丁包在build/outputs/patch目录下:
补丁包

最后一步上传补丁包到平台

上传补丁包到平台并下发编辑规则
上传补丁包

Ok,具体方法已经写完了,大家可以轻松的使用热更新到自己的项目中去了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值