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

来源:互联网 发布:百度还有哪些软件 编辑:程序博客网 时间:2024/05/02 10:01

首先为什么要集成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地址:点击打开链接

1 0
原创粉丝点击