Android內建Bugly熱更新
@Author GQ 2017年12月18日
最近要新開一個項目,上司說了不讓老更新app,每次幾十兆…
是以內建熱更吧,也是大趨勢~
順便用用谷歌爸爸Kt語言!
查資料
主流熱更架構有:
- 阿裡
AndFix
- 騰訊
Bugly
- 美團
Robust
Tinker
是微信官方的 Android熱更新檔 解決方案 (推薦)
它支援動态下發代碼、So庫以及資源,讓應用能夠在不需要重新安裝的情況下實作更新。當然,你也可以使用 Tinker 來更新你的插件。
架構對比
資料來自郭神的訂閱号: https://mp.weixin.qq.com/s/lMcCSKG54xvvCo9ZGI8ZlA
選擇Bulgy
建立一個新項目
先能正常運作跑起來
內建Bugly
注意不要着急build項目,東西加載不全的時候會報錯,先按順序修改好配置檔案
build前記得先進官網注冊拿到appId改到項目中
項目的build.gradle中
buildscript {
ext.kotlin_version = '1.1.51'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
//目前版本,可以使用latest.release引入最新版
classpath "com.tencent.bugly:tinker-support:1.1.1"
}
}
在app下的build.gradle中
注意 : 必須需要将打包的jks寫在build.gradle中,别瞎搞什麼幺蛾子,就因為這破玩意,我這卡到這裡1個小時,
該導入的包都導入,記得多導入一個v4包,會用到
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply from: 'tinker-support.gradle'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion "27.0.0"
defaultConfig {
applicationId rootProject.ext.android.applicationId
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
// 開啟multidex
multiDexEnabled true
}
dexOptions {
// 支援大工程模式
jumboMode = true
}
signingConfigs {
release {
keyAlias 'oakey'
keyPassword '123456'
//根據自己的路徑寫
storeFile file('/Users/GUOQI/Android/workplace_git/jks/test.jks')
//自己設定密碼
storePassword '123456'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:25.4.0'
//V4包
implementation 'com.android.support:support-v4:25.4.0'
implementation 'com.android.support:design:25.4.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:support-vector-drawable:25.4.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
// 多dex配置
implementation 'com.android.support:multidex:1.0.2'
// 遠端倉庫內建方式(推薦)
implementation 'com.tencent.bugly:crashreport_upgrade:1.3.4'
}
- 這裡面使用了
這麼個東西,别急就是為了共用提出來而已rootProject.ext.android.xxx
在項目跟目錄下建立 config.gradle
檔案
config.gradle
這個gradle檔案用來存放剛才提出來公共的東西
ext {
android = [compileSdkVersion: ,
applicationId : "com.example.test",//包名寫自己的
minSdkVersion : ,
targetSdkVersion : ,
versionCode : ,
versionName : "1.0"]
}
在回到項目預設生成的 build.gradle
中
build.gradle
apply from: 'config.gradle'//引入剛才寫好的config.gradle檔案,多了這一行
buildscript {
...
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' } //引入這個倉庫
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
建立最重要的一個檔案 tinker-support.gradle
tinker-support.gradle
注意是在app目錄下,别建錯位置,導緻報錯找不到
然後打開編輯這個
tinker-support.gradle
内容
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此處填寫每次建構生成的基準包目錄
*
* 以後隻需要改動這裡的myTinkeId
*/
def baseApkDir = "app-1218-12-53-16" //打包的時候後面的數字是自動根據時間戳生成的,暫時可以不用管
//def myTinkerId = "base-" + rootProject.ext.android.versionName // 用于生成基準包(不用修改)
def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.1" // 用于生成更新檔包(每次生成更新檔包都要修改一次,最好是 patch-${versionName}.x.x)
/**
* 對于插件各參數的詳細解析請參考
*
* enable屬性true
* enableProxyApplication屬性false
* 以上兩個屬性不要看網上其他不同的資料瞎改
*/
tinkerSupport {
// 開啟tinker-support插件,預設值true
enable = true
// 是否啟用加強模式,預設為false.(tinker-spport 1.0.7起支援)
// isProtectedApp = true
// 是否開啟反射Application模式
enableProxyApplication = false
// 是否支援新增非export的Activity(注意:設定為true才能修改AndroidManifest檔案)
supportHotplugComponent = true
// 指定歸檔目錄,預設值目前module的子目錄tinker
autoBackupApkDir = "${bakPath}"
// 是否啟用覆寫tinkerPatch配置功能,預設值false
// 開啟後tinkerPatch配置不生效,即無需添加tinkerPatch
overrideTinkerPatchConfiguration = true
// 編譯更新檔包時,必需指定基線版本的apk,預設值為空
// 如果為空,則表示不是進行更新檔包的編譯
// @{link tinkerPatch.oldApk }
baseApk = "${bakPath}/${baseApkDir}/app-release.apk"
// 對應tinker插件applyMapping
baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
// 對應tinker插件applyResourceMapping
baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
// 建構基準包和更新檔包都要指定不同的tinkerId,并且必須保證唯一性
tinkerId = "${myTinkerId}"
// 建構多管道更新檔時使用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
}
/**
* 一般來說,我們無需對下面的參數做任何的修改
* 對于各參數的詳細介紹請參考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
//oldApk ="${bakPath}/${appName}/app-release.apk"
ignoreWarning = false
useSign = true
dex {
dexMode = "jar"
pattern = ["classes*.dex"]
loader = []
}
lib {
pattern = ["lib/*/*.so"]
}
res {
pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
ignoreChange = []
largeModSize =
}
packageConfig {
}
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
// path = "/usr/local/bin/7za"
}
buildConfig {
keepDexApply = false
//tinkerId = "1.0.1-base"
//applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" // 可選,設定mapping檔案,建議保持舊apk的proguard混淆方式
//applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可選,設定R.txt檔案,通過舊apk檔案保持ResId的配置設定
}
}
建立一個MyApplicationLike繼承DefaultApplicationLike
初始化什麼的都按順序寫好了,直接cv
class MyApplicationLike(application: Application, tinkerFlags: Int,
tinkerLoadVerifyFlag: Boolean, applicationStartElapsedTime: Long,
applicationStartMillisTime: Long, tinkerResultIntent: Intent) : DefaultApplicationLike(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent) {
private var mContext: Application? = null
override fun onCreate() {
super.onCreate()
mContext = application
//配置Tinker
configTinker()
//初始化圖檔加載,初始化xxx自己用到的都寫到這裡來
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
override fun onBaseContextAttached(base: Context?) {
super.onBaseContextAttached(base)
// you must install multiDex whatever tinker is installed!
MultiDex.install(base)
// 安裝tinker
Beta.installTinker(this)
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
fun registerActivityLifecycleCallback(callbacks: Application.ActivityLifecycleCallbacks) {
application.registerActivityLifecycleCallbacks(callbacks)
}
override fun onTerminate() {
super.onTerminate()
Beta.unInit()
}
companion object {
val TAG = "Tinker.MyApplicationLike"
}
private fun configTinker() {
// 設定是否開啟熱更新能力,預設為true
Beta.enableHotfix = true
// 設定是否自動下載下傳更新檔,預設為true
Beta.canAutoDownloadPatch = true
// 設定是否自動合成更新檔,預設為true
Beta.canAutoPatch = true
// 設定是否提示使用者重新開機,預設為false
Beta.canNotifyUserRestart = true
// 更新檔回調接口
Beta.betaPatchListener = object : BetaPatchListener {
override fun onPatchReceived(patchFile: String) {
Toast.makeText(mContext, "更新檔下載下傳位址" + patchFile, Toast.LENGTH_SHORT).show()
}
override fun onDownloadReceived(savedLength: Long, totalLength: Long) {
Toast.makeText(mContext,
String.format(Locale.getDefault(), "%s %d%%",
Beta.strNotificationDownloading,
(if (totalLength == L) else savedLength * / totalLength).toInt()),
Toast.LENGTH_SHORT).show()
}
override fun onDownloadSuccess(msg: String) {
Toast.makeText(mContext, "更新檔下載下傳成功", Toast.LENGTH_SHORT).show()
}
override fun onDownloadFailure(msg: String) {
Toast.makeText(mContext, "更新檔下載下傳失敗", Toast.LENGTH_SHORT).show()
}
override fun onApplySuccess(msg: String) {
Toast.makeText(mContext, "更新檔應用成功", Toast.LENGTH_SHORT).show()
}
override fun onApplyFailure(msg: String) {
Toast.makeText(mContext, "更新檔應用失敗", Toast.LENGTH_SHORT).show()
}
override fun onPatchRollback() {
}
}
// 設定開發裝置,預設為false,上傳更新檔如果下發範圍指定為“開發裝置”,需要調用此接口來辨別開發裝置
Bugly.setIsDevelopmentDevice(mContext, false)
// 多管道需求塞入
// String channel = WalleChannelReader.getChannel(getApplication());
// Bugly.setAppChannel(getApplication(), channel);
// 這裡實作SDK初始化,appId替換成你的在Bugly平台申請的appId
Bugly.init(mContext, AppId.APP_ID_BUGLY, true)
}
}
再建立一個項目的TestApplication繼承自TinkerApplication
注意第二個參數填寫自己對應的MyApplicationLike包名,cv的時候注意下
class TestApplication : TinkerApplication(ShareConstants.TINKER_ENABLE_ALL, "com.xxx.MyApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false) {
//内部什麼都不用寫
}
修改 Manifest
的 name
屬性
Manifest
name
寫自己剛才建立的内部是空的TestApplication
<application
android:name=".example.TestApplication"
...
- 然後
必須加入以下權限和配置Manifest
<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"/>
//注意我沒有寫application标簽,自己放在application的内部
<activity
android:name="com.tencent.bugly.beta.ui.BetaActivity"
android:configChanges="keyboardHidden|orientation|screenSize|locale"
android:theme="@android:style/Theme.Translucent"/>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
執行 build
等待導包建構
build
build成功之後,點選右側欄執行
gradle
assembleRelease
等待建構release包成功
這時候需要用到最開始提到的打包的jks那些配置,是以一定寫到build.gradle中
成功後可以在
下的
app
中看到
build
-
新生成的檔案表示基準apkbakApk
-
已經是代簽名的正式包,可以直接安裝到手機或者模拟器了app-release.apk
制作更新檔檔案
在app某個頁面中随便改個textView
證明這是需要更新的類或者資源檔案
打開 tinker-support.gradle
檔案
tinker-support.gradle
将那行注釋掉,放開
base
那行,多次生成更新檔記得改
patch
那個數字就行
.0.1
def baseApkDir = "app-1218-12-53-16"
//def myTinkerId = "base-" + rootProject.ext.android.versionName // 用于生成基準包(不用修改)
def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.1" // 用于生成更新檔包(每次生成更新檔包都要修改一次,最好是 patch-${versionName}.x.x)
然後打開右側欄 build.gradle
執行 buildTinkerPatchRelease
build.gradle
buildTinkerPatchRelease
等待執行完成成功後
打包的更新檔檔案在下, 重點 是
app/build/output/patch
這就是更新檔檔案
xx7zip.apk
打開Bugly官網
官網https://bugly.qq.com/v2/index
注冊->登入->建立應用->拿到appId->到項目修改
将上面的7zip.apk 上傳到 項目->應用更新->熱更新->釋出新更新
- 顯示下發中,還需要稍等一會
- 再次打開之前安裝到手機或模拟器的apk 就會提示熱更新并重新開機應用