關于作者:
李濤,騰訊Android工程師,14年加入騰訊SNG增值產品部,期間主要負責手Q動漫、企鵝電競等項目的功能開發(fā)和技術優(yōu)化。業(yè)務時間喜歡折騰新技術,寫一些技術文章,個人技術博客:http://www.ltlovezh.com?。
ApkChannelPackage是一種快速多渠道打包工具,同時支持基于V1和V2簽名進行渠道打包。插件本身會自動檢測Apk使用的簽名方法,并選擇合適的多渠道打包方式,對使用者來說完全透明。
Github地址:
概述
眾所周知,因為國內Android應用分發(fā)市場的現(xiàn)狀,我們在發(fā)布APP時,一般需要生成多個渠道包,上傳到不同的應用市場。這些渠道包需要包含不同的渠道信息,在APP和后臺交互或者數(shù)據(jù)上報時,會帶上各自的渠道信息。這樣,我們就能統(tǒng)計到每個分發(fā)市場的下載數(shù)、用戶數(shù)等關鍵數(shù)據(jù)。
普通的多渠道打包方案
既然我們需要進行多渠道打包,那我們就看下最常見的多渠道打包方案。
Android Gradle Plugin
Gradle Plugin本身提供了多渠道的打包策略:
首先,在AndroidManifest.xml中添加渠道信息占位符:
然后,通過Gradle Plugin提供的productFlavors標簽,添加渠道信息:
productFlavors{? ? "YingYongBao"{? ? ? ? manifestPlaceholders = [InstallChannel : "YingYongBao"]? ? }? ? "360"{? ? ? ? manifestPlaceholders = [InstallChannel : "360"]? ? } }
這樣,Gradle編譯生成多渠道包時,會用不同的渠道信息替換AndroidManifest.xml中的占位符。我們在代碼中,也就可以直接讀取AndroidManifest.xml中的渠道信息了。
但是,這種方式存在一些缺點:
每生成一個渠道包,都要重新執(zhí)行一遍構建流程,效率太低,只適用于渠道較少的場景。
Gradle會為每個渠道包生成一個不同的BuildConfig.java類,記錄渠道信息,導致每個渠道包的DEX的CRC值都不同。一般情況下,這是沒有影響的。但是如果你使用了微信的Tinker熱補丁方案,那么就需要為不同的渠道包打不同的補丁,這完全是不可以接受的。(因為Tinker是通過對比基礎包APK和新包APK生成差分補丁,然后再把補丁和基礎包APK一起合成新包APK。這就要求用于生成差分補丁的基礎包DEX和用于合成新包的基礎包DEX是完全一致的,即:每一個基礎渠道包的DEX文件是完全一致的,不然就會合成失?。?/p>
ApkTool
ApkTool是一個逆向分析工具,可以把APK解開,添加代碼后,重新打包成APK。因此,基于ApkTool的多渠道打包方案分為以下幾步:
復制一份新的APK
通過ApkTool工具,解壓APK(apktool d origin.apk)
刪除已有簽名信息
添加渠道信息(可以在APK的任何文件添加渠道信息)
通過ApkTool工具,重新打包生成新APK(apktool b newApkDir)
重新簽名
經(jīng)過測試,這種方案完全是可行的。
優(yōu)點:
不需要重新構建新渠道包,僅需要復制修改就可以了。并且因為是重新簽名,所以同時支持V1和V2簽名。
缺點:
ApkTool工具不穩(wěn)定,曾經(jīng)遇到過升級Gradle Plugin版本后,低版本ApkTool解壓APK失敗的情況。
生成新渠道包時,需要重新解包、打包和簽名,而這幾步操作又是相對比較耗時的。經(jīng)過測試:生成企鵝電競10個渠道包需要16分鐘左右,雖然比Gradle Plugin方案減少很多耗時。但是若需要同時生成上百個渠道包,則需要幾個小時,顯然不適合渠道非常多的業(yè)務場景。
那有沒有一種方案,可以在添加渠道信息后,不需要重新簽名那?
首先我們要了解一下APK的簽名和校驗機制。
數(shù)據(jù)摘要、數(shù)字簽名和數(shù)字證書
在進一步學習V1和V2簽名之前,我們有必要學習一下簽名相關的基礎知識。
數(shù)據(jù)摘要
數(shù)據(jù)摘要算法是一種能產生特定輸出格式的算法,其原理是根據(jù)一定的運算規(guī)則對原始數(shù)據(jù)進行某種形式的信息提取,被提取出的信息就是原始數(shù)據(jù)的消息摘要,也稱為數(shù)據(jù)指紋。
一般情況下,數(shù)據(jù)摘要算法具有以下特點:
無論輸入數(shù)據(jù)有多大(長),計算出來的數(shù)據(jù)摘要的長度總是固定的。例如:MD5算法計算出的數(shù)據(jù)摘要有128Bit。
一般情況下(不考慮碰撞的情況下),只要原始數(shù)據(jù)不同,那么其對應的數(shù)據(jù)摘要就不會相同。同時,只要原始數(shù)據(jù)有任何改動,那么其數(shù)據(jù)摘要也會完全不同。即:相同的原始數(shù)據(jù)必有相同的數(shù)據(jù)摘要,不同的原始數(shù)據(jù),其數(shù)據(jù)摘要也必然不同。
不可逆性,即只能正向提取原始數(shù)據(jù)的數(shù)據(jù)摘要,而無法從數(shù)據(jù)摘要中恢復出原始數(shù)據(jù)。
著名的摘要算法有RSA公司的MD5算法和SHA系列算法。
數(shù)字簽名和數(shù)字證書
數(shù)字簽名和數(shù)字證書是成對出現(xiàn)的,兩者不可分離(數(shù)字簽名主要用來校驗數(shù)據(jù)的完整性,數(shù)字證書主要用來確保公鑰的安全發(fā)放)。
要明白數(shù)字簽名的概念,必須要了解數(shù)據(jù)的加密、傳輸和校驗流程。一般情況下,要實現(xiàn)數(shù)據(jù)的可靠通信,需要解決以下兩個問題:
確定數(shù)據(jù)的來源是其真正的發(fā)送者。
確保數(shù)據(jù)在傳輸過程中,沒有被篡改,或者若被篡改了,可以及時發(fā)現(xiàn)。
而數(shù)字簽名,就是為了解決這兩個問題而誕生的。
首先,數(shù)據(jù)的發(fā)送者需要先申請一對公私鑰對,并將公鑰交給數(shù)據(jù)接收者。
然后,若數(shù)據(jù)發(fā)送者需要發(fā)送數(shù)據(jù)給接收者,則首先要根據(jù)原始數(shù)據(jù),生成一份數(shù)字簽名,然后把原始數(shù)據(jù)和數(shù)字簽名一起發(fā)送給接收者。
數(shù)字簽名由以下兩步計算得來:
計算發(fā)送數(shù)據(jù)的數(shù)據(jù)摘要
用私鑰對提取的數(shù)據(jù)摘要進行加密
這樣,數(shù)據(jù)接收者拿到的消息就包含了兩塊內容:
原始數(shù)據(jù)內容
附加的數(shù)字簽名
接下來,接收者就會通過以下幾步,校驗數(shù)據(jù)的真實性:
用相同的摘要算法計算出原始數(shù)據(jù)的數(shù)據(jù)摘要。
用預先得到的公鑰解密數(shù)字簽名。
對比簽名得到的數(shù)據(jù)是否一致,如果一致,則說明數(shù)據(jù)沒有被篡改,否則數(shù)據(jù)就是臟數(shù)據(jù)了。
因為私鑰只有發(fā)送者才有,所以其他人無法偽造數(shù)字簽名。這樣通過數(shù)字簽名就確保了數(shù)據(jù)的可靠傳輸。
綜上所述,數(shù)字簽名就是只有發(fā)送者才能產生的別人無法偽造的一段數(shù)字串,這段數(shù)字串同時也是對發(fā)送者發(fā)送數(shù)據(jù)真實性的一個有效證明。
想法雖好,但是上面的整個流程,有一個前提,就是數(shù)據(jù)接收者能夠正確拿到發(fā)送者的公鑰。如果接收者拿到的公鑰被篡改了,那么壞人就會被當成好人,而真正的數(shù)據(jù)發(fā)送者發(fā)送的數(shù)據(jù)則會被視作臟數(shù)據(jù)。那怎么才能保證公鑰的安全性那?這就要靠數(shù)字證書來解決了。
數(shù)字證書是由有公信力的證書中心(CA)頒發(fā)給申請者的證書,主要包含了:證書的發(fā)布機構、證書的有效期、申請者的公鑰、申請者信息、數(shù)字簽名使用的算法,以及證書內容的數(shù)字簽名。
可見,數(shù)字證書也用到了數(shù)字簽名技術。只不過簽名的內容是數(shù)據(jù)發(fā)送方的公鑰,以及一些其它證書信息。
這樣數(shù)據(jù)發(fā)送者發(fā)送的消息就包含了三部分內容:
原始數(shù)據(jù)內容
附加的數(shù)字簽名
申請的數(shù)字證書。
接收者拿到數(shù)據(jù)后,首先會根據(jù)CA的公鑰,解碼出發(fā)送者的公鑰。然后就與上面的校驗流程完全相同了。
所以,數(shù)字證書主要解決了公鑰的安全發(fā)放問題。
因此,包含數(shù)字證書的整個簽名和校驗流程如下圖所示:
V1簽名和多渠道打包方案
V1簽名機制
默認情況下,APK使用的就是V1簽名。解壓APK后,在META-INF目錄下,可以看到三個文件:MANIFEST.MF、CERT.SF、CERT.RSA。它們都是V1簽名的產物。其中,MANIFEST.MF文件內容如下所示:
它記錄了APK中所有原始文件的數(shù)據(jù)摘要的Base64編碼,而數(shù)據(jù)摘要算法就是SHA1。CERT.SF文件內容如下所示:
SHA1-Digest-Manifest-Main-Attributes主屬性記錄了MANIFEST.MF文件所有主屬性的數(shù)據(jù)摘要的Base64編碼。SHA1-Digest-Manifest則記錄了整個MANIFEST.MF文件的數(shù)據(jù)摘要的Base64編碼。
其余的普通屬性則和MANIFEST.MF中的屬性一一對應,分別記錄了對應數(shù)據(jù)塊的數(shù)據(jù)摘要的Base64編碼。例如:CERT.SF文件中skin_drawable_btm_line.xml對應的SHA1-Digest,就是下面內容的數(shù)據(jù)摘要的Base64編碼。
Name: res/drawable/skin_drawable_btm_line.xml SHA1-Digest: JqJbk6/AsWZMcGVehCXb33Cdtrk= \r\n
這里要注意的是:最后一行的換行符是必不可少,需要參與計算的。
CERT.RSA文件包含了對CERT.SF文件的數(shù)字簽名和開發(fā)者的數(shù)字證書。RSA就是計算數(shù)字簽名使用的非對稱加密算法。
V1簽名的詳細流程可參考SignApk.java,整個簽名流程如下圖所示:
整個簽名機制的最終產物就是MANIFEST.MF、CERT.SF、CERT.RSA三個文件。
V1校驗流程
在安裝APK時,Android系統(tǒng)會校驗簽名,檢查APK是否被篡改。代碼流程是:PackageManagerService.java -> PackageParser.java,PackageParser類負責V1簽名的具體校驗。整個校驗流程如下圖所示:
若中間任何一步校驗失敗,APK就不能安裝。
OK,了解了V1的簽名和校驗流程。我們來看下,V1簽名是怎么保證APK文件不被篡改的?
首先,如果破壞者修改了APK中的任何文件,那么被篡改文件的數(shù)據(jù)摘要的Base64編碼就和MANIFEST.MF文件的記錄值不一致,導致校驗失敗。
其次,如果破壞者同時修改了對應文件在MANIFEST.MF文件中的Base64值,那么MANIFEST.MF中對應數(shù)據(jù)塊的Base64值就和CERT.SF文件中的記錄值不一致,導致校驗失敗。
最后,如果破壞者更進一步,同時修改了對應文件在CERT.SF文件中的Base64值,那么CERT.SF的數(shù)字簽名就和CERT.RSA記錄的簽名不一致,也會校驗失敗。
那有沒有可能繼續(xù)偽造CERT.SF的數(shù)字簽名那?理論上不可能,因為破壞者沒有開發(fā)者的私鑰。那破壞者是不是可以用自己的私鑰和數(shù)字證書重新簽名那,這倒是完全可以!
綜上所述,任何對APK文件的修改,在安裝時都會失敗,除非對APK重新簽名。但是相同包名,不同簽名的APK也是不能同時安裝的。
APK文件結構
由上述V1簽名和校驗機制可知,修改APK中的任何文件都會導致安裝失敗!那怎么添加渠道信息那?只能從APK的結構入手了。
APK文件本質上是一個ZIP壓縮包,而ZIP格式是固定的,主要由三部分構成,如下圖所示:
第一部分是內容塊,所有的壓縮文件都在這部分。每個壓縮文件都有一個local file header,主要記錄了文件名、壓縮算法、壓縮前后的文件大小、修改時間、CRC32值等。
第二部分稱為中央目錄,包含了多個central directory file header(和第一部分的local file header一一對應),每個中央目錄文件頭主要記錄了壓縮算法、注釋信息、對應local file header的偏移量等,方便快速定位數(shù)據(jù)。
最后一部分是EOCD,主要記錄了中央目錄大小、偏移量和ZIP注釋信息等,其詳細結構如下圖所示:
根據(jù)之前的V1簽名和校驗機制可知,V1簽名只會檢驗第一部分的所有壓縮文件,而不理會后兩部分內容。因此,只要把渠道信息寫入到后兩塊內容就可以通過V1校驗,而EOCD的注釋字段無疑是最好的選擇。
基于V1簽名的多渠道打包方案
既然找到了突破口,那么基于V1簽名的多渠道打包方案就應運而生:在APK文件的注釋字段,添加渠道信息。
整個方案包括以下幾步:
復制APK
找到EOCD數(shù)據(jù)塊
修改注釋長度
添加渠道信息
添加渠道信息長度
添加魔數(shù)
添加渠道信息后的EOCD數(shù)據(jù)塊如下所示:
這里添加魔數(shù)的好處是方便從后向前讀取數(shù)據(jù),定位渠道信息。
因此,讀取渠道信息包括以下幾步:
定位到魔數(shù)
向前讀兩個字節(jié),確定渠道信息的長度LEN
繼續(xù)向前讀LEN字節(jié),就是渠道信息了。
通過16進制編輯器,可以查看到添加渠道信息后的APK(小端模式),如下所示:
6C 74 6C 6F 76 75 7A 68是魔數(shù),04 00表示渠道信息長度為4,6C 65 6F 6E就是渠道信息leon了。0E 00就是APK注釋長度了,正好是15。
雖說整個方案很清晰,但是在找到EOCD數(shù)據(jù)塊這步遇到一個問題。如果APK本身沒有注釋,那最后22字節(jié)就是EOCD。但是若APK本身已經(jīng)包含了注釋字段,那怎么確定EOCD的起始位置那?這里借鑒了系統(tǒng)V2簽名確定EOCD位置的方案。整個計算流程如下圖所示:
整個方案介紹完了,該方案的最大優(yōu)點就是:不需要解壓縮APK,不需要重新簽名,只需要復制APK,在注釋字段添加渠道信息。每個渠道包僅需幾秒的耗時,非常適合渠道較多的APK。
但是好景不長,Android7.0之后新增了V2簽名,該簽名會校驗整個APK的數(shù)據(jù)摘要,導致上述渠道打包方案失效。所以如果想繼續(xù)使用上述方案,需要關閉Gradle Plugin中的V2簽名選項,禁用V2簽名。
V2簽名和多渠道打包方案
為什么需要V2簽名
從前面的V1簽名介紹,可以知道V1存在兩個弊端:
MANIFEST.MF中的數(shù)據(jù)摘要是基于原始未壓縮文件計算的。因此在校驗時,需要先解壓出原始文件,才能進行校驗。而解壓操作無疑是耗時的。
V1簽名僅僅校驗APK第一部分中的文件,缺少對APK的完整性校驗。因此,在簽名后,我們還可以修改APK文件,例如:通過zipalign進行字節(jié)對齊后,仍然可以正常安裝。
正是基于這兩點,Google提出了V2簽名,解決了上述兩個問題:
V2簽名是對APK本身進行數(shù)據(jù)摘要計算,不存在解壓APK的操作,減少了校驗時間。
V2簽名是針對整個APK進行校驗(不包含簽名塊本身),因此對APK的任何修改(包括添加注釋、zipalign字節(jié)對齊)都無法通過V2簽名的校驗。
關于第一點的耗時問題,這里有一份實驗室數(shù)據(jù)(Nexus 6P、Android 7.1.1)可供參考。
APK安裝耗時對比 取5次平均耗時(秒) V1簽名APK 11.64 V2簽名APK 4.42
可見,V2簽名對APK的安裝速度還是提升不少的。
V2簽名機制
不同于V1,V2簽名會生成一個簽名塊,插入到APK中。因此,V2簽名后的APK結構如下圖所示:
APK簽名塊位于中央目錄之前,文件數(shù)據(jù)之后。V2簽名同時修改了EOCD中的中央目錄的偏移量,使簽名后的APK還符合ZIP結構。
APK簽名塊的具體結構如下圖所示:
首先是8字節(jié)的簽名塊大小,此大小不包含該字段本身的8字節(jié);其次就是ID-Value序列,就是一個4字節(jié)的ID和對應的數(shù)據(jù);然后又是一個8字節(jié)的簽名塊大小,與開始的8字節(jié)是相等的;最后是16字節(jié)的簽名塊魔數(shù)。
其中,ID為0x7109871a對應的Value就是V2簽名塊數(shù)據(jù)。
V2簽名塊的生成可參考ApkSignerV2,整體結構和流程如下圖所示:
首先,根據(jù)多個簽名算法,計算出整個APK的數(shù)據(jù)摘要,組成左上角的APK數(shù)據(jù)摘要集;
接著,把最左側一列的數(shù)據(jù)摘要、數(shù)字證書和額外屬性組裝起來,形成類似于V1簽名的“MF”文件(第二列第一行);
其次,再用相同的私鑰,不同的簽名算法,計算出“MF”文件的數(shù)字簽名,形成類似于V1簽名的“SF”文件(第二列第二行);
然后,把第二列的類似MF文件、類似SF文件和開發(fā)者公鑰一起組裝成通過單個keystore簽名后的v2簽名塊(第三列第一行)。
最后,把多個keystore簽名后的簽名塊組裝起來,就是完整的V2簽名塊了(Android中允許使用多個keystore對apk進行簽名)。
上述流程比較繁瑣。簡而言之,單個keystore簽名塊主要由三部分組成,分別是上圖中第二列的三個數(shù)據(jù)塊:類似MF文件、類似SF文件和開發(fā)者公鑰,其結構如下圖所示:
除此之外,Google也優(yōu)化了計算數(shù)據(jù)摘要的算法,使得可以并行計算,如下圖所示:
數(shù)據(jù)摘要的計算包括以下幾步:
首先,將上述APK中文件內容塊、中央目錄、EOCD按照1MB大小分割成一些小塊。
然后,計算每個小塊的數(shù)據(jù)摘要,基礎數(shù)據(jù)是0xa5 + 塊字節(jié)長度 + 塊內容。
最后,計算整體的數(shù)據(jù)摘要,基礎數(shù)據(jù)是0x5a + 數(shù)據(jù)塊的數(shù)量 + 每個數(shù)據(jù)塊的摘要內容。
這樣,每個數(shù)據(jù)塊的數(shù)據(jù)摘要就可以并行計算,加快了V2簽名和校驗的速度。
V2校驗流程
Android Gradle Plugin2.2之上默認會同時開啟V1和V2簽名,同時包含V1和V2簽名的CERT.SF文件會有一個特殊的主屬性,如下圖所示:
該屬性會強制APK走V2校驗流程(7.0之上),以充分利用V2簽名的優(yōu)勢(速度快和更完善的校驗機制)。
因此,同時包含V1和V2簽名的APK的校驗流程如下所示:
簡而言之:優(yōu)先校驗V2,沒有或者不認識V2,則校驗V1。
這里引申出另外一個問題:APK簽名時,只有V2簽名,沒有V1簽名行不行?
經(jīng)過嘗試,這種情況是可以編譯通過的,并且在Android 7.0之上也可以正確安裝和運行。但是7.0之下,因為不認識V2,又沒有V1簽名,所以會報沒有簽名的錯誤。
OK,明確了Android平臺對V1和V2簽名的校驗選擇之后,我們來看下V2簽名的具體校驗流程(PackageManagerService.java -> PackageParser.java -> ApkSignatureSchemeV2Verifier.java),如下圖所示:
其中,最強簽名算法是根據(jù)該算法使用的數(shù)據(jù)摘要算法來對比產生的,比如:SHA512 > SHA256。
校驗成功的定義是至少找到一個keystore對應的簽名塊,并且所有簽名塊都按照上述流程校驗成功。
下面我們來看下V2簽名是怎么保證APK不被篡改的?
首先,如果破壞者修改了APK文件的任何部分(簽名塊本身除外),那么APK的數(shù)據(jù)摘要就和“MF”數(shù)據(jù)塊中記錄的數(shù)據(jù)摘要不一致,導致校驗失敗。
其次,如果破壞者同時修改了“MF”數(shù)據(jù)塊中的數(shù)據(jù)摘要,那么“MF”數(shù)據(jù)塊的數(shù)字簽名就和“SF”數(shù)據(jù)塊中記錄的數(shù)字簽名不一致,導致校驗失敗。
然后,如果破壞者使用自己的私鑰去加密生成“SF”數(shù)據(jù)塊,那么使用開發(fā)者的公鑰去解密“SF”數(shù)據(jù)塊中的數(shù)字簽名就會失??;
最后,更進一步,若破壞者甚至替換了開發(fā)者公鑰,那么使用數(shù)字證書中的公鑰校驗簽名塊中的公鑰就會失敗,這也正是數(shù)字證書的作用。
綜上所述,任何對APK的修改,在安裝時都會失敗,除非對APK重新簽名。但是相同包名,不同簽名的APK也是不能同時安裝的。
到這里,V2簽名已經(jīng)介紹完了。但是在最后一步“數(shù)據(jù)摘要校驗”這里,隱藏了一個點,不知道有沒有人發(fā)現(xiàn)?
因為,我們V2簽名塊中的數(shù)據(jù)摘要是針對APK的文件內容塊、中央目錄和EOCD三塊內容計算的。但是在寫入簽名塊后,修改了EOCD中的中央目錄偏移量,那么在進行V2簽名校驗時,理論上在“數(shù)據(jù)摘要校驗”這步應該會校驗失敗??!但是為什么V2簽名可以校驗通過那?
這個問題很重要,因為我們下面要介紹的基于V2簽名的多渠道打包方案也會修改EOCD的中央目錄偏移量。
其實也很簡單,原來Android系統(tǒng)在校驗APK的數(shù)據(jù)摘要時,首先會把EOCD的中央目錄偏移量替換成簽名塊的偏移量,然后再計算數(shù)據(jù)摘要。而簽名塊的偏移量不就是v2簽名之前的中央目錄偏移量嘛?。?!,因此,這樣計算出的數(shù)據(jù)摘要就和“MF”數(shù)據(jù)塊中的數(shù)據(jù)摘要完全一致了。具體代碼邏輯,可參考ApkSignatureSchemeV2Verifier.java的416 ~ 420行。
基于V2簽名的多渠道打包方案
在上節(jié)V2簽名的校驗流程中,有一個很重要的細節(jié):Android系統(tǒng)只會關注ID為0x7109871a的V2簽名塊,并且忽略其他的ID-Value,同時V2簽名只會保護APK本身,不包含簽名塊。
因此,基于V2簽名的多渠道打包方案就應運而生:在APK簽名塊中添加一個ID-Value,存儲渠道信息。
整個方案包括以下幾步:
找到APK的EOCD塊
找到APK簽名塊
獲取已有的ID-Value Pair
添加包含渠道信息的ID-Value
基于所有的ID-Value生成新的簽名塊
修改EOCD的中央目錄的偏移量(上面已介紹過:修改EOCD的中央目錄偏移量,不會導致數(shù)據(jù)摘要校驗失?。?/p>
用新的簽名塊替代舊的簽名塊,生成帶有渠道信息的APK
實際上,除了渠道信息,我們可以在APK簽名塊中添加任何輔助信息。
通過16進制編輯器,可以查看到添加渠道信息后的APK(小端模式),如下所示:
6C 65 6F 6E就是我們的渠道信息leon。向前4個字節(jié):FF 55 11 88就是我們添加的ID,再向前8個字節(jié):08 00 00 00 00 00 00 00就是我們的ID-Value的長度,正好是8。
整個方案介紹完了,該方案的最大優(yōu)點就是:支持7.0之上新增的V2簽名,同時兼有V1方案的所有優(yōu)點。
多渠道包的強校驗
那么如何保證通過這些方案生成的渠道包,能夠在所有Android平臺上正確安裝那?
原來Google提供了一個同時支持V1和V2簽名和校驗的工具:apksig。它包括一個apksigner命令行和一個apksig類庫。其中前者就是Android SDK build-tools下面的命令行工具。而我們正是借助后面的apksig來進行渠道包強校驗,它可以保證渠道包在apk Minsdk ~ 最高版本之間都校驗通過。詳細代碼可參考VerifyApk.java
多渠道打包工具對比
目前市面上的多渠道打包工具主要有packer-ng-plugin和美團的Walle。下表是我們的ApkChannelPackage和它們之間的簡單對比。
這里我之所以同時支持V1和V2簽名方案,主要是擔心后續(xù)Android平臺加強簽名校驗機制,導致V2多渠道打包方案行不通,可以無痛切換到V1簽名方案。后續(xù)我也會盡快支持命令行工具。
ApkChannelPackage插件接入
多渠道打包方式選擇
目前Gradle Plugin 2.2以上默認開啟V2簽名,所以如果想關閉V2簽名,可將下面的v2SigningEnabled設置為false。
signingConfigs {? ? ? ? release {? ? ? ? ? ? ...? ? ? ? ? ? v1SigningEnabled true? ? ? ? ? ? v2SigningEnabled false? ? ? ? }? ? ? ? debug {? ? ? ? ? ? ...? ? ? ? ? ? v1SigningEnabled true? ? ? ? ? ? v2SigningEnabled false? ? ? ? }? ? }
接入流程
1.在根工程的build.gradle中,添加對打包Plugin的依賴:
dependencies {? ? ? ? classpath 'com.android.tools.build:gradle:2.2.0'? ? ? ? classpath 'com.leon.channel:plugin:1.0.1'}
2.在主App工程的build.gradle中,添加對ApkChannelPackage Plugin的引用:
apply plugin: 'channel'
3.在主App工程的build.gradle中,添加讀取渠道信息的helper類庫依賴:
dependencies {? ? compile 'com.leon.channel:helper:1.0.1'}
4.在gradle.properties文件中,配置渠道文件名稱
channel_file=channel.txt
其中channel.txt即為包含渠道信息的文件,需放置在根工程目錄下,一行一個渠道信息。
5.渠道包信息配置
若是直接編譯生成多渠道包,則通過channel標簽配置:
channel{? ? //多渠道包的輸出目錄,默認為new File(project.buildDir,"channel")? ? baseOutputDir = new File(project.buildDir,"xxx")? ? //多渠道包的命名規(guī)則,默認為:${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}? ? apkNameFormat ='${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}'}
其中,多渠道包的命名規(guī)則中,可使用以下字段:
appName : 當前project的name
versionName : 當前Variant的versionName
versionCode : 當前Variant的versionCode
buildType : 當前Variant的buildType,即debug or release
flavorName : 當前的渠道名稱
appId : 當前Variant的applicationId
若是根據(jù)已有基礎包生成多渠道包,則通過rebuildChannel標簽配置:
rebuildChannel { baseDebugApk = 已有Debug APK? ? baseReleaseApk = 已有Release APK//默認為new File(project.buildDir, "rebuildChannel/debug")debugOutputDir = Debug渠道包輸出目錄? ? //默認為new File(project.buildDir, "rebuildChannel/release")releaseOutputDir = Release渠道包輸出目錄 }
這里要注意一下,已有APK的名字必須包含base字符串,這樣插件生成多渠道包時,會用當前的渠道替換base字符串,形成新的渠道包。
6.生成多渠道包
若沒有通過Gradle Plugin的 productFlavors配置多渠道,那么通過以下Task
channelDebug 、channelRelease分別負責生成Debug和Release的多渠道包。
若是配置了productFlavors,那么對應的Task則是channelFlavorXDebug、channelFlavorXRelease,F(xiàn)lavorX表示在productFlavors中配置的渠道名稱。
除此之外,如果是根據(jù)已有基礎包生成多渠道包,那么對應的Task則是reBuildChannel。
7.讀取渠道信息
通過helper類庫中的ChannelReaderUtil類讀取渠道信息。
String channel = ChannelReaderUtil.getChannel(getApplicationContext());
更多精彩內容歡迎關注騰訊 Bugly的微信公眾賬號:
騰訊 Bugly是一款專為移動開發(fā)者打造的質量監(jiān)控工具,幫助開發(fā)者快速,便捷的定位線上應用崩潰的情況以及解決方案。智能合并功能幫助開發(fā)同學把每天上報的數(shù)千條?Crash?根據(jù)根因合并分類,每日日報會列出影響用戶數(shù)最多的崩潰,精準定位功能幫助開發(fā)同學定位到出問題的代碼行,實時上報可以在發(fā)布后快速的了解應用的質量情況,適配最新的 iOS, Android 官方操作系統(tǒng),鵝廠的工程師都在使用,快來加入我們吧!