集成腾讯bugly的热修复功能sdk步骤

首先为什么要集成bugly热修复。市面上有其他的热修复框架,为什么就用bugly?这里给出2张图大家就明白了。




引用腾讯bugly官网的一段话:

  • 无需关注Tinker是如何合成补丁的
  • 无需自己搭建补丁管理后台
  • 无需考虑后台下发补丁策略的任何事情
  • 无需考虑补丁下载合成的时机,处理后台下发的策略
  • 我们提供了更加方便集成Tinker的方式
  • 我们提供应用升级一站式解决方案
进入正题:接入流程主要是以下几个步骤:

  • 打基准包安装并上报联网(注:填写唯一的tinkerId)
  • 对基准包的bug修复(可以是Java代码变更,资源的变更)
  • 修改基准包路径、填写补丁包tinkerId、mapping文件路径、resId文件路径
  • 执行tinkerPatchRelease打Release版本补丁包
  • 选择app/build/outputs/patch目录下的补丁包并上传(注:不要选择tinkerPatch目录下的补丁包,不然上传会有问题)
  • 编辑下发补丁规则,点击立即下发
  • 重启基准包,请求补丁策略(SDK会自动下载补丁并合成)
  • 再次重启基准包,检验补丁应用结果

1:新建基准包工程项目(人为制造有BUG的app版本)

 btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                String str = LoadBugClass.getBugString();
                String str = BugClass.bug();
                Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
            }
        });
public class BugClass {

    public static String bug(){
        String str = null;
        int str_length = str.length();
        return "this is bug class";
    }
}
这个可以看出点击一个按钮会报空指针异常。

2:接着就是配置相关属性和添加一个插件依赖了。

官方教程地址:点击打开链接

下面也给出我自己配置的过程。

首先在最外层的build.gradle文件中添加依赖,看下图:



其次新建sampleapplication和sampleapplicationLike两个java类

package com.henry.testappbugly;

import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Build;
import android.support.multidex.MultiDex;

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

/**
 * Created by W61 on 2016/11/29.
 */

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(), "", 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);
    }

}

package com.henry.testappbugly;

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

/**
 * Created by W61 on 2016/11/29.
 */

public class SampleApplication extends TinkerApplication {
    public SampleApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "SampleApplicationLike所在的包名路径",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }
}

在在Androidmanifest.xml文件中配置权限及application类名
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.henry.testappbugly">

    <application
        android:name=".SampleApplication"
        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>


        <!--API 24以上配置-->
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.tencent.bugly.hotfix.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>

    </application>


    <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"/>

</manifest>

在到res目录下:



<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 这里配置的两个外部存储路径是升级SDK下载的文件可能存在的路径 -->
    <!-- /storage/emulated/0/Download/com.bugly.upgrade.demo/.beta/apk-->
    <external-path name="beta_external_path" path="Download/"/>
    <!--/storage/emulated/0/Android/data/com.bugly.upgrade.demo/files/apk/-->
    <external-path name="beta_external_files_path" path="Android/data/"/>
</paths>


在到app目录下新建:

# you can copy the tinker keep rule at
# build/intermediates/tinker_intermediates/tinker_multidexkeep.pro

-keep class com.tencent.tinker.loader.** {
    *;
}

-keep class com.tencent.bugly.hotfix.SampleApplication {
    *;
}

-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {
    *;
}

-keep public class * extends com.tencent.tinker.loader.TinkerLoader {
    *;
}

-keep public class * extends com.tencent.tinker.loader.app.TinkerApplication {
    *;
}

# here, it is your own keep rules.
# you must be careful that the class name you write won't be proguard
# but the tinker class above is OK, we have already keep for you!

然后在混淆文件.pro中添加这几句代码(bugly都有说明解释)

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}


最后就是app目录下的build.gradle文件配置了:

apply plugin: 'com.android.application'

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:24.1.1'

    // 多dex配置
    compile "com.android.support:multidex:1.0.1"
    // 集成Bugly热更新aar(灰度时使用方式)
//    compile(name: 'bugly_crashreport_upgrade-1.2.0', ext: 'aar')
    compile "com.tencent.bugly:crashreport_upgrade:1.2.0"
}


android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

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

    // recommend
    dexOptions {
        jumboMode = true
    }
    // 签名配置
    signingConfigs {
        // 签名配置
        signingConfigs {
            release {
                try {
                    storeFile file("./keystore/release.keystore")
                    storePassword "testres"
                    keyAlias "testres"
                    keyPassword "testres"
                } catch (ex) {
                    throw new InvalidUserDataException(ex.toString())
                }
            }

            debug {
                storeFile file("./keystore/debug.keystore")
            }
        }
    }

    defaultConfig {
        applicationId "com.henry.testappbugly"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 2
        versionName "2.0"

        // 开启multidex
        multiDexEnabled true
        // 以Proguard的方式手动加入要放到Main.dex中的类
        multiDexKeepProguard file("keep_in_main_dex.txt")
    }
    buildTypes {

        release {
            minifyEnabled true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        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-1201-09-46-25.apk"
    // proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-release-1201-09-46-25-mapping.txt"
    // resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-release-1201-09-46-25-R.txt"

    // only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/app-release-1201-09-46-25"
}

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 = "可以是签名版本号字符串等等比如:assdhfkdshfksdhfuksfhuk" // 必选,默认为null
        }

        // dex相关配置项
        dex {
            dexMode = "jar" // 可选,默认为jar
            usePreGeneratedPatchDex = true // 可选,默认为false
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
            // 必选
            loader = ["com.tencent.tinker.loader.*",
                      "SampleApplication所在的全路径",
            ]
        }

        // 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"
                    }

                }
            }
        }
    }


}



最后run as生成有bug的基准包app


在去腾讯bugly官网将这个基准包上传上去即可。



-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来,制作补丁包。

由于刚才点击按钮报空指针,下面将代码稍做改动如下:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String str = LoadBugClass.getBugString();
//                String str = BugClass.bug();
                Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
            }
        });
    }
public class LoadBugClass {
    /**
     * 获取bug字符串.
     *
     * @return 返回bug字符串
     */
    public static String getBugString() {
//        BugClass bugClass = new BugClass();
        return "iS OK";
    }
}
这样点击按钮就会弹出is ok了不会报错。

修改配置文件:



这里注意点,补丁包是基于基准包所生成的patch文件并不是版本升级,所以此处补丁包不需要修改versioncode,versionname

然后双击下图中所指地方:


稍等片刻就会出现下图中类容:


其中的patch_signed_7zip.apk就是补丁包了。将这个补丁包上传到腾讯bugly即可。

注意:上传完补丁包点击了立即下发,就需要重新启动基准包策略。从有bug版本的app到修复有一个时间差的。估计1到2分钟左右才能看到效果。


附上集成过程中可能遇到的坑解决办法地址:点击打开链接

最后附上自己写的demo地址:点击打开链接

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值