本文转载自:点击打开链接
首先为什么要集成bugly热修复。市面上有其他的热修复框架,为什么就用bugly?这里给出2张图大家就明白了。
引用腾讯bugly官网的一段话:
•无需关注Tinker是如何合成补丁的
•无需自己搭建补丁管理后台
•无需考虑后台下发补丁策略的任何事情
•无需考虑补丁下载合成的时机,处理后台下发的策略
•我们提供了更加方便集成Tinker的方式
•我们提供应用升级一站式解决方案
进入正题:接入流程主要是以下几个步骤:•打基准包安装并上报联网(注:填写唯一的tinkerId)
•对基准包的bug修复(可以是Java代码变更,资源的变更)
•修改基准包路径、填写补丁包tinkerId、mapping文件路径、resId文件路径
•执行tinkerPatchRelease打Release版本补丁包
•选择app/build/outputs/patch目录下的补丁包并上传(注:不要选择tinkerPatch目录下的补丁包,不然上传会有问题)
•编辑下发补丁规则,点击立即下发
•重启基准包,请求补丁策略(SDK会自动下载补丁并合成)
•再次重启基准包,检验补丁应用结果
1:新建基准包工程项目(人为制造有BUG的app版本)
[java] view plain copy
1.btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
4./ String str = LoadBugClass.getBugString(); - String str = BugClass.bug();
- Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
- }
- });
[java] view plain copy
1.public class BugClass {
- public static String bug(){
- String str = null;
- int str_length = str.length();
- return "this is bug class";
- }
8.}
这个可以看出点击一个按钮会报空指针异常。
2:接着就是配置相关属性和添加一个插件依赖了。
官方教程地址:点击打开链接
下面也给出我自己配置的过程。
首先在最外层的build.gradle文件中添加依赖,看下图:
其次新建sampleapplication和sampleapplicationLike两个java类
[java] view plain copy
1.package com.henry.testappbugly;
-
3.import android.annotation.TargetApi;
4.import android.app.Application;
5.import android.content.Context;
6.import android.content.Intent;
7.import android.content.res.AssetManager;
8.import android.content.res.Resources;
9.import android.os.Build;
10.import android.support.multidex.MultiDex; -
12.import com.tencent.bugly.Bugly;
13.import com.tencent.bugly.beta.Beta;
14.import com.tencent.tinker.loader.app.DefaultApplicationLike; -
16./**
-
- Created by W61 on 2016/11/29.
- */
-
20.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);
- }
-
59.}
[java] view plain copy
1.package com.henry.testappbugly;
-
3.import com.tencent.tinker.loader.app.TinkerApplication;
4.import com.tencent.tinker.loader.shareutil.ShareConstants; -
6./**
-
- Created by W61 on 2016/11/29.
- */
-
10.public class SampleApplication extends TinkerApplication {
- public SampleApplication() {
- super(ShareConstants.TINKER_ENABLE_ALL, "SampleApplicationLike所在的包名路径",
- "com.tencent.tinker.loader.TinkerLoader", false);
- }
15.}
在在Androidmanifest.xml文件中配置权限及application类名
[java] view plain copy 43.
1.
2.
在到res目录下:
[java] view plain copy
1.
2.
8.
在到app目录下新建:
[java] view plain copy
1.# you can copy the tinker keep rule at
2.# build/intermediates/tinker_intermediates/tinker_multidexkeep.pro
-
4.-keep class com.tencent.tinker.loader.** {
- *;
6.} -
8.-keep class com.tencent.bugly.hotfix.SampleApplication {
- *;
10.} -
12.-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {
- *;
14.} -
16.-keep public class * extends com.tencent.tinker.loader.TinkerLoader {
- *;
18.} -
20.-keep public class * extends com.tencent.tinker.loader.app.TinkerApplication {
- *;
22.} -
24.# here, it is your own keep rules.
25.# you must be careful that the class name you write won't be proguard
26.# but the tinker class above is OK, we have already keep for you!
然后在混淆文件.pro中添加这几句代码(bugly都有说明解释)
[java] view plain copy
1.-dontwarn com.tencent.bugly.
2.-keep public class com.tencent.bugly.{*;}
最后就是app目录下的build.gradle文件配置了:
[java] view plain copy
1.apply plugin: 'com.android.application'
-
3.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(灰度时使用方式)
10.// compile(name: 'bugly_crashreport_upgrade-1.2.0', ext: 'aar') - compile "com.tencent.bugly:crashreport_upgrade:1.2.0"
12.} -
15.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
- }
91.} -
94.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'")
- }
104.} -
107.def bakPath = file("${buildDir}/bakApk/")
-
109./**
-
- 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
- */
114.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"
128.} -
130.def getOldApkPath() {
- return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
132.} -
134.def getApplyMappingPath() {
- return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
136.} -
138.def getApplyResourceMappingPath() {
- return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
140.} -
142.def getTinkerIdValue() {
- return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
144.} -
146.def buildWithTinker() {
- return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
148.} -
150.def getTinkerBuildFlavorDirectory() {
- return ext.tinkerBuildFlavorDirectory
152.} -
154./**
-
- 更多Tinker插件详细的配置,参考:https://github.com/Tencent/tinker/wiki
- */
157.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
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"
- }
- }
- }
- }
- }
-
306.}
最后run as生成有bug的基准包app
在去腾讯bugly官网将这个基准包上传上去即可。
接下来,制作补丁包。
由于刚才点击按钮报空指针,下面将代码稍做改动如下:
[java] view plain copy
- 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();
10.// String str = BugClass.bug(); - Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
- }
- });
- }
[java] view plain copy
1.public class LoadBugClass {
- /**
-
- 获取bug字符串.
-
-
- @return 返回bug字符串
- */
- public static String getBugString() {
8.// BugClass bugClass = new BugClass(); - return "iS OK";
- }
11.}
这样点击按钮就会弹出is ok了不会报错。
修改配置文件:
这里注意点,补丁包是基于基准包所生成的patch文件并不是版本升级,所以此处补丁包不需要修改versioncode,versionname
然后双击下图中所指地方:
稍等片刻就会出现下图中类容:
其中的patch_signed_7zip.apk就是补丁包了。将这个补丁包上传到腾讯bugly即可。
注意:上传完补丁包点击了立即下发,就需要重新启动基准包策略。从有bug版本的app到修复有一个时间差的。估计1到2分钟左右才能看到效果。