首先为什么要集成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>
- # 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";
- }
- }
修改配置文件:
这里注意点,补丁包是基于基准包所生成的patch文件并不是版本升级,所以此处补丁包不需要修改versioncode,versionname
然后双击下图中所指地方:
稍等片刻就会出现下图中类容:
其中的patch_signed_7zip.apk就是补丁包了。将这个补丁包上传到腾讯bugly即可。
注意:上传完补丁包点击了立即下发,就需要重新启动基准包策略。从有bug版本的app到修复有一个时间差的。估计1到2分钟左右才能看到效果。
附上集成过程中可能遇到的坑解决办法地址:点击打开链接
最后附上自己写的demo地址:点击打开链接