編輯:關於Android編程
隨著時代的發展,由於公司的項目需要去求變化平凡計劃總趕不上變化,H5的高靈活性,開發周期短,更新速度快H5以及一些混合開發越來越被看好,然而主要原因之一:這種混合開發的方式容錯率大,更新和修復BUG快.不用發布版本就可以讓用戶不覺的情況下就更新對應的內容或者BUG,我們不能否認混合開發的快捷,正在此前提下熱修復和熱更新技術也得到了非常大的發展,不管熱修復還是熱更新,都是對app的內容或者邏輯變化做出像web頁面更新一樣的體驗.而本文只對熱修復進行探索,不對H5進行深入研究.而今天的主人公的話是微信Tinker.
不久前微信開源了Tinker,github的star數量直飚5000+,我的天,還在等什麼,學習學習.
什麼是熱修復
熱修復補丁(hotfix),又稱為patch,指能夠修復軟件漏洞的一些代碼,是一種快速、低成本修復產品軟件版本缺陷的方式。
前言中描述的”不用發布版本就可以讓用戶不覺的情況下就更新對應的內容或者BUG”可能不算准確,所以我自行百度了一下.
熱修復說白了就是”打補丁”,比如你們公司上線一個app,用戶反應有重大bug,需要緊急修復。如果按照通
常做法,那就是程序猿加班搞定bug,然後測試,重新打包並發布。這樣帶來的問題就是成本高,效率低。於是,熱
修復就應運而生.一般通過事先設定的接口從網上下載無Bug的代碼來替換有Bug的代碼。這樣就省事多了,用
戶體驗也好.
原理
類似與插件開發,關於插件開發原理,看這篇Android插件原理剖析,其中介紹了一下Java中的類加載器和Android中的類加載器. 熱修復就是利用android中的 DexClassLoader 類加載器,動態加載補丁dex,替換有bug的類
已有的熱修復解決方案:
https://github.com/dodola/HotFix
https://github.com/jasonross/Nuwa
https://github.com/bunnyblue/DroidFix
微信Tinker
Tinker的github地址:https://github.com/Tencent/tinke
Tinker原理:微信Android熱補丁實踐演進之路
官方給出的定義:
Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstalling apk.
Tinker是微信官方的Android熱補丁解決方案,它支持動態下發代碼、So庫以及資源,讓應用能夠在不需要重新安裝的情況下實現更新。當然,你也可以使用Tinker來更新你的插件。
這裡原理以及好處.在這裡就BB了,我們開發人員只需要關心怎麼使用,實現就可以了.不過這裡還是貼出來給大家學習..那麼接下來直接實踐作為一個資深的開發人員學習一個新的技術,第一想到就是去官網看看文檔跑跑Demo,當然我也不例外(資深).
導入Sample工程
tinkerd地址,下載下來解壓打開導入Android Studio,我們只需要把tinker-sample-android這個目錄導入即可.
導入之後,構建一下,想都不用想肯定出錯,提示“tinkerId is not set!!!”,WTF????然後我們肯定會去看他的接入指南,前面一大堆BBBB…
看到了Sample的使用方法內心激動起來以為可以知道了什麼原因了,再次WTF???沒有直接就是運行的後的說明,不能忍,於是我又去網上找找,算是找到了解決的辦法,但是後面才知道這些問題微信維護開源人員被問了煩了,直接列出了常見問題,我都不知道說什麼了…….
問題解決:這是因為沒有正確的配置IDE的git路徑, 若不是通過clone方式下載tinker,需要本地手動commit一次。這裡你也可以使用其他字符作為tinkerId;
我這裡的話直接就把當前的版本號作為id..
補充:關於獲取Git提交版本號?
1git rev-parse –short HEAD
這段代碼主要是用來顯示最近一次提交到HEAD上的記錄編號(類似於“b03b0c4”的字符串,每次提交,字符串都不一樣。個人對git命令行了解不多,如果有知道的大神麻煩指教一下)。
所以前面說的,除了環境變量要配置git(可以在命令行輸入 git –version ,顯示出了版本號,便是配置成功),還要把你的項目與git關聯起來,並且保證有一次提交記錄,才能獲取到該字符串。
具體使用可以看我的另一篇文章:關於git命令“git rev-parse –short HEAD”在android studio中使用與配置的個人探究
個人覺得,加入這段代碼,顯得更麻煩了,還不如直接寫死,或者獲取其他的版本號。
編譯運行原版apk
接下使用assembleDebug命令,再拿到下圖中的app-debug-xxxxx.apk裝在手機上運行
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPrvy1d/Wsb3T1MvQ0Ciyu7n90qrPyLnYsdVJbnN0YW50IFJ1bikgLSZndDtmaWxlLSZndDtzZXR0aW5nLSZndDtCdWlsZC5FJmhlbGxpcDsuLSZndDtJbnN0YW50IFJ1biC12tK7uPbIpbX0vs2/ydLU1MvQ0MHLPC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L3A+DQo8aDEgaWQ9"配置原版apk路徑">
配置原版apk路徑
這裡的oldapkpath是上圖編譯運行原版apk中得到的apk路徑和R.txt路徑配置下就ok
if (buildWithTinker()) {
apply plugin: 'com.tencent.tinker.patch'
tinkerPatch {
* the old apk path, use to diff with the new apk to build
* add apk from the build/bakApk
*/
oldApk = "${bakPath}/app-debug-1108-13-43-27.apk"
ignoreWarning = false
useSign = true
buildConfig {
applyMapping = getApplyMappingPath()
applyResourceMapping = getApplyResourceMappingPath()
tinkerId = getTinkerIdValue()
}
這裡的oldapk也要修改成上面
編譯運行原版apk生成apk的路徑
修改源碼 生成新版apk 補丁
運行起來之後,打開代碼MianActvity,修改代碼,打開Log.e(TAG, “i am on onCreate string:” + getResources().getString(R.string.test_resource))的注釋
再運行下面圖中的tinkerPatchDebug,或者在Terminal使用gradlew tinkerPatchDebug ,Terminal->就是
Android studio 一般左下角的那個cmd控制台一樣的東西
這樣在app/build/outputs/tinkerPatch/debug/patch_signed_7zip.apk路徑下找到這個差異包,也就是我們俗稱的補丁.
推送補丁
然後把patch_signed_7zip.apk放到手機SD卡中去使用命令
adb push ./app/build/outputs/tinkerPatch/debug/patch_signed_7zip.apk /storage/sdcard0/
這裡放置的路徑與apk中獲取補丁位置一致
運行應用,加載補丁
再次運行apk,點擊LoadPatch時調用
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");方法,加載補丁.
查看控制台日志,打印出i am on onCreate string:I am in the base apk就表示成功了
補充:返回鍵退出後進入,並沒有執行修復。
(當時以為是我手機的原因,就沒太在意),現在有朋友評論說自己也加載成功但沒法修復,是不是跟我一樣按得返回鍵退出。
殺進程後再進入 ,應該就可以修復成功了,如果不成功,把補丁包逆向一下,看看自己修復的部分有沒有在裡面。
集成到自己的項目中
1. 添加gradle依賴
在項目的build.gradle中,添加tinker-patch-gradle-plugin的依賴
buildscript {
dependencies {
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.3')
}
}
然後在app的gradle文件app/build.gradle,我們需要添加tinker的庫依賴以及apply tinker的gradle插件.
dependencies {
//可選,用於生成application類
provided('com.tencent.tinker:tinker-android-anno:1.7.3')
//tinker的核心庫
compile('com.tencent.tinker:tinker-android-lib:1.7.3')
}
...
...
//apply tinker插件
apply plugin: 'com.tencent.tinker.patch'
2. 添加生成補丁方法
tinkerPatch {
//有問題的apk的地址,就是要修復BUG的那個apk,這是在電腦上位置
oldApk = "D://1//app-debug-old.apk"
ignoreWarning = false
useSign = true
buildConfig {
tinkerId = "1.0"
}
packageConfig {
//寫這個為了修復一個bug,詳見github issue #22
configField("TINKER_ID", "1.0")
}
dex {
dexMode = "jar"
pattern = ["classes*.dex", "assets/secondary-dex-?.jar"]
loader = ["com.tencent.tinker.loader.*", "com.kairu.rxjava.app.MyApplicationLike"]
}
lib {
pattern = ["lib/armeabi/*.so", "lib/arm64-v8a/*.so", "lib/armeabi-v7a/*.so", "lib/mips/*.so", "lib/mips64/*.so", "lib/x86/*.so", "lib/x86_64/*.so"]
}
res {
pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
largeModSize = 100
}
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
}
}
3. 配置Application
程序啟動時會加載默認的Application類,這導致我們補丁包是無法對它做修改了。如何規避?在這裡我們並沒有使用類似InstantRun hook Application的方式,而是通過代碼框架的方式來避免,這也是為了盡量少的去反射,提升框架的兼容性。
這裡我們要實現的是完全將原來的Application類隔離起來,即其他任何類都不能再引用我們自己的Application。我們需要做的其實是以下幾個工作:
將我們自己Application類以及它的繼承類的所有代碼拷貝到自己的ApplicationLike繼承類中,例如SampleApplicationLike。你也可以直接將自己的Application改為繼承ApplicationLike;
Application的attachBaseContext方法實現要單獨移動到onBaseContextAttached中;
對ApplicationLike中,引用application的地方改成getApplication();
對其他引用Application或者它的靜態對象與方法的地方,改成引用ApplicationLike的靜態對象與方法;
更詳細的事例,大家可以參考下面的一些例子以及SampleApplicationLike的做法。
這是我的例子:也可以參考https://github.com/Tencent/tinker/wiki/Tinker-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%89%A9%E5%B1%95
@DefaultLifeCycle(
application = "com.kairu.rxjava.app.MyApplication",//這的Application是以前項目中的MyApplication
flags = ShareConstants.TINKER_ENABLE_ALL
)
public class MyApplicationLike extends DefaultApplicationLike {
private static Application mApplication;
public static String currentGirl = "http://ww2.sinaimg.cn/large/610dc034jw1f5k1k4azguj20u00u0421.jpg";
public MyApplicationLike(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();
//這裡把所有的Application換成getApplication() 原因看https://github.com/Tencent/tinker/wiki/Tinker-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%89%A9%E5%B1%95
mApplication = getApplication();
//配置是否顯示log
LogUtil.isDebug = true;
//配置時候顯示toast
ToastUtils.isShow = true;
//配置程序異常退出處理
}
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
TinkerInstaller.install(this);
//在初始化的時候調用加載補丁的方法,路徑是實際補丁放的位置
TinkerInstaller.onReceiveUpgradePatch(this.getApplication(), Environment.getExternalStorageDirectory().getAbsolutePath()+"/patch_signed_7zip.apk");
}
public static Context getContext() {
return mApplication;
}
public static Application getIntstance() {
return mApplication;
}
}
是不是簡單暴力就完了?當然配置就搞定了,沒有那麼復雜…..
最後我們都配置好了那怎麼得到補丁包呢?
也是一樣
步驟1:編譯運行原版apk
把生成的apk放在自己定義的路徑下
tinkerPatch {
...
//有問題的apk的地址,就是要修復BUG的那個apk,這是在電腦上位置
oldApk = "D://1//app-debug-old.apk"
...
}
步驟2:修改源碼 生成新版apk 補丁
這裡修改源碼指的是實際項目中修復BUG更改的代碼…
後續的步驟都一樣就搞定了…………
Tinker的局限
如果出現以下的情況,並且ignoreWarning為false,我們將中斷編譯。因為這些情況可能會導致編譯出來的patch包帶來風險:
1. minSdkVersion小於14,但是dexMode的值為”raw”;
2. 新編譯的安裝包出現新增的四大組件(Activity, BroadcastReceiver…);
3. 定義在dex.loader用於加載補丁的類不在main dex中;
4. 定義在dex.loader用於加載補丁的類出現修改;
5. resources.arsc改變,但沒有使用applyResourceMapping編譯。
還有就是需要結束當前進程才能進行修復....
本章節所有內容皆為原創,如需轉載,請注明出處。http://blog.csdn.net/manoel/article/details/38471825Android是一個
Android Http 客戶端編程之GET 說起Http編程,不盡然想起GET和POST兩種請求方式,本文以簡潔明了的的步驟和說明,將Android中常用
目錄概述StickHeaderItemDecoration是用於顯示固定頭部的item裝飾類,擴展來自系統的ItemDecoration.本文參考了一部分sticky-h
導語這裡展示的View估計項目中多半是用不到的,只是用來加深理解的。文章末尾會有全部的代碼,如果想研究可以復制過去直接運行,不需要額外的資源。先看效果:這裡指針是通過手指