熱修復——Bugly讓熱修復變得如此簡單
一、簡述
在上一篇《熱修復——Tinker的整合與使用》中,根據Tinker官方Wiki集成了Tinker,但那僅僅只是本地整合,有一個重要的問題沒有解決,那就是補丁從伺服器下發到使用者手機上,如果你團隊中的後臺開發人員實力夠強,那麼完全可以自己做一個補丁管理系統,但我想應該沒多少人願意花精力在這個後臺管理系統的開發上面吧,且開發有時候就是在造bug,鬼知道會挖出一個多大的坑呢?對於這樣的一個問題,據我所知,市面上有3種Tinker的補丁管理系統,如下:
「Bugly」和「tinker-manager」是免費的,「tinkerpatch」是收費的,因為「tinkerpatch」收費,所以暫時不做考慮。Bugly由騰訊團隊開發並維護,穩定性肯定沒得說,而「tinker-manager」是GitHub上個人開發者開發維護的,穩定性沒法保證(我沒有貶低開發者的意思,畢竟勢單力薄,人多力量大嘛),故本人覺得,Bugly是目前最優的Tinker熱修復解決方案。在開始進入Bugly整合之前,你可以先
二、獲取App ID
要使用Bugly的熱修復功能,首先得註冊並登入Bugly,然後點選進入「Bugly產品頁面」,或點選“我的產品 ”。
我這個賬號之前是沒有建立過產品,所以這裡什麼也沒有,接著點選“新建產品”。
填寫必要的資訊後,點選“儲存”。
通過“產品設定”,選擇剛剛建立的產品(圖中第3步),可以檢視到產品對應的App ID。
這個App ID很重要,先記錄好,後續會用到。
Demo的App ID為: 3062edb401。不要用我的,對你來說一點用處都沒有,請使用你自己產品的App ID。
二、新增外掛依賴
專案的build.gradle:
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
// tinkersupport外掛(1.0.3以上無須再配置tinker外掛)
classpath "com.tencent.bugly:tinker-support:1.1.1"
}
三、整合SDK
app的build.gradle:
apply from: 'tinker-support.gradle' android { defaultConfig { ... // 開啟multidex multiDexEnabled true } // recommend dexOptions { jumboMode = true } // 簽名配置 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") } } // 構建型別 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'] } } } dependencies { ... implementation "com.android.support:multidex:1.0.1" // 多dex配置 implementation 'com.tencent.bugly:crashreport_upgrade:1.3.4'// 遠端倉庫整合方式(推薦) }
簽名配置部分請根據你專案的實際情況修改,如:
四、配置Tinker
在app的build.gradle檔案同級目錄下建立一個tinker-support.gradle檔案,內容如下:
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此處填寫每次構建生成的基準包目錄
*/
def baseApkDir = "tinker-bugly-1211-16-01-34"
def myTinkerId = "base-" + rootProject.ext.android.versionName // 用於生成基準包(不用修改)
//def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.0" // 用於生成補丁包(每次生成補丁包都要修改一次,最好是 patch-${versionName}.x.x)
/**
* 對於外掛各引數的詳細解析請參考
*/
tinkerSupport {
// 開啟tinker-support外掛,預設值true
enable = true
// 是否啟用加固模式,預設為false.(tinker-spport 1.0.7起支援)
// isProtectedApp = true
// 是否開啟反射Application模式
enableProxyApplication = true
// 是否支援新增非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 {
...
}
1、overrideTinkerPatchConfiguration
當overrideTinkerPatchConfiguration = true時,tinkerPatch可以省略不寫,Bugly會載入預設的Tinker配置。但請注意,如果你的so檔案不是存放在libs目錄下(與src目錄同級),又或者資原始檔的存放在你自定義的目錄中,那麼這時你要小心了,這些檔案在製作補丁包時不會被檢測,也就是說這些so檔案和資原始檔將不會被熱修復,這種情況下就需要將overrideTinkerPatchConfiguration = false,並設定tinkerPatch的lib和res屬性。
2、baseApkDir
baseApkDir是基準包(也稱基線包)的目錄,在生產補丁時需要根據基準包在bakApk下具體資料夾名字修改,如:bakApk/xxxx,到時生成補丁包時要將baseApkDir的值改為xxxx。(xxxx是Tinker自動生成的,根據時間戳來命名)。
3、tinkerId
tinkerId是Bugly熱修復方案最最重要的一個因素,一般取值為git版本號、versionName等等(我習慣用versionName),它會將補丁包與基準包產生對應關係,假設基準包的tinkerId為 base-1.0,則生成的補丁包中的YAPATCH.MF檔案關係如下:
Bugly要求baseApk(基準包)的tinkerId與補丁包的tinkerId要不一樣。所以,在生成基準包時,請用如下tinkerId:
def myTinkerId = "base-" + rootProject.ext.android.versionName // 用於生成基準包(不用修改)
當生成補丁包時,請使用如下tinkerId:
def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.0" // 用於生成補丁包(每次生成補丁包都要修改一次,最好是 patch-${versionName}.x.x)
對於同一個基準包,我們可能會多次生成補丁包上傳到Bugly的熱修復管理後臺,這時,這些補丁包的tinkerId也要不一樣,不然的話,當客戶手機上的App在獲取補丁時,會錯亂(親測,當同個基準包的補丁包的tinkerId一樣時,App每次重啟都會獲取不同的補丁包,導致tinkerId相同的補丁包輪流下發)。所以,”patch-” + rootProject.ext.android.versionName + “.0.0”中的”.0.0”(稱為計數)就是為了區分每次生成的補丁包,如.0.1,.0.2等等,建議versionName更新時計數重置。
因為Tinker的配置放在了tinker-support.gradle檔案中,與app的build.gradle不在同一個檔案中,所以沒辦法通過android.defaultConfig.versionName直接獲取App的versionName,這裡我使用了config.gradle來提取共同的屬性,rootProject.ext.android.versionName獲取的是config.gradle中的versionName屬性,詳情請百度。
4、補丁新舊判定
def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.0" // 用於生成補丁包(每次生成補丁包都要修改一次,最好是 patch-${versionName}.x.x)
對於一個基準包,可以在Bugly上釋出多個補丁包(切記tinkerid不同),這裡或許會讓你誤以為計數越大,表明補丁越新,這是錯誤的,這個計數僅僅只是區分不同的補丁包而已,它沒有標記補丁新舊的作用,補丁新舊由Bugly來判定,最後上傳的補丁便是最新的補丁,舉個例子,我在昨天上傳了tinkerid為”patch-1.0.0.9”的補丁1,在今天上傳了tinkerid為”patch-1.0.0.1”的補丁2,雖然補丁2的計數比補丁1小,但補丁2比補丁1晚上傳,所以補丁2是最新的補丁,即補丁新舊與計數無關。Bugly會下發並應用最新的補丁(即補丁2),但還是建議計數從小到大計算,這裡僅僅只是說明Bugly如何判定補丁新舊罷了。
五、初始化SDK
Bugly的初始化工作需要在Application中完成,但對原生Tinker來說,預設的Application是無法實現熱修復的。看過Tinker官方Wiki的人應該知道,Tinker針對Application無法熱修復的問題,給予開發者兩個選擇,分別是:
- 使用「繼承TinkerApplication + DefaultApplicationLike」。
- 使用「DefaultLifeCycle註解 + DefaultApplicationLike」。
這2種選擇都需要對自定義的Application進行改造,對於自定義Application程式碼不多的情況來說還可以接受,但有些情況還是比較”討厭”這2種選擇的,對此,Bugly給出了它的2種解決方法,分別如下:
- 使用原來的自定義Application,Bugly通過反射為App動態生成新的Application。
- 使用「繼承TinkerApplication + DefaultApplicationLike」。
DefaultLifeCycle註解在Bugly中被閹割了。
分別對應tinker-support.gradle檔案中enableProxyApplication的值:true或false。
1、enableProxyApplication = true
Bugly將通過反射的方式針對專案中自定義的Application動態生成新的Application,下圖是原始碼中的AndroidManifest.xml和編譯好的apk中的AndroidManifest.xml:
既然將enableProxyApplication的值設定為true,那接下來的重點就是完成Bugly的初始化工作了。需要在自定義的Application的onCreate()中進行Bugly的配置,在attachBaseContext()中進行Bugly的安裝:
public class MyApplication extends Application {
private Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
// 這裡實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
// 除錯時,將第三個引數改為true
configTinker();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// you must install multiDex whatever tinker is installed!
MultiDex.install(mContext);
// 安裝tinker
// 此介面僅用於反射Application方式接入。
Beta.installTinker();
}
}
注意:
1. Bugly的安裝必須在attachBaseContext()方法中,否則將無法從Bugly伺服器獲取最新補丁。
2. tinker需要你開啟MultiDex,你需要在dependencies中進行配置compile “com.android.support:multidex:1.0.1”才可以使用MultiDex.install方法。
最後在清單檔案中,宣告使用我們自定義的Application即可:
<application
android:name="com.lqr.MyApplication"
...>
2、enableProxyApplication = false
這是Bugly推薦的方式,穩定性有保障(因為第1種方式使用的是反射,可能會存在不穩定的因素),它需要對Application進行改造,首先就是繼承TinkerApplication,然後在預設的建構函式中,將第2個引數修改為你專案中的ApplicationLike繼承類的全限定名稱:
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "com.lqr.SampleApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
注意:這個類整合TinkerApplication類,這裡面不做任何操作,所有Application的程式碼都會放到ApplicationLike繼承類當中
引數解析
引數1:tinkerFlags 表示Tinker支援的型別 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
引數2:delegateClassName Application代理類 這裡填寫你自定義的ApplicationLike
引數3:loaderClassName Tinker的載入器,使用預設即可
引數4:tinkerLoadVerifyFlag 載入dex或者lib是否驗證md5,預設為false
接著就是建立ApplicationLike繼承類:
public class SampleApplicationLike extends DefaultApplicationLike {
public static final String TAG = "Tinker.SampleApplicationLike";
private Application mContext;
public SampleApplicationLike(Application application, int tinkerFlags,
boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@Override
public void onCreate() {
super.onCreate();
mContext = getApplication();
configTinker();
}
@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
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
@Override
public void onTerminate() {
super.onTerminate();
Beta.unInit();
}
}
注意:
SampleApplicationLike這個類是Application的代理類,以前所有在Application的實現必須要全部拷貝到這裡,在onCreate方法呼叫SDK的初始化方法,在onBaseContextAttached中呼叫Beta.installTinker(this)。
最後在清單檔案中,宣告改造好的Application(注意不是ApplicationLike):
<application
android:name="com.lqr.SampleApplication"
...>
3、配置Bugly
這是Bugly官方給出的配置,應有盡有,註釋也很nice,請仔細看看,對專案的功能拓展與使用者體驗有幫助:
private void configTinker() {
// 設定是否開啟熱更新能力,預設為true
Beta.enableHotfix = true;
// 設定是否自動下載補丁,預設為true
Beta.canAutoDownloadPatch = true;
// 設定是否自動合成補丁,預設為true
Beta.canAutoPatch = true;
// 設定是否提示使用者重啟,預設為false
Beta.canNotifyUserRestart = true;
// 補丁回撥介面
Beta.betaPatchListener = new BetaPatchListener() {
@Override
public void onPatchReceived(String patchFile) {
Toast.makeText(mContext, "補丁下載地址" + patchFile, Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadReceived(long savedLength, long totalLength) {
Toast.makeText(mContext,
String.format(Locale.getDefault(), "%s %d%%",
Beta.strNotificationDownloading,
(int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)),
Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadSuccess(String msg) {
Toast.makeText(mContext, "補丁下載成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadFailure(String msg) {
Toast.makeText(mContext, "補丁下載失敗", Toast.LENGTH_SHORT).show();
}
@Override
public void onApplySuccess(String msg) {
Toast.makeText(mContext, "補丁應用成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onApplyFailure(String msg) {
Toast.makeText(mContext, "補丁應用失敗", Toast.LENGTH_SHORT).show();
}
@Override
public void onPatchRollback() {
}
};
// 設定開發裝置,預設為false,上傳補丁如果下發範圍指定為“開發裝置”,需要呼叫此介面來標識開發裝置
Bugly.setIsDevelopmentDevice(mContext, false);
// 多渠道需求塞入
// String channel = WalleChannelReader.getChannel(getApplication());
// Bugly.setAppChannel(getApplication(), channel);
// 這裡實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
Bugly.init(mContext, "e9d0b7f57f", true);
}
這裡就用到了一開始獲取到的App ID了,將其傳入Bugly.init()方法的第二個引數,切記,用你自己的App ID。
其中如下兩個方法很重要:
- Bugly.setIsDevelopmentDevice()
設定當前裝置是不是開發裝置,這跟Bugly上傳補丁包時所選的”下發範圍”有關。
- Bugly.init(context, appid, isDebug)
這個方法除了設定App ID外,還可以設定是否輸出Log,可以觀察到Bugly在App啟動時做了哪些聯網操作。
六、AndroidManifest.xml
1、 許可權配置
<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"/>
2、Activity配置
<activity
android:name="com.tencent.bugly.beta.ui.BetaActivity"
android:configChanges="keyboardHidden|orientation|screenSize|locale"
android:theme="@android:style/Theme.Translucent"/>
3、FileProvider配置
<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>
如果你使用的第三方庫也配置了同樣的FileProvider, 可以通過繼承FileProvider類來解決合併衝突的問題,示例如下:
<provider
android:name=".utils.BuglyFileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="name,authorities,exported,grantUriPermissions">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"
tools:replace="name,resource"/>
</provider>
4、升級SDK下載路徑配置
在res目錄新建xml資料夾,建立provider_paths.xml檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
<external-path name="beta_external_path" path="Download/"/>
<!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
<external-path name="beta_external_files_path" path="Android/data/"/>
</paths>
注:1.3.1及以上版本,可以不用進行以上配置,aar已經在AndroidManifest配置了,並且包含了對應的資原始檔。
七、混淆
# Bugly混淆規則
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# 避免影響升級功能,需要keep住support包的類
-keep class android.support.**{*;}
好了,整合完畢,接下來就是製作基準包、補丁包和上傳補丁包了。
八、製作基準包
在app編碼完成並測試完成後,就是打包上線了,上線前打的包就是基準包啦,下面我們就來製作基準包,分3步:
- 開啟app下的tinker-support.gradle檔案。
- 將帶”base”的tinkerId註釋解開,並註釋掉帶”patch”的tinkerId。
- 雙擊執行build下的assembleRelease。
通常主Module的名字是”app”,但我這個Demo是”tinker-bugly”,所以你執行第3步時,要根據具體專案找到要製作基準包的主Module。
AS在執行assembleRelease指令時,就是在編譯基準包了,當編譯完成時,app的build目錄下會自動生成基準包資料夾,以時間戳來命名的(也就是說,每次執行assembleRelease指令都會在build目錄建立不同的基準包資料夾)。
這3個檔案對之後製作補丁包來說是相當重要的,你需要做的就是將這3個檔案儲存好,可以儲存到雲盤、Git伺服器上等等,但就不要讓它就這麼放著,因為在你執行clean Project時,app的build目錄會被刪除,這樣基準包及mapping與R檔案都會丟失。
到這裡,你就可以把它(基準包:tinker-bugly-release.apk)上架到應用市場了。試下Demo:
tip:加固與多渠道打包
本篇不涉及具體的加固與多渠道打包。
1、加固
如果你的app需要加固,那就需要在製作基準包之前,將tinker-support.gradle檔案的isProtectedApp = true的註釋去掉,然後加固,重新簽名,最後上架,它對加固平臺也有一定的要求。
2、多渠道打包
分「gradle配置productFlavors方式」與「多渠道打包工具打多渠道包方式(推薦)」。
九、補丁包
現在要動態修復App了,對於程式碼修復、so庫修復、資原始檔修復,分別對應Demo中的”say something”、”get string from .so”、”我的頭像”,修復過程無非是改程式碼,替換so檔案,替換資原始檔,這裡就不演示了,直接開始製作補丁包,先將tinker-support.gradle檔案開啟。
1、基準包命名
確保基準包及相關檔案的命名與配置檔案中的一致:
2、修改baseApkDir與tinkerId
- 修改baseApkDir的值為基準包所有資料夾的名字。
- 註釋掉帶”base”的tinkerId,取消帶”patch”的tinkerId的註釋(多次生成補丁時,記得修改”計數”,區分不同的補丁)。
3、執行編譯,生成補丁
開啟側邊的Gradle標籤,找到專案的主Module,雙擊tinker-support下的buildTinkerPatchRelease指令,生成補丁包。
當編譯完成後,在app的build/outputs/patch目錄下會在”patch_singed_7zip.apk”檔案,它就是補丁包,雙擊開啟它,可以看到其中有一個YAPATCH.MF,裡面記錄了基準包與補丁包的tinkerId(兩者是肯定不同,如果一樣則說明配置有問題了)。
十、上傳補丁包
1、流程圖解
點選你要管理的產品後,依次點選”應用升級”、”熱更新”,可以檢視到該產品的補丁下發情況(這個產品我還沒上傳過補丁,故一片空白)。
按下圖順序操作即可上傳補丁包:
2、上傳失敗分析
有可能你在上傳完補丁包時,頁面會提示”未匹配到可應用補丁包的App版本,請確認補丁包的基線版本是否已經發布”。
遇到這種情況請先冷靜,首先來說明一件事:Bugly怎麼知道基線版本是否已經發布?
通常按我們理解的,基準包釋出就是上架到應用市場,但應用市場又不會通知Bugly某某產品已經上架了,對吧。其實,Bugly的上架通知是這樣的:當基準包在手機上啟動時,Bugly框架就會讓App聯網通知Bugly的伺服器,同時上傳當前App的版本號、tinkerId等資訊,它這麼做的目的有如下兩個:
- 標記某個tinkerId的基準包已經被安裝到手機上使用了(即釋出)。
- 獲取該tinkerId的基準包最新的補丁資訊。
所以,當出現了”未匹配到可應用補丁包的App版本,請確認補丁包的基線版本是否已經發布”這樣的提示時,可以確定,這個基準包的tinkerId等資訊沒有被上傳到Bugly伺服器,對此,鄙人將踩過的坑總結起來,摸索出了自己的解決方法,分如下幾步:
- 檢查App是否能夠聯網。
- 檢查App ID是否正確。
- 結合enableProxyApplication的取值,檢查AndroidManifest.xml中宣告的Application是否寫對。
- 檢查Bugly的安裝是不是在attachBaseContext()或onBaseContextAttached()方法中完成。
像我就犯過這樣的錯,明明在tinker-support.gradle檔案中設定了enableProxyApplication = true,結果在AndroidManifest.xml中卻聲明瞭TinkerApplication的繼承類。
所以這裡只需要將AndroidManifest.xml中宣告我們自定義的Application即可(MyApplication)。
除了聯網問題以外,其他的幾種情況都需要重新生成基準包。這裡再分享一個可以快速確定App是否有上傳過版本資訊的方法:
3、上傳成功
先驗證下上面的方法,當我把問題解決掉之後,把重新生成的基準包安裝到手機上開啟(此時Bugly框架會上傳App的版本號、tinkerId到伺服器),再檢視”版本管理”,出現了,版本號為”1.0”(其實就是App的versionName)。
再回頭來看看上傳補丁,這次又會有什麼不同呢?
耶,成功。點選”立即下發”,可以看到現在補丁處於”下發中”狀態:
隨便來看看使用者手中的App是什麼反應吧(真正將補丁下發到使用者手機上的這段時間可能會有點久,不是立即下發的):
再回頭看看Bugly伺服器上的補丁下發情況:
十一、其他
1、補丁管理
Bugly伺服器除了可以上傳下發補丁外,還可以對補丁進行管理:
- 停止下發:不再把該補丁下發到客戶手機上(停止後可重新開啟)。
- 撤回:將Bugly伺服器上的某個補丁刪掉,這個操作是不可逆的(不知道使用者手機上被成功打上的補丁是否也會被解除安裝)。
- 編輯:可以修改”下發範圍”(開發裝置、全量裝置、備註等等)。
- 歷史:檢視修改記錄。
2、強調一下一些需要注意的地方
- 一個基準包可以有多個補丁包,Bugly會將最新的補丁進行下發(舊補丁預設會變成”停止下發狀態”),客戶手機上的App的舊補丁會被新補丁覆蓋。
- 製作基礎包時,請使用帶”base”的tinkerId,執行的是assembleRelease指令。
- 製作基礎包後,一定要將baseApk、mapping.txt、R.txt儲存好,不能弄丟了。
- 製作補丁包時,先將baseApkDir的值修改為基準包所有資料夾的名字,然後啟用帶”patch”的tinkerId,同時修改”計數”,執行的是buildTinkerPatchRelease指令。
- 製作補丁包後,最後開啟它檢查YAPATCH.MF檔案中的from和to資訊,檢查該補丁包對應的基準包的tinkerId是否正確。
- 建議上線的基準包將Bugly的Log輸出關閉:Bugly.init(mContext, AppID, false);
- 如果是測試補丁包是否用效果,建議設定為開發裝置:Bugly.setIsDevelopmentDevice(mContext, true);
- so檔案需要手動先呼叫一下 TinkerLoadLibrary.installNavitveLibraryABI(this, CPU_ABI) 方法才能生效。
3、Bugly官方文件
4、本系列文章連結
最後貼下Demo連結
相關推薦
熱修復——Bugly讓熱修復變得如此簡單
一、簡述 在上一篇《熱修復——Tinker的整合與使用》中,根據Tinker官方Wiki集成了Tinker,但那僅僅只是本地整合,有一個重要的問題沒有解決,那就是補丁從伺服器下發到使用者手機上,如果你團隊中的後臺開發人員實力夠強,那麼完全可以自己做一個補丁管理
APICloud可以讓你開發變得很簡單
sta key class ucc 驗證 doctype status tex pid QQ登錄、分享怎麽做?相信APICloud可以讓你開發變得很簡單。這裏分享一個demo供大家參考。 1.使用模塊,先打開它的開發文檔http://www.apicloud.com/m
前端分頁神器,jquery grid的使用(前後端聯調),讓分頁變得更簡單。
jquery grid 是一款非常好用的前端分頁外掛,下面來講講怎麼使用。 首先需要引入jquery grid 的CSS和JS (我們使用的是bootstrap的樣式) 下面我們通過一個例子來講解,需求是:查詢使用者列表(支援分頁功能)。 一、前端 htm
[翻譯]Kafka Streams簡介: 讓流處理變得更簡單
看到一篇不錯的譯文,再推送一撥 Introducing Kafka Streams: Stream Processing Made Simple 這是Jay Kreps在三月寫的一篇文章,用來介紹Kafka Streams。當時Kafka Streams
Moment.js讓日期處理變得更簡單
七天 根據 日期時間 ear script 號碼 eem 參考 number 在大多數的vue項目中,都可以看到引用了Moment.js這個JavaScript 日期處理類庫,進入官網看了一下,確實十分實用,基本日常用到的和時間相關的邏輯處理,都能通過moment.js輕松
一鍵套紅——讓公文起草變得更加簡單
目前的網路協同辦公系統應用中,公文的起草通常要涉及到模板套紅這一步驟,通常的解決方式有兩種:一種是前期套紅,另外一種是後期套紅。 前期套紅指的是在起草公文時,使用者在一個帶紅頭的模板檔案裡撰寫公文內容,一般這裡使用的模板實際上是一個帶紅頭的普通doc檔案。 此方法的優
JQuery學習—封裝,讓這個世界變得更簡單
1、編者寄語 小編認為,JQuery就是用JavaScript封裝成的一些js方法,又將這些方法封裝在一起,稱為JQuery。除此之外,js還有兩個庫就是Prototype、MooTools,這裡不再多做介紹。 在之前學過的js中知道,js中的函式和方法
深入解析 Kubebuilder:讓編寫 CRD 變得更簡單
作者 | 劉洋(炎尋) 阿里雲高階開發工程師 **導讀:**自定義資源 CRD(Custom Resource Definit
如何讓OKR實踐變得更簡單一些
什麼是OKR 近幾年OKR的概念在國內開始流行起來了,之前公司也有人想實施OKR,但現在看來之前的OKR實施者只是在哪兒看了一下OKR的資料,本著跟老闆邀功的想法比較功利的在推進,所以基本沒有效果,今年換了一個不錯的團隊,比較幸運的成為了技術部(150人左右)OKR實踐的操盤手,於是整個對OKR的認識便不太
4 個概念,1 個動作,讓應用管理變得更簡單
作者: 劉洋(炎尋) EDAS-OAM 架構與開發負責人 鄧洪超 OAM spec maintainer 孫健波(天元) OAM spec maintainer 隨著以 K8s 為主的雲原生基礎架構遍地生根,越來越多的團隊開始基於 K8s 搭建持續部署、自助式釋出
jQuery Validation讓驗證變得如此easy(二)
inpu jsb sage on() popu action func fonts strong 上一個樣例我們是統一引用jquery.validate.js這樣全部必填字段的提示信息都將是This field is required.如今要改成動態提示,比方姓名假設為
讓開發變得更簡單 | 阿里雲中間件推出全新開發者服務
要碼出未來,除了程式設計師專屬的節日福利,還需要實打實的為程式設計師解決手頭的難題。 10月24日,阿里雲中間件推出全新開發者服務,釋出3款開發工具,包括Alibaba Cloud Toolkit 、鏈路追蹤Tracing Analysis和應用高可用服務AHAS,旨在幫助開發
Console命令詳解,讓除錯js程式碼變得更簡單
Firebug是網頁開發的利器,能夠極大地提升工作效率。 但是,它不太容易上手。我曾經翻譯過一篇《Firebug入門指南》,介紹了一些基本用法。今天,繼續介紹它的高階用法。 =================================== Firebug控制檯詳
讓Birt報表指令碼資料來源變得既簡單又強大
概述:執行在 JVM 上的 SQL 函式和儲存過程 總所周知,有些資料庫沒有強大的分析函式(eg. Mysql), 有些資料庫沒有儲存過程(eg. Vertica),當遇到複雜的資料計算,往往只能通過 Python,R 等外部指令碼來實現,但這些指令碼語言和主流工程語言(J
讓安卓開發變得很簡單——Lombok,lambda,Instant run
今天開始介紹專案中的一些奇淫技巧,幫助安卓開發者更快更方便的開發! 1.騷年技巧一之Lombok Lombok簡介 Lombok是一個可以通過簡單的註解形式來幫助我們簡化消除一些必須有但顯得很臃腫的Java程式碼的工具,通過使用對應的註解,可以
Cygwin讓windows運維變得更簡單
Cygwin簡介 從使用角度來看:Cygwin就是一個windows軟體,該軟體就是在windows上模擬linux作業系統。簡言之,cygwin是一個在windows平臺上執行的 linux模擬環境,使用一個Dll(動態連結庫)來實現,這樣,我們可以開發出Cygwin下的UNIX工具,使用這個DL
讓iOS開發變得更有效率-分類、工具類
在工作中整理的一些分類與工具類,分享給大家。這些工具類可以減少專案中的程式碼量,讓程式碼變得更簡潔,可以大大的提升專案的效率,直接拖到專案中使用即可。下載地址:https://github.com/leeSmile/tools/tree/master/textTools 歡迎s
雲端計算讓大資料分析變得更簡單、快捷
網際網路、雲端計算以及大資料,如今成了三個密不可分的詞彙。一般而言,一家網際網路公司一定同時是資料公司,反之,不能從資料中獲取利益的網際網路公司一定不是一個好的雲端計算應用者。更進一步,挖掘資料價值很多企業都會做,但如果不能用最低成本得到資料價值,企業同樣活不下去。把資料以低
【譯】AI 讓科技公司變得更強大嗎
機器學習可能是當今技術中最重要的基本趨勢。由於機器學習的基礎是資料 - 大量的資料 - 很常見的是,人們越來越擔心已經擁有大量資料的公司會變得更強大。這有一定的道理,但是以相當狹窄的方式,同時ML也看到了很多能力的擴散 - 可能存在與集中化一樣多的分散化。 首先,說機器學習是關於資料的意思是什
一款能讓Vive開發變得簡單的外掛——Vive Input Utility使用指南
相信各位小夥伴們在使用SteamVR Unity Plugin的過程中應該都遇到過這樣的問題:獲取裝置很麻煩,裝置在重啟後indexID會改變從而導致設定好的左右手裝置出現交錯;無法與UGUI的事件系統連線導致無法使用UGUI等等很多問題。(見圖1) 官