Tinker學(xué)習(xí)計劃(1)-Tinker的集成

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工程。

接入指南

  1. 當(dāng)然是添加依賴了败许,在項目的build.gradle中添加如下依賴王带,即引用tinker的patch插件

     buildscript {
         dependencies {
             classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.11')
         }
     }
    
  2. 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后會報錯,如下:

    不慌市殷,往下看

  3. 添加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)找到原因憨栽,有些缺不知為何帜矾。后期會在去研究。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屑柔,一起剝皮案震驚了整個濱河市屡萤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掸宛,老刑警劉巖死陆,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異唧瘾,居然都是意外死亡措译,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門饰序,熙熙樓的掌柜王于貴愁眉苦臉地迎上來领虹,“玉大人,你說我怎么就攤上這事求豫∷ィ” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵蝠嘉,是天一觀的道長最疆。 經(jīng)常有香客問我,道長蚤告,這世上最難降的妖魔是什么努酸? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮罩缴,結(jié)果婚禮上蚊逢,老公的妹妹穿的比我還像新娘层扶。我一直安慰自己,他們只是感情好烙荷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布镜会。 她就那樣靜靜地躺著,像睡著了一般终抽。 火紅的嫁衣襯著肌膚如雪戳表。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天昼伴,我揣著相機(jī)與錄音匾旭,去河邊找鬼。 笑死圃郊,一個胖子當(dāng)著我的面吹牛价涝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播持舆,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼色瘩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逸寓?” 一聲冷哼從身側(cè)響起居兆,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎竹伸,沒想到半個月后泥栖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勋篓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年吧享,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片生巡。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡耙蔑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出孤荣,到底是詐尸還是另有隱情甸陌,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布盐股,位于F島的核電站钱豁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疯汁。R本人自食惡果不足惜牲尺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谤碳,春花似錦溃卡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搓茬,卻和暖如春犹赖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卷仑。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工峻村, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锡凝。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓粘昨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親窜锯。 傳聞我的和親對象是個殘疾皇子雾棺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容