Tinker使用
前言
寫在前面的話糠惫,在上家公司一直在主導(dǎo)組件框架的開發(fā)拉盾,所以對Android領(lǐng)域組件化,熱更新的發(fā)展都有所關(guān)注仍侥。當(dāng)時做組件化時要出,也調(diào)研了不少資料,那時候也正是騰訊在宣傳Tinker的時機(jī)农渊,并宣稱準(zhǔn)備全部開源患蹂,其實(shí)是一直有所期待的,原因也是因?yàn)槲⑿旁椅桑绻嫒珧v訊所說传于,微信的熱更新用的是Tinker,按照微信的體量和要求醉顽,Tinker應(yīng)該是神一般的存在沼溜,就如阿里的Atlas一樣(雖然說現(xiàn)在Atlas也已開源,但是按照阿里的尿性游添,估計內(nèi)部也已經(jīng)早已改朝換代了)系草,總之都是神一般的框架通熄,值得我們花大把的時間去研究和學(xué)習(xí)。
玩Dota的人應(yīng)該清楚找都,Tinker的來源于英雄Tinker唇辨,其大招就是刷新技能和道具,我想這也正是騰訊將其作為熱更新框架名字的用意吧能耻,無線刷新App赏枚,玩的就是飄逸。
歷史
在了解Tinker之前晓猛,我們先回顧一下熱更新的前世今生饿幅,個人覺得先從宏觀上去了解這些事還是很有必要的,最起碼裝逼很有必要鞍帝。熱更新主要分為兩個流派:Java和Native诫睬。
- Native代表有阿里的Dexposed,AndFix,據(jù)說騰訊內(nèi)部也有一個KKFIx帕涌,不太了解摄凡。
- Java主要代表有Qzone的超級補(bǔ)丁、大眾點(diǎn)評的nuwa蚓曼、百度金融的rocooFix, 餓了么的amigo以及美團(tuán)的robust亲澡。
我們先說Java,萬變不離其宗,都離不開Dex替換的原則,不管是拆分dex還是全量dex坚俗,說白了就是讓classloader先加載新的那段dex代碼從而達(dá)到改頭換面的目的贼急。至于Native,我只知道基本原理用的都是Native Hook的方式台囱。可以參考這篇文章
微信Android熱補(bǔ)丁實(shí)踐演進(jìn)之路。至于各大廠商的熱更新產(chǎn)品痹雅,可以看下圖:
Tinker | Qzone | AndFix | Robust | |
---|---|---|---|---|
類替換 | yes | yes | <font color="Red">no</font> | <font color="Red">no</font> |
So替換 | yes | <font color="Red">no</font> | <font color="Red">no</font> | <font color="Red">no</font> |
資源替換 | yes | yes | <font color="Red">no</font> | <font color="Red">no</font> |
全平臺支持 | yes | yes | yes | yes |
及時生效 | <font color="Red">no</font> | <font color="Red">no</font> | yes | yes |
性能損耗 | 較小 | 較大 | 較小 | 較小 |
補(bǔ)丁包大小 | 較小 | 較大 | 中等 | 中等 |
開發(fā)透明 | yes | yes | <font color="Red">no</font> | <font color="Red">no</font> |
復(fù)雜度 | 較低 | 較低 | 復(fù)雜 | 復(fù)雜 |
gradle支持 | yes | <font color="Red">no</font> | <font color="Red">no</font> | <font color="Red">no</font> |
Rom體積 | 較大 | 較小 | 較小 | 較小 |
成功率 | 較高 | 較高 | 一般 | 最高 |
綜上來說:
- AndFix是Native解決方案,在混合編譯出現(xiàn)后遇到最大的問題就是兼容性和穩(wěn)定性問題糊识,而且無法實(shí)現(xiàn)類更換绩社,本身是Native方案,這對開發(fā)者來說上手難度也更大
- Qzone由于插樁的緣故赂苗,犧牲了很大的虛擬機(jī)性能愉耙,而且為了解決后面由于Arrt內(nèi)存地址錯亂只能采用全量替換Dex,也導(dǎo)致補(bǔ)丁包急速增大拌滋。
- AndFix和Robust有一個很大的優(yōu)點(diǎn)就是能夠及時生效朴沿,但是Robust的缺點(diǎn)是無法做到類替換,而且無法新增變量和類鸠真,導(dǎo)致局限性很小悯仙,只能作為bug修復(fù)龄毡,無法做到功能發(fā)布。
說了這么多感覺貌似只有Tinker是最適合的锡垄,最牛逼的沦零,當(dāng)然不是,我們講沒有萬能的方案只有適合的方案货岭,試想如果產(chǎn)品要求我們的熱更新只需要作為bug修復(fù)的手段路操,功能發(fā)布用組件化方案,而且必須要及時生效千贯,那這時候Tinker就無法滿足要求了屯仗。所以任何一個方案都不是萬能的,我相信微信在搞Tinker的時候也是站在巨人的肩膀上搔谴。不過既然我們是要研究Tinker魁袜,還是先講講Tinker的優(yōu)點(diǎn),或者說官方發(fā)布的優(yōu)點(diǎn)敦第,至于具體原理是什么峰弹,后面的文章會一一說明。
- 穩(wěn)定芜果,這點(diǎn)毋庸置疑鞠呈,當(dāng)然我不是說其他廠商的開源框架不夠穩(wěn)定,微信的大體量和高日活決定這款產(chǎn)品要有足夠的穩(wěn)定性右钾,這點(diǎn)也保證了Tinker要有足夠的穩(wěn)定性與兼容性
- 開發(fā)社區(qū)活躍蚁吝,Tinker是在15年6月開始,到現(xiàn)在才2年時間舀射,至少現(xiàn)在GitHub上還算活躍窘茁,也希望騰訊能夠一直保持下去。
- patch包小脆烟,這個也算Tinker的一個亮點(diǎn)吧庙曙。自己實(shí)現(xiàn)了Diff算法,保證Patch包足夠小浩淘。
Tinker的已知問題:
- 不支持修改AndroidManifest.xml。這也就決定了Tinker不支持新增四大組件
- Android N上吴攒,補(bǔ)丁對應(yīng)用啟動時間有影響(暫時不知张抄,后面看到了會說明)
- 在某些三星 android-21手機(jī)上 還有兼容性問題
- 資源替換中,不支持修改remoteView,transition動畫洼怔,通知欄圖標(biāo)和應(yīng)用圖標(biāo)
希望Tinker的開源也會讓熱更新領(lǐng)域發(fā)展的越來越好署惯。
Tinker的集成
在還沒有閱讀Tinker源碼之前,我們先來集成一下Tinker镣隶,不管怎樣极谊,先用起來再說诡右。
Tinker Github地址:Tinker
Android Studio clone tinker源碼,如下所示
不得不說轻猖,騰訊這次真是業(yè)界良性帆吻,開源了所有東西,包括插件源碼咙边。
話不多說猜煮,首先建立一個Demo工程。
-
當(dāng)然是添加依賴了败许,在項目的build.gradle中添加如下依賴王带,即引用tinker的patch插件
buildscript { dependencies { classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.11') } }
-
APP 的 build.gradle中添加如下依賴
dependencies { //自己的依賴 ... compile('com.tencent.tinker:tinker-android-lib:1.7.11') ... } ... compile('com.tencent.tinker:tinker-android-lib:1.7.11')
sync project后會報錯,如下:
不慌市殷,往下看
-
添加Tinkid愕撰。這里解釋一下TinkId,運(yùn)行過程中,Tinker需要驗(yàn)證基準(zhǔn)apk包的tinkerId是否等于補(bǔ)丁包的tinkerId醋寝。這個是決定補(bǔ)丁包能運(yùn)行在哪些基準(zhǔn)包上面搞挣。說白了就是id匹配的作用,保持基準(zhǔn)包和pacth包的兼容甥桂。所以這里建議大家的方式柿究,可以看下github demo的APP gradle文件。網(wǎng)上很多人的做法是復(fù)制整個文件黄选,然后運(yùn)行蝇摸,貌似可行,實(shí)際上并未達(dá)到學(xué)習(xí)的效果办陷,我在改文件里標(biāo)注了所有的gradle的注釋貌夕,大家可以看下。
apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.3" defaultConfig { applicationId "com.netease.xu.tinkerdemo" minSdkVersion 10 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" //設(shè)置BuildConfig.java里message字段民镜,沒什么特殊的作用啡专,可有可無 buildConfigField "String", "MESSAGE", "\"I am the base apk\"" //在BuildConfig.java里設(shè)置TinkerID字段,實(shí)際上如果代碼里沒有調(diào)用這個制圈,也可以不設(shè)置 buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\"" //同上 buildConfigField "String", "PLATFORM", "\"all\"" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' //tinker的核心庫 compile('com.tencent.tinker:tinker-android-lib:1.7.11') } //定義基準(zhǔn)apk構(gòu)建的位置 def bakPath = file("${buildDir}/bakApk/") //額外屬性们童,實(shí)際上這些也是可以不用寫的,騰訊真是良心鲸鹦,可以支持Gradle腳本自定義慧库,這些值實(shí)際上都可以在gradle.properites中自定義 ext { //是否支持tinker構(gòu)建 tinkerEnabled = true //如果需要構(gòu)建patch包,這里需要天上同一個其基準(zhǔn)包地址 tinkerOldApkPath = "${bakPath}/app-debug-1018-17-32-47.apk" //如果需要構(gòu)建patch包馋嗜,這里需要天上同一個其基準(zhǔn)包的混淆文件 tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt" //如果差分包有修改資源文件齐板,則必須需要輸入以前基準(zhǔn)包的R文件,主要是為了固定id來用的。 tinkerApplyResourcePath = "${bakPath}/app-debug-1018-17-32-47-R.txt" //給打渠道包配置的甘磨,這里是學(xué)習(xí)階段橡羞,暫時注釋 // tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47" } if (ext.tinkerEnabled) { //引用patch插件 apply plugin: 'com.tencent.tinker.patch' //tinkerPath任務(wù),補(bǔ)丁關(guān)鍵任務(wù)济舆,源碼也有卿泽,后面我會有詳細(xì)撰述,這里只知道怎么用即可 //直接使用基準(zhǔn)apk包與新編譯出來的apk包做差異吗冤,得到最終的補(bǔ)丁包 tinkerPatch { oldApk = getTinkerOldApkPath() /** * 是否忽略警告 * 值為false將中斷編譯又厉。因?yàn)檫@些情況可能會導(dǎo)致編譯出來的patch包帶來風(fēng)險: * 1. minSdkVersion小于14,但是dexMode的值為"raw"; dexmode下面會有介紹 * 2. 新編譯的安裝包出現(xiàn)新增的四大組件(Activity, BroadcastReceiver...)椎瘟; * 3. 定義在dex.loader用于加載補(bǔ)丁的類不在main dex中; * 4. 定義在dex.loader用于加載補(bǔ)丁的類出現(xiàn)修改覆致; * 5. resources.arsc改變,但沒有使用applyResourceMapping編譯肺蔚。 */ ignoreWarning = false /** * 是否簽名煌妈,保證基準(zhǔn)包和補(bǔ)丁包的簽名一致,代碼里有判斷邏輯 */ useSign = true /** * 編譯相關(guān)配置項 */ buildConfig { //在編譯新的apk時候宣羊,通過保持舊apk的proguard混淆方式璧诵,從而減少補(bǔ)丁包的大小。這個只是推薦設(shè)置仇冯,不設(shè)置applyMapping也不會影響任何的assemble編譯 applyMapping = getTinkerApplyMappingPath() //在編譯新的apk時候之宿,通過舊apk的R.txt文件保持ResId的分配,這樣不僅可以減少補(bǔ)丁包的大小苛坚,同時也避免由于ResId改變導(dǎo)致remote view異常比被。 applyResourceMapping = getTinkerApplyResourcePath() //tinkerID tinkerId = getTinkerIdValue() //如果有多個dex,編譯補(bǔ)丁時可能會由于類的移動導(dǎo)致變更增多。若打開keepDexApply模式泼舱,補(bǔ)丁包將根據(jù)基準(zhǔn)包的類分布來編譯等缀。 keepDexApply = false } dex { //只能是'raw'或者'jar'。 //對于'raw'模式娇昙,將會保持輸入dex的格式尺迂。 //對于'jar'模式,將會把輸入dex重新壓縮封裝到j(luò)ar冒掌。如果你的minSdkVersion小于14噪裕,你必須選擇‘jar’模式,而且它更省存儲空間股毫,但是驗(yàn)證md5時比'raw'模式耗時州疾。默認(rèn)我們并不會去校驗(yàn)md5,一般情況下選擇jar模式即可。 dexMode = "jar" //需要處理dex路徑皇拣,支持*、?通配符,必須使用'/'分割氧急。路徑是相對安裝包的颗胡,例如assets/... pattern = ["classes*.dex", "assets/secondary-dex-?.jar"] /** * 這一項非常重要,它定義了哪些類在加載補(bǔ)丁包的時候會用到吩坝。這些類是通過Tinker無法修改的類毒姨,也是一定要放在main dex的類。 這里需要定義的類有: 1. 你自己定義的Application類钉寝; 2. Tinker庫中用于加載補(bǔ)丁包的部分類弧呐,即com.tencent.tinker.loader.*; 3. 如果你自定義了TinkerLoader嵌纲,需要將它以及它引用的所有類也加入loader中俘枫; 4. 其他一些你不希望被更改的類,例如Sample中的BaseBuildInfo類逮走。這里需要注意的是鸠蚪,這些類的直接引用類也需要加入到loader中∈Γ或者你需要將這個類變成非preverify茅信。 5. 使用1.7.6版本之后版本,參數(shù)1墓臭、2會自動填寫蘸鲸。 */ loader = [ //use sample, let BaseBuildInfo unchangeable with tinker // "tinker.sample.android.app.BaseBuildInfo" ] } lib { /** * 需要處理lib路徑,支持*窿锉、?通配符酌摇,必須使用'/'分割。與dex.pattern一致, 路徑是相對安裝包的榆综,例如assets/... */ pattern = ["lib/armeabi/*.so"] } res { /** *需要處理res路徑妙痹,支持*、?通配符鼻疮,必須使用'/'分割怯伊。與dex.pattern一致, 路徑是相對安裝包的,例如assets/...判沟,務(wù)必注意的是耿芹,只有滿足pattern的資源才會放到合成后的資源包。 */ pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] /** * 支持*挪哄、?通配符吧秕,必須使用'/'分割。若滿足ignoreChange的pattern迹炼,在編譯時會忽略該文件的新增砸彬、刪除與修改颠毙。 最極端的情況,ignoreChange與上面的pattern一致砂碉,即會完全忽略所有資源的修改蛀蜜。 */ ignoreChange = ["assets/sample_meta.txt"] /** * 對于修改的資源,如果大于largeModSize增蹭,我們將使用bsdiff算法滴某。這可以降低補(bǔ)丁包的大小,但是會增加合成時的復(fù)雜度滋迈。默認(rèn)大小為100kb */ largeModSize = 100 } packageConfig { /** * configField("key", "value"), 默認(rèn)我們自動從基準(zhǔn)安裝包與新安裝包的Manifest中讀取tinkerId,并自動寫入configField霎奢。在這里,你可以定義其他的信息饼灿,在運(yùn)行時可以通過TinkerLoadResult.getPackageConfigByName得到相應(yīng)的數(shù)值幕侠。但是建議直接通過修改代碼來實(shí)現(xiàn),例如BuildConfig赔退。 */ configField("patchMessage", "tinker is sample to use") /** * just a sample case, you can use such as sdkVersion, brand, channel... * you can parse it in the SamplePatchListener. * Then you can use patch conditional! */ configField("platform", "all") /** * patch version via packageConfig */ configField("patchVersion", "1.0") } /** *7zip路徑配置項橙依,執(zhí)行前提是useSign為true */ sevenZip { /** * 例如"com.tencent.mm:SevenZip:1.1.10",將自動根據(jù)機(jī)器屬性獲得對應(yīng)的7za運(yùn)行文件硕旗,推薦使用 */ zipArtifact = "com.tencent.mm:SevenZip:1.1.10" /** * 系統(tǒng)中的7za路徑窗骑,例如"/usr/local/bin/7za"。path設(shè)置會覆蓋zipArtifact漆枚,若都不設(shè)置创译,將直接使用7za去嘗試。 */ // path = "/usr/local/bin/7za" } } List<String> flavors = new ArrayList<>(); project.android.productFlavors.each {flavor -> flavors.add(flavor.name) } boolean hasFlavors = flavors.size() > 0 /** * bak apk and mapping * 渠道包相關(guān)配置墙基。 */ android.applicationVariants.all { variant -> /** * task type, you want to bak */ def taskName = variant.name //def date = new Date().format("MMdd-HH-mm-ss") def date = new Date().format("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") } } } } } } //渠道構(gòu)建相關(guān)软族,暫時不考慮 // 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" // } // // } // } // } // } } def getTinkerApplyResourcePath() { return ext.tinkerApplyResourcePath } def getTinkerApplyMappingPath() { return ext.tinkerApplyMappingPath } def isTinkerEnabled() { return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled } def getTinkerOldApkPath() { return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath } def getTinkerIdValue() { return hasProperty("TINKER_ID") ? TINKER_ID : 1 }
4.配置完成后,在MainActivity中加入以下代碼:
MainActivity.java
activity_main.xml
這些都比較簡單的demo残制,做好這些以后立砸,在命令行里敲入:
./gradlew assembleDebug
5.由于我沒有開混淆,而且前面gradle里也定義了輸出文件的路徑初茶,所以在改路徑下生成了兩個文件:
基準(zhǔn)包和一個R.txt文件颗祝,R是APK用到的資源索引
大家可以看下R.txt文件,實(shí)際上里面也包括所有的系統(tǒng)資源恼布,這里就不做撰述
細(xì)心的同學(xué)會發(fā)現(xiàn)螺戳,manifest.xml會多了一行:
這個1實(shí)際上也是我們自己配置的tinkerid。這邊我直接寫死了折汞,如果是線上項目倔幼,很多是用git的提交號來。
BuildConfig.java中也多了在gradle里配置的字段
6.這些做完夠了么爽待,當(dāng)然不夠损同,可以構(gòu)建出APK出來翩腐,但是APK里不包含熱更新相關(guān)的處理邏輯,最起碼有l(wèi)oad patch包揖庄,以及冷啟動相關(guān)代碼邏輯栗菜。
在做這些之前,我們先回顧一下上面的過程蹄梢,顯然Tinker干預(yù)了打包的過程,實(shí)際上補(bǔ)丁包的生成也是有單獨(dú)的命令的富俄。從這一點(diǎn)來看禁炒,tinker的侵入性還是比較強(qiáng)的。
我們接著來添加代碼已滿足需求:
-
load 補(bǔ)丁包: 說白了就是給一個補(bǔ)丁包的地址霍比,然后Tinker去load幕袱。這個地址應(yīng)該是sd卡和files都可以,sd卡最后也會拷貝到files下去加載悠瞬。所以我在xml里加一個一個button顯示的load補(bǔ)丁包们豌。至于補(bǔ)丁包的地址寫死吧。 /sdcard/patch.apk 代碼如下:
當(dāng)運(yùn)行時會報如下錯誤:
顯然是由于Tinker沒有安裝導(dǎo)致的浅妆,一般框架也是這個尿性望迎,所以這時候要對Tinker進(jìn)行初始化,說到這里凌外,發(fā)現(xiàn)Tinker的侵入性又+1辩尊。不管了,既然是集成康辑,也只能按照它的協(xié)議往下做了摄欲。
一般人集成也許第一步就初始化了,我的角度是站在普通人的思路去做這個事疮薇,發(fā)現(xiàn)問題再去解決問題胸墙,這樣才能去分析這個框架,更大可能的去理解創(chuàng)造者的思想按咒,當(dāng)然網(wǎng)上很多人直接復(fù)制粘貼迟隅,這個就不推薦了。
- Tinker初始化胖齐。在官方Demo的ReadMe中找到:
也只能是這個尿性了玻淑,這不是重點(diǎn)。重點(diǎn)是這個SampleApplicationLike呀伙。微信為什么高出這個呢补履,他應(yīng)該是一個代理,這個里面做Tinker熱跟新相關(guān)的處理剿另,后面我們分析源碼的時候會講到箫锤。這里不做撰述贬蛙,照葫蘆畫瓢即可。實(shí)際上內(nèi)部也是有一個Default的實(shí)現(xiàn)的谚攒,這里為了學(xué)習(xí)阳准,就自定義了一個。按部就班的來馏臭,最后運(yùn)行的時候發(fā)現(xiàn)報一下錯誤
竟然說我的Application類重復(fù)了野蝇,詭異。繼續(xù)找原因:
原因應(yīng)該是SampleApplicationLike中的注解自動生成了一個Application導(dǎo)致和代碼里重寫的注解@DefaultCycle導(dǎo)致的括儒。所以我嘗試取消這個注解绕沈。編譯可以通過,但是運(yùn)行的時候出現(xiàn)以下崩潰
哎帮寻,難道Tinker不支持自定義Application乍狐。暫時不管了,刪除自己寫的Application固逗,添加注解浅蚪。就可以編譯安裝了。
<font color="red">這個設(shè)計烫罩,有點(diǎn)反人類惜傲。侵入性嚴(yán)重。難受...</font>
7.做完這些事以后嗡髓,構(gòu)建出一個基準(zhǔn)包(./gradlew assembleDebug)操漠,并且安裝。由于需要用到sd卡權(quán)限饿这,所以
manefest里要加上sd卡讀寫權(quán)限浊伙,省的重來一次。
運(yùn)行后长捧,在 app/build/bakApk/ 目錄下會生成一個Apk以及相應(yīng)的R.txt文件嚣鄙,會帶有一個數(shù)字,如下:
通過adb 安裝這個基準(zhǔn)包串结。然后修改一行代碼哑子,這個隨便自己。只要能看出來當(dāng)前修改和上次基準(zhǔn)包有區(qū)別就可以肌割。我這邊就是修改了一下toast彈出的文案卧蜓。
8.通過上面的7構(gòu)建出來基準(zhǔn)包后,修改app/build.gralde腳本把敞。
這個路徑很好理解弥奸。至于那個Mappingpath。由于我用的是debug包奋早,直接忽略盛霎,刪除都可以赠橙。運(yùn)行 ./gradlew tinkerPatchDebug 構(gòu)建補(bǔ)丁包竟然需要一分鐘以上。吐槽一下愤炸。成功以后期揪,在 app/build/outputs/tikerPath/目錄下會生成補(bǔ)丁包,有帶簽名和不帶簽名的规个。
9.通過adb push導(dǎo)入補(bǔ)丁包到 sd卡凤薛。打開應(yīng)用在關(guān)閉屏幕,點(diǎn)擊按鈕彈出toast诞仓。就是新的t文案了枉侧。
結(jié)束
上面完成了Tinker的集成。雖然說工作量不大狂芋,但是從中也遇到了一些問題,有些已經(jīng)找到原因憨栽,有些缺不知為何帜矾。后期會在去研究。