本文转载自:点击打开链接

首先为什么要集成bugly热修复。市面上有其他的热修复框架,为什么就用bugly?这里给出2张图大家就明白了。
集成腾讯bugly的热修复功能sdk步骤_第1张图片集成腾讯bugly的热修复功能sdk步骤_第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() {

  1. @Override
  2. public void onClick(View view) {
    4./ String str = LoadBugClass.getBugString();
  3. String str = BugClass.bug();
  4. Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
  5. }
  6. });

[java] view plain copy
1.public class BugClass {

  1. public static String bug(){
  2. String str = null;
  3. int str_length = str.length();
  4. return "this is bug class";
  5. }
    8.}
    这个可以看出点击一个按钮会报空指针异常。
    2:接着就是配置相关属性和添加一个插件依赖了。

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

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

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

集成腾讯bugly的热修复功能sdk步骤_第3张图片
其次新建sampleapplication和sampleapplicationLike两个java类

[java] view plain copy
1.package com.henry.testappbugly;

  1. 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;

  2. 12.import com.tencent.bugly.Bugly;
    13.import com.tencent.bugly.beta.Beta;
    14.import com.tencent.tinker.loader.app.DefaultApplicationLike;

  3. 16./**

    • Created by W61 on 2016/11/29.
  4. */
  5. 20.public class SampleApplicationLike extends DefaultApplicationLike {

  6. public static final String TAG = "Tinker.SampleApplicationLike";
  7. public SampleApplicationLike(Application application, int tinkerFlags,
  8. boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
  9. long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources,
  10. ClassLoader[] classLoader, AssetManager[] assetManager) {
  11. super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime,
  12. applicationStartMillisTime, tinkerResultIntent, resources, classLoader,
  13. assetManager);
  14. }
  15. @Override
  16. public void onCreate() {
  17. super.onCreate();
  18. // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
  19. Bugly.init(getApplication(), "", true);
  20. }
  21. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  22. @Override
  23. public void onBaseContextAttached(Context base) {
  24. super.onBaseContextAttached(base);
  25. // you must install multiDex whatever tinker is installed!
  26. MultiDex.install(base);
  27. // 安装tinker
  28. // TinkerManager.installTinker(this); 替换成下面Bugly提供的方法
  29. Beta.installTinker(this);
  30. }
  31. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  32. public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
  33. getApplication().registerActivityLifecycleCallbacks(callbacks);
  34. }
  35. 59.}

[java] view plain copy
1.package com.henry.testappbugly;

  1. 3.import com.tencent.tinker.loader.app.TinkerApplication;
    4.import com.tencent.tinker.loader.shareutil.ShareConstants;

  2. 6./**

    • Created by W61 on 2016/11/29.
  3. */
  4. 10.public class SampleApplication extends TinkerApplication {

  5. public SampleApplication() {
  6. super(ShareConstants.TINKER_ENABLE_ALL, "SampleApplicationLike所在的包名路径",
  7. "com.tencent.tinker.loader.TinkerLoader", false);
  8. }
    15.}

在在Androidmanifest.xml文件中配置权限及application类名

[java] view plain copy
1.
2.

  1. package="com.henry.testappbugly">
  2. android:name=".SampleApplication"
  3. android:allowBackup="true"
  4. android:icon="@mipmap/ic_launcher"
  5. android:label="@string/app_name"
  6. android:supportsRtl="true"
  7. android:theme="@style/AppTheme">
  8. android:name="android.support.v4.content.FileProvider"
  9. android:authorities="com.tencent.bugly.hotfix.fileProvider"
  10. android:exported="false"
  11. android:grantUriPermissions="true">
  12. android:name="android.support.FILE_PROVIDER_PATHS"
  13. android:resource="@xml/provider_paths"/>
  14. 43.

在到res目录下:
集成腾讯bugly的热修复功能sdk步骤_第4张图片
[java] view plain copy
1.
2.;


  1. 8.

在到app目录下新建:集成腾讯bugly的热修复功能sdk步骤_第5张图片
[java] view plain copy
1.# you can copy the tinker keep rule at
2.# build/intermediates/tinker_intermediates/tinker_multidexkeep.pro

  1. 4.-keep class com.tencent.tinker.loader.** {

  2. *;
    6.}
  3. 8.-keep class com.tencent.bugly.hotfix.SampleApplication {

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

  6. *;
    14.}
  7. 16.-keep public class * extends com.tencent.tinker.loader.TinkerLoader {

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

  10. *;
    22.}
  11. 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'

  1. 3.dependencies {

  2. compile fileTree(include: ['*.jar'], dir: 'libs')
  3. compile 'com.android.support:appcompat-v7:24.1.1'
  4. // 多dex配置
  5. compile "com.android.support:multidex:1.0.1"
  6. // 集成Bugly热更新aar(灰度时使用方式)
    10.// compile(name: 'bugly_crashreport_upgrade-1.2.0', ext: 'aar')
  7. compile "com.tencent.bugly:crashreport_upgrade:1.2.0"
    12.}
  8. 15.android {

  9. compileSdkVersion 23
  10. buildToolsVersion "23.0.2"
  11. // 编译选项
  12. compileOptions {
  13. sourceCompatibility JavaVersion.VERSION_1_7
  14. targetCompatibility JavaVersion.VERSION_1_7
  15. }
  16. // recommend
  17. dexOptions {
  18. jumboMode = true
  19. }
  20. // 签名配置
  21. signingConfigs {
  22. // 签名配置
  23. signingConfigs {
  24. release {
  25. try {
  26. storeFile file("./keystore/release.keystore")
  27. storePassword "testres"
  28. keyAlias "testres"
  29. keyPassword "testres"
  30. } catch (ex) {
  31. throw new InvalidUserDataException(ex.toString())
  32. }
  33. }
  34. debug {
  35. storeFile file("./keystore/debug.keystore")
  36. }
  37. }
  38. }
  39. defaultConfig {
  40. applicationId "com.henry.testappbugly"
  41. minSdkVersion 14
  42. targetSdkVersion 23
  43. versionCode 2
  44. versionName "2.0"
  45. // 开启multidex
  46. multiDexEnabled true
  47. // 以Proguard的方式手动加入要放到Main.dex中的类
  48. multiDexKeepProguard file("keep_in_main_dex.txt")
  49. }
  50. buildTypes {
  51. release {
  52. minifyEnabled true
  53. signingConfig signingConfigs.release
  54. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  55. }
  56. debug {
  57. debuggable true
  58. minifyEnabled false
  59. signingConfig signingConfigs.debug
  60. }
  61. }
  62. sourceSets {
  63. main {
  64. jniLibs.srcDirs = ['libs']
  65. }
  66. }
  67. repositories {
  68. flatDir {
  69. dirs 'libs'
  70. }
  71. }
  72. lintOptions {
  73. checkReleaseBuilds false
  74. abortOnError false
  75. }
    91.}
  76. 94.def gitSha() {

  77. try {
  78. String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
  79. if (gitRev == null) {
  80. throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
  81. }
  82. return gitRev
  83. } catch (Exception e) {
  84. throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
  85. }
    104.}
  86. 107.def bakPath = file("${buildDir}/bakApk/")

  87. 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
  88. */
    114.ext {
  89. // for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
  90. tinkerEnabled = true
  91. // for normal build
  92. // old apk file to build patch apk
  93. tinkerOldApkPath = "${bakPath}/app-release-1201-09-46-25.apk"
  94. // proguard mapping file to build patch apk
  95. tinkerApplyMappingPath = "${bakPath}/app-release-1201-09-46-25-mapping.txt"
  96. // resource R.txt to build patch apk, must input if there is resource changed
  97. tinkerApplyResourcePath = "${bakPath}/app-release-1201-09-46-25-R.txt"
  98. // only use for build all flavor, if not, just ignore this field
  99. tinkerBuildFlavorDirectory = "${bakPath}/app-release-1201-09-46-25"
    128.}
  100. 130.def getOldApkPath() {

  101. return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
    132.}
  102. 134.def getApplyMappingPath() {

  103. return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
    136.}
  104. 138.def getApplyResourceMappingPath() {

  105. return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
    140.}
  106. 142.def getTinkerIdValue() {

  107. return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
    144.}
  108. 146.def buildWithTinker() {

  109. return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
    148.}
  110. 150.def getTinkerBuildFlavorDirectory() {

  111. return ext.tinkerBuildFlavorDirectory
    152.}
  112. 154./**

    • 更多Tinker插件详细的配置,参考:https://github.com/Tencent/tinker/wiki
  113. */
    157.if (buildWithTinker()) {
  114. // 依赖tinker插件
  115. apply plugin: 'com.tencent.tinker.patch'
  116. apply plugin: 'com.tencent.bugly.tinker-support'
  117. tinkerSupport {
  118. }
  119. // 全局信息相关配置项
  120. tinkerPatch {
  121. oldApk = getOldApkPath() //必选, 基准包路径
  122. ignoreWarning = false // 可选,默认false
  123. useSign = true // 可选,默认true, 验证基准apk和patch签名是否一致
  124. // 编译相关配置项
  125. buildConfig {
  126. applyMapping = getApplyMappingPath() // 可选,设置mapping文件,建议保持旧apk的proguard混淆方式
  127. applyResourceMapping = getApplyResourceMappingPath() // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
  128. tinkerId = "可以是签名版本号字符串等等比如:assdhfkdshfksdhfuksfhuk" // 必选,默认为null
  129. }
  130. // dex相关配置项
  131. dex {
  132. dexMode = "jar" // 可选,默认为jar
  133. usePreGeneratedPatchDex = true // 可选,默认为false
  134. pattern = ["classes*.dex",
  135. "assets/secondary-dex-?.jar"]
  136. // 必选
  137. loader = ["com.tencent.tinker.loader.*",
  138. "SampleApplication所在的全路径",
  139. ]
  140. }
  141. // lib相关的配置项
  142. lib {
  143. pattern = ["lib/armeabi/*.so"]
  144. }
  145. // res相关的配置项
  146. res {
  147. pattern = ["res/", "assets/", "resources.arsc", "AndroidManifest.xml"]
  148. ignoreChange = ["assets/sample_meta.txt"]
  149. largeModSize = 100
  150. }
  151. // 用于生成补丁包中的'package_meta.txt'文件
  152. packageConfig {
  153. configField("patchMessage", "tinker is sample to use")
  154. configField("platform", "all")
  155. configField("patchVersion", "1.0")
  156. }
  157. // 7zip路径配置项,执行前提是useSign为true
  158. sevenZip {
  159. zipArtifact = "com.tencent.mm:SevenZip:1.1.10" // optional
  160. // path = "/usr/local/bin/7za" // optional
  161. }
  162. }
  163. List flavors = new ArrayList<>();
  164. project.android.productFlavors.each { flavor ->
  165. flavors.add(flavor.name)
  166. }
  167. boolean hasFlavors = flavors.size() > 0
  168. /**
    • bak apk and mapping
  169. */
  170. android.applicationVariants.all { variant ->
  171. /**
    • task type, you want to bak
  172. */
  173. def taskName = variant.name
  174. def date = new Date().format("MMdd-HH-mm-ss")
  175. tasks.all {
  176. if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
  177. it.doLast {
  178. copy {
  179. def fileNamePrefix = "${project.name}-${variant.baseName}"
  180. def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
  181. def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
  182. from variant.outputs.outputFile
  183. into destPath
  184. rename { String fileName ->
  185. fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
  186. }
  187. from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
  188. into destPath
  189. rename { String fileName ->
  190. fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
  191. }
  192. from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
  193. into destPath
  194. rename { String fileName ->
  195. fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
  196. }
  197. }
  198. }
  199. }
  200. }
  201. }
  202. project.afterEvaluate {
  203. //sample use for build all flavor for one time
  204. if (hasFlavors) {
  205. task(tinkerPatchAllFlavorRelease) {
  206. group = 'tinker'
  207. def originOldPath = getTinkerBuildFlavorDirectory()
  208. for (String flavor : flavors) {
  209. def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
  210. dependsOn tinkerTask
  211. def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
  212. preAssembleTask.doFirst {
  213. String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
  214. project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
  215. project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
  216. project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"
  217. }
  218. }
  219. }
  220. task(tinkerPatchAllFlavorDebug) {
  221. group = 'tinker'
  222. def originOldPath = getTinkerBuildFlavorDirectory()
  223. for (String flavor : flavors) {
  224. def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
  225. dependsOn tinkerTask
  226. def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
  227. preAssembleTask.doFirst {
  228. String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
  229. project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
  230. project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
  231. project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
  232. }
  233. }
  234. }
  235. }
  236. }
  237. 306.}

最后run as生成有bug的基准包app集成腾讯bugly的热修复功能sdk步骤_第6张图片
在去腾讯bugly官网将这个基准包上传上去即可。

接下来,制作补丁包。

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

[java] view plain copy

  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_main);
  4. btn = (Button) findViewById(R.id.btn);
  5. btn.setOnClickListener(new View.OnClickListener() {
  6. @Override
  7. public void onClick(View view) {
  8. String str = LoadBugClass.getBugString();
    10.// String str = BugClass.bug();
  9. Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
  10. }
  11. });
  12. }

[java] view plain copy
1.public class LoadBugClass {

  1. /**
    • 获取bug字符串.
    • @return 返回bug字符串
  2. */
  3. public static String getBugString() {
    8.// BugClass bugClass = new BugClass();
  4. return "iS OK";
  5. }
    11.}
    这样点击按钮就会弹出is ok了不会报错。
    修改配置文件:集成腾讯bugly的热修复功能sdk步骤_第7张图片
    这里注意点,补丁包是基于基准包所生成的patch文件并不是版本升级,所以此处补丁包不需要修改versioncode,versionname

然后双击下图中所指地方:集成腾讯bugly的热修复功能sdk步骤_第8张图片
稍等片刻就会出现下图中类容:集成腾讯bugly的热修复功能sdk步骤_第9张图片
其中的patch_signed_7zip.apk就是补丁包了。将这个补丁包上传到腾讯bugly即可。

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