首先爲什麼要集成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";
}
}
這樣點擊按鈕就會彈出is ok了不會報錯。
修改配置文件:
這裏注意點,補丁包是基於基準包所生成的patch文件並不是版本升級,所以此處補丁包不需要修改versioncode,versionname
然後雙擊下圖中所指地方:
稍等片刻就會出現下圖中類容:
其中的patch_signed_7zip.apk就是補丁包了。將這個補丁包上傳到騰訊bugly即可。
注意:上傳完補丁包點擊了立即下發,就需要重新啓動基準包策略。從有bug版本的app到修復有一個時間差的。估計1到2分鐘左右才能看到效果。
附上集成過程中可能遇到的坑解決辦法地址:點擊打開鏈接
最後附上自己寫的demo地址:點擊打開鏈接