上一篇,咱們學習了《微信Tinker項目中的maven-publish封裝》,瞭解到了在一個成熟項目中,maven相關gradle的通用封裝,進而鞏固前面學習的gradle相關理論知識java
接下來,咱們將對Tinker項目中的tinker-patch-gradle-plugin模塊進行解析,進一步感覺Gradle在億級應用中散發的魅力android
PS1:本章主要是跟蹤《tinker-patch-gradle-plugin模塊》實現,來鞏固gradle相關知識,具體熱修相關安卓知識的話不會展開git
PS2:因爲tinker的官方工程比較大,對於鞏固gradle知識干擾比較大,因此本章的代碼工程是閹割了官方的代碼展開,更加聚焦,同時實現博客和源碼配套的模式github
tinker-patch-gradle-plugin模塊,是開發者使用Thinker入口;微信
若是咱們app集成thinker,其實就是對這個模塊的使用,由於thinker的實現markdown
都以插件的方式被封裝到了這個模塊,具體官方代碼位置戳這裏>>>app
上面說到,咱們會對官方工程進行裁剪,裁剪後對應的模塊位置戳這裏>>>less
此圖爲咱們的app模塊引入thinker的步驟maven
1)模塊所在位置,其實就是咱們app模塊的gradle文件ide
2)插件引入,經過classpath關鍵字引入封裝好的thinker插件,其中插件的maven發佈咱們發佈到了本地,因此用的時候咱們maven指向了本地的 ‘../repo’
3)插件的使用,經過apply引入,而後tinkerPatch,buildConfig都是插件的自定義拓展,具體實如今下面步驟講解
1)插件實現工程目錄,能夠看出這是一個gradle插件的標準目錄,具體詮釋見往期教程
2)插件實現工程gradle文件,這裏除了有自定義插件的依賴外,還用到了上一章講解的maven-publish封裝
上面有提到tinkerPatch,buildConfig關鍵字爲自定義拓展,這兩個關鍵字只是thinker
中的拓展之一,咱們來看下thinker的自定義拓展全貌:
tinkerPatch {
/** * necessary,default 'null' * the old apk path, use to diff with the new apk to build * add apk from the build/bakApk */
oldApk = getOldApkPath()
/** * optional,default 'false' * there are some cases we may get some warnings * if ignoreWarning is true, we would just assert the patch process * case 1: minSdkVersion is below 14, but you are using dexMode with raw. * it must be crash when load. * case 2: newly added Android Component in AndroidManifest.xml, * it must be crash when load. * case 3: loader classes in dex.loader{} are not keep in the main dex, * it must be let tinker not work. * case 4: loader classes in dex.loader{} changes, * loader classes is ues to load patch dex. it is useless to change them. * it won't crash, but these changes can't effect. you may ignore it * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build */
ignoreWarning = false
/** * optional,default 'true' * whether sign the patch file * if not, you must do yourself. otherwise it can't check success during the patch loading * we will use the sign config with your build type */
useSign = true
/** * optional,default 'true' * whether use tinker to build */
tinkerEnable = buildWithTinker()
/** * Warning, applyMapping will affect the normal android build! */
buildConfig {
/** * optional,default 'null' * if we use tinkerPatch to build the patch apk, you'd better to apply the old * apk mapping file if minifyEnabled is enable! * Warning: * you must be careful that it will affect the normal assemble build! */
applyMapping = getApplyMappingPath()
/** * optional,default 'null' * It is nice to keep the resource id from R.txt file to reduce java changes */
applyResourceMapping = getApplyResourceMappingPath()
/** * necessary,default 'null' * because we don't want to check the base apk with md5 in the runtime(it is slow) * tinkerId is use to identify the unique base apk when the patch is tried to apply. * we can use git rev, svn rev or simply versionCode. * we will gen the tinkerId in your manifest automatic */
tinkerId = getTinkerIdValue()
/** * if keepDexApply is true, class in which dex refer to the old apk. * open this can reduce the dex diff file size. */
keepDexApply = false
/** * optional, default 'false' * Whether tinker should treat the base apk as the one being protected by app * protection tools. * If this attribute is true, the generated patch package will contain a * dex including all changed classes instead of any dexdiff patch-info files. */
isProtectedApp = false
/** * optional, default 'false' * Whether tinker should support component hotplug (add new component dynamically). * If this attribute is true, the component added in new apk will be available after * patch is successfully loaded. Otherwise an error would be announced when generating patch * on compile-time. * * <b>Notice that currently this feature is incubating and only support NON-EXPORTED Activity</b> */
supportHotplugComponent = false
}
dex {
/** * optional,default 'jar' * only can be 'raw' or 'jar'. for raw, we would keep its original format * for jar, we would repack dexes with zip format. * if you want to support below 14, you must use jar * or you want to save rom or check quicker, you can use raw mode also */
dexMode = "jar"
/** * necessary,default '[]' * what dexes in apk are expected to deal with tinkerPatch * it support * or ? pattern. */
pattern = ["classes*.dex",
"assets/secondary-dex-?.jar"]
/** * necessary,default '[]' * Warning, it is very very important, loader classes can't change with patch. * thus, they will be removed from patch dexes. * you must put the following class into main dex. * Simply, you should add your own application {@code tinker.sample.android.SampleApplication} * own tinkerLoader, and the classes you use in them * */
loader = [
//use sample, let BaseBuildInfo unchangeable with tinker
"tinker.sample.android.app.BaseBuildInfo"
]
}
lib {
/** * optional,default '[]' * what library in apk are expected to deal with tinkerPatch * it support * or ? pattern. * for library in assets, we would just recover them in the patch directory * you can get them in TinkerLoadResult with Tinker */
pattern = ["lib/*/*.so"]
}
res {
/** * optional,default '[]' * what resource in apk are expected to deal with tinkerPatch * it support * or ? pattern. * you must include all your resources in apk here, * otherwise, they won't repack in the new apk resources. */
pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
/** * optional,default '[]' * the resource file exclude patterns, ignore add, delete or modify resource change * it support * or ? pattern. * Warning, we can only use for files no relative with resources.arsc */
ignoreChange = ["assets/sample_meta.txt"]
/** * default 100kb * for modify resource, if it is larger than 'largeModSize' * we would like to use bsdiff algorithm to reduce patch file size */
largeModSize = 100
}
packageConfig {
/** * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE' * package meta file gen. path is assets/package_meta.txt in patch file * you can use securityCheck.getPackageProperties() in your ownPackageCheck method * or TinkerLoadResult.getPackageConfigByName * we will get the TINKER_ID from the old apk manifest for you automatic, * other config files (such as patchMessage below)is not necessary */
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")
}
//or you can add config filed outside, or get meta value from old apk
//project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))
//project.tinkerPatch.packageConfig.configField("test2", "sample")
/** * if you don't use zipArtifact or path, we just use 7za to try */
sevenZip {
/** * optional,default '7za' * the 7zip artifact path, it will use the right 7za with your platform */
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
/** * optional,default '7za' * you can specify the 7za path yourself, it will overwrite the zipArtifact value */
// path = "/usr/local/bin/7za"
}
}
複製代碼
精簡後大體結構以下:
tinkerPatch {
oldApk = getOldApkPath()
buildConfig {
supportHotplugComponent = false
}
dex {
pattern = ["classes*.dex",
"assets/secondary-dex-?.jar"]
}
lib {
pattern = ["lib/*/*.so"]
}
res {
largeModSize = 100
}
packageConfig {
configField("patchVersion", "1.0")
}
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
}
}
複製代碼
這裏具體的配置是什麼意思,先不用關注,由於配置的意思涉及到thinker自己業務和一些安卓熱修相關知識點,咱們主要關注下若是咱們要實現這樣的結構拓展,應該怎麼作?下面咱們來看看thinker是怎麼作的:
1)插件入口,gradle插件的代碼類入口
2)和 3),拓展調用和定義,在定義中能夠看到經過‘project.extensions.create’ 來建立自定義拓展,層級嵌套拓展經過追加方式,如:先自定義tinkerPatch,而後在tinkerPatch中嵌套buildConfig
其中,細心的朋友會看到,爲何有些create是2個參數,有些是3個參數
根據官方定義能夠看出,前面兩個是定義拓展的key和value,而後後面的可變參數是 要傳給value拓展的參數,舉個例子:
project.tinkerPatch.extensions.create('buildConfig', TinkerBuildConfigExtension, project)
複製代碼
key爲‘buildConfig’,value爲‘TinkerBuildConfigExtension’
而後project則是傳遞參數給到了‘TinkerBuildConfigExtension’,具體見TinkerBuildConfigExtension的定義以下:
其餘拓展以此類推,這裏不一一展開
1)preDexLibraries 設置爲false
默認狀況下,preDexLibraries是爲true的,做用主要是用來確認是否對Lib作preDexing操做,操做了的話帶來的好處提升增量構建的速度;
這裏設置爲false,猜想是thinker涉及了多dex,爲避免和庫工程衝突
另外preDexLibraries是dexOptions屬性之一,dexoptions是一個gradle對象,這個對象用來設置從java代碼向.dex文件轉化的過程當中的一些配置選項;
更多dexOptions屬性能夠戳這裏>>>
2)jumboMode 設置爲true
jumboMode設置爲true,意識是忽略方法數限制的檢查
這樣作的缺點是apk沒法再低版本的設備上面安裝,會出現錯誤:INSTALL_FAILED_DEXOPT
具體細節戳這裏>>>
3)關閉 ENABLE_DEX_ARCHIVE
void disableArchiveDex(Project project) {
println 'disableArchiveDex -->'
try {
def booleanOptClazz = Class.forName('com.android.build.gradle.options.BooleanOption')
def enableDexArchiveField = booleanOptClazz.getDeclaredField('ENABLE_DEX_ARCHIVE')
enableDexArchiveField.setAccessible(true)
def enableDexArchiveEnumObj = enableDexArchiveField.get(null)
def defValField = enableDexArchiveEnumObj.getClass().getDeclaredField('defaultValue')
defValField.setAccessible(true)
defValField.set(enableDexArchiveEnumObj, false)
} catch (Throwable thr) {
// To some extends, class not found means we are in lower version of android gradle
// plugin, so just ignore that exception.
if (!(thr instanceof ClassNotFoundException)) {
project.logger.error("reflectDexArchiveFlag error: ${thr.getMessage()}.")
}
}
}
複製代碼
ENABLE_DEX_ARCHIVE 這個功能主要是減小dex的大小
這裏關閉主要避免破壞multidex的maindex規則,進而實現多dex的場景
4)keepRuntimeAnnotatedClasses 設置爲 false
keepRuntimeAnnotatedClasses 主要做用是帶有運行時註解的類,保留在主dex中
thinker關閉,主要下降主dex大小,兼容5.0如下的狀況
該部分功能實現:使用導出的符號表進行資源id的固定
爲何要進行資源ID的固定?具體戳這裏>>,背景細節不在這展開
其中,這裏實現邏輯爲:
1)判斷當前《Android Gradle Plugin》是否啓動aapt2,若是沒有啓動跳過,若是啓動了則進行aapt2的資源固定適配
2)aapt2的資源固定適配操做,經過指定穩定的資源id映射文件,同時結合「--stable-ids」命令進行固定
到這裏gradle的學習接下來就要告一斷落了,前面主要是學習了系列基礎理論,而後結合了兩篇實戰進行鞏固,剩下的就要靠本身項目中不斷的使用,才能進入到下一步高級的境界;
哈哈,該篇就寫到這裏(一塊兒體系化學習,一塊兒成長)