Android V2签名与校验原理分析
【前言】
?????V1簽名作為一種歷史悠久的簽名方式,弊端也是比較明顯的,一方面由于V1簽名是對Apk內(nèi)的單個文件逐一計(jì)算摘要進(jìn)行簽名校驗(yàn)的,所以要是Apk內(nèi)的文件比較多,計(jì)算速度是非常慢的,同時又因?yàn)橹粚蝹€文件的完整性進(jìn)行校驗(yàn),那么對apk壓縮包包體進(jìn)行篡改的話,簽名依然還是可以校驗(yàn)通過,完整性的校驗(yàn)工作做得不夠到位。到了Android 7.0,V2簽名方式就應(yīng)運(yùn)而生,V2簽名一種全文件簽名方案,它對壓縮包的三大基本組成部分:數(shù)據(jù)區(qū)、中央目錄記錄區(qū)、中央目錄記錄結(jié)尾區(qū)進(jìn)行分塊,每小塊 1MB,然后并行計(jì)算出每小塊的摘要值,最后將計(jì)算出來的摘要值拼接起來再一起算出整體的摘要值,并用私鑰對其進(jìn)行簽名,計(jì)算速度方面相比V1簽名有很大的提升,同時完整性校驗(yàn)方面做得更加周全。
一、V2簽名過程分析
1、V2簽名Apk的結(jié)構(gòu)示意圖
?????使用 APK v2簽名方案 進(jìn)行簽名時,會在 APK 文件中插入一個 APK 簽名分塊,該分塊位于ZIP 中央目錄部分之前并緊鄰該部分。在APK 簽名分塊內(nèi),v2 簽名和簽名者身份信息會存儲在 APK 簽名方案 v2 分塊中。APK v2簽名方案 是在 Android 7.0 (Nougat) 才引入的,為了使 APK 可在 Android 6.0 (Marshmallow) 及更低版本的設(shè)備上安裝,應(yīng)先使用 JAR 簽名功能對 APK 進(jìn)行簽名,然后再使用 v2 方案對其進(jìn)行簽名。
?????為了保持與 v1 APK 格式向后兼容,v2 及更高版本的 APK 簽名會存儲在“APK 簽名分塊”內(nèi),該分塊是為了支持 APK 簽名方案 v2 而引入的一個新容器。在 APK 文件中,“APK 簽名分塊”位于“ZIP 中央目錄”之前并緊鄰該部分。該分塊包含多個“ID-VALUE”對,所采用的封裝方式有助于更輕松地在 APK 中找到該分塊,APK 的 v2 簽名會存儲為一個“ID-VALUE”對,其中 ID 為 0x7109871a。
2、APK簽名分塊(APK Signing Block)格式
?????APK簽名分塊的前8個字節(jié)記錄了APK簽名分塊的大小 size of block(不含自身8字節(jié)),其后緊接著鍵值對數(shù)據(jù)塊,數(shù)據(jù)塊由一個個的鍵值對塊組成。 每個鍵值對塊的開始8字節(jié)記錄了「鍵值對的ID」+「鍵值對的Value」的大小,接下來4字節(jié)是鍵值對的ID,后面緊跟著對應(yīng)的值。 ID = 0x7109871a 的鍵值對塊就是保存V2簽名信息的地方。 鍵值對數(shù)據(jù)塊的后面還有8個字節(jié),也是用于記錄「整個APK簽名分塊」的大小,它的值和最開始的8字節(jié)相同。 簽名塊的末尾是一個魔數(shù)magic,也就是APK Sig Block 42的 ASCII 碼(小端排序)。
?????在解析 APK 時,首先要通過以下方法找到“ZIP 中央目錄”的起始位置:在文件末尾找到“ZIP 中央目錄結(jié)尾”記錄,然后從該記錄中讀取“中央目錄”的起始偏移量。通過 magic 值,可以快速確定“中央目錄”前方可能是“APK 簽名分塊”。然后,通過 size of block 值,可以高效地找到該分塊在文件中的起始位置,在解譯該分塊時,應(yīng)忽略 ID 未知的“ID-值”對。
【注意】
????? 由上圖分析可知,APK簽名分塊的大小 size of block占8個字節(jié),表示的是除了上圖紅框之外的所有數(shù)據(jù)的大小(即黃色框里面那部分?jǐn)?shù)據(jù)的大小),一開始以為 size of block取值范圍:0~ 263-1 , 結(jié)果看了驗(yàn)證V2簽名源碼,發(fā)現(xiàn)size of block取值范圍為:24 ~ (整型最大值-8),即24 ~ (231-1-8)
????? 構(gòu)造APK簽名區(qū)塊的代碼邏輯如下:
3、V2簽名信息數(shù)據(jù)格式
?????APK 由一個或多個簽名者簽名,每個簽名者均由一個簽名密鑰來表示。該信息會以“APK 簽名方案 v2 分塊”的形式存儲。對于每個簽名者,都會存儲以下信息:
- (簽名算法、摘要、簽名)元組。摘要會存儲起來,以便將簽名驗(yàn)證和 APK 內(nèi)容完整性檢查拆開進(jìn)行。
- 表示簽名者身份的 X.509 證書鏈。
- 采用鍵值對形式的其他屬性。
?????對于每位簽名者,都會使用收到的列表中支持的簽名來驗(yàn)證 APK。簽名算法未知的簽名會被忽略。如果遇到多個支持的簽名,則由每個實(shí)現(xiàn)來選擇使用哪個簽名。這樣一來,以后便能夠以向后兼容的方式引入安全系數(shù)更高的簽名方法。建議的方法是驗(yàn)證安全系數(shù)最高的簽名。
4、APK摘要計(jì)算過程
為了保護(hù) APK 內(nèi)容,APK 包含以下 4 個部分:
- ZIP 條目的內(nèi)容(從偏移量 0 處開始一直到“APK 簽名分塊”的起始位置)
- APK 簽名分塊
- ZIP 中央目錄
- ZIP 中央目錄結(jié)尾
APK 簽名方案 v2 負(fù)責(zé)保護(hù)第 1、3、4 部分的完整性,以及第 2 部分包含的“APK 簽名方案 v2 分塊”中的 signed data 分塊的完整性。第 1、3 和 4 部分的完整性通過其內(nèi)容的一個或多個摘要來保護(hù),這些摘要存儲在 signed data 分塊中,而這些signed data分塊則通過一個或多個簽名來保護(hù)。
第 1、3 和 4 部分的摘要采用以下計(jì)算方式,類似于兩級 Merkle 樹:
① 拆分塊chunk
?????將每個部分(即上面標(biāo)注第1、3、4部分)拆分成多個大小為 1 MB大小的塊chunk,最后一個塊chunk可能小于1MB。之所以分塊,是為了可以通過并行計(jì)算摘要以加快計(jì)算速度;
② 計(jì)算塊chunk摘要
?????字節(jié) 0xa5 + 塊的長度(字節(jié)數(shù)) + 塊的內(nèi)容 拼接起來用對應(yīng)的摘要算法進(jìn)行計(jì)算出每一塊的摘要值;
③ 計(jì)算整體摘要
?????字節(jié) 0x5a + chunk數(shù) + 塊的摘要(按塊在 APK 中的順序)拼接起來用對應(yīng)的摘要算法進(jìn)行計(jì)算出整體的摘要值;
【注意】
?????中央目錄結(jié)尾記錄中包含了中央目錄的起始偏移量,插入APK簽名分塊后,中央目錄的起始偏移量將發(fā)生變化。故在校驗(yàn)簽名計(jì)算摘要時,需要把中央目錄的起始偏移量當(dāng)作APK簽名分塊的起始偏移量。
二、V2簽名驗(yàn)證過程分析
?????因?yàn)閂2簽名機(jī)制是在Android 7.0中引入的,為了使APK可在Android 7.0以下版本中安裝,應(yīng)先用V1簽名對APK進(jìn)行簽名,再用V2方案進(jìn)行簽名。要注意順序一定是先V1簽名再V2簽名,因?yàn)閂1簽名的改動會修改到ZIP三大部分的內(nèi)容,先使用V2簽名再V1簽名會破壞V2簽名的完整性。
?????在 Android 7.0 以上版本,會優(yōu)先以 v2方案驗(yàn)證 APK,在Android 7.0以下版本中,系統(tǒng)會忽略 v2 簽名,僅驗(yàn)證 v1 簽名。Android 7.0+的校驗(yàn)過程如下:
1、防回滾保護(hù)
?????因?yàn)樵诮?jīng)過V2簽名的APK中同時帶有V1簽名,攻擊者可能將APK的V2簽名刪除,使得Android系統(tǒng)只校驗(yàn)V1簽名。為了防范此類攻擊,帶 v2 簽名的 APK 如果還帶 V1 簽名,其 META-INF/*.SF 文件的主要部分中必須包含 X-Android-APK-Signed 屬性。該屬性的值是一組以英文逗號分隔的 APK 簽名方案 ID(v2 方案的 ID 為 2)。在驗(yàn)證 v1 簽名時,對于此組中驗(yàn)證程序首選的 APK 簽名方案(例如,v2 方案),如果 APK 沒有相應(yīng)的簽名,APK 驗(yàn)證程序必須要拒絕這些 APK。此項(xiàng)保護(hù)依賴于 META-INF/*.SF 文件受 v1 簽名保護(hù)這一事實(shí)。
?????攻擊者可能還會試圖從“APK 簽名方案 v2 分塊”中刪除安全系數(shù)較高的簽名。為了防范此類攻擊,對 APK 進(jìn)行簽名時使用的簽名算法 ID 的列表會存儲在通過各個簽名保護(hù)的 signed data 分塊中。
2、簽名校驗(yàn)過程
?????我們知道跟安裝包相關(guān)的處理邏輯都會經(jīng)過PackageManagerService,下面時序圖展示了從PackageManagerService開始,一直到V2簽名校驗(yàn)函數(shù)的流程,在Android Studio中下載對應(yīng)版本SDK的源碼,雙擊Shift鍵,輸入搜索PackageManagerService即可一步步找到V2簽名校驗(yàn)的源碼,以下是android api 30的源碼時序圖:
下面來看看怎么從apk中找到APK簽名分塊:
Ⅰ、先從ZIP中央目錄開始位置centralDirOffset,指針往前16個字節(jié),然后讀取16個字節(jié)數(shù)據(jù),判斷是否等于魔數(shù)“APK Sig Block 42”ASCII碼值
Ⅱ、再從ZIP中央目錄開始位置centralDirOffset,指針往前24個字節(jié),然后讀取8個字節(jié)的數(shù)據(jù),這個值就是尾部記錄的APK簽名分塊大小apkSigBlockSizeInFooter
Ⅲ、尾部記錄的APK簽名分塊大小apkSigBlockSizeInFooter + 8字節(jié),就是APK簽名分塊整體的大小totalSize,APK簽名分塊開始位置apkSigBlockOffset = ZIP中央目錄開始位置centralDirOffset- APK簽名分塊整體的大小totalSize
Ⅳ、從APK簽名分塊開始位置apkSigBlockOffset開始,讀取8個字節(jié)數(shù)據(jù),這個值就是頭部記錄的APK簽名分塊大小apkSigBlockSizeInHeader
Ⅴ、假如 頭部記錄的APK簽名分塊大小apkSigBlockSizeInHeader = 尾部記錄的APK簽名分塊大小apkSigBlockSizeInFooter,那么從APK簽名分塊開始位置apkSigBlockOffset 開始,讀取APK簽名分塊整體的大小totalSize個字節(jié)數(shù)據(jù),這就是整個APK簽名分塊數(shù)據(jù)
接下來看看怎么從APK簽名分塊中找到V2簽名信息:
Ⅰ、鍵值對數(shù)據(jù)分塊是保存著一個個帶有長度前綴的鍵值對,大致如下:
其中,鍵值對長度 = key的長度(固定4個字節(jié)) + value的長度,即鍵值對長度不含自身的長度(固定8字節(jié))
Ⅱ、ByteBuffer 讀取數(shù)據(jù)時候,讀4個字節(jié)的數(shù)據(jù)可以用getInt,讀8個字節(jié)的數(shù)據(jù)可以用getLong,使用這兩個方法之后,指針會自動往前移動對應(yīng)的字節(jié)
最后來看看V2簽名信息校驗(yàn)流程:
Ⅰ、先從V2簽名信息區(qū)中讀取被簽名的數(shù)據(jù)signedData、多個簽名者的簽名signatures、公鑰字節(jié)數(shù)據(jù)publicKeyBytes
Ⅱ、從多個簽名者的簽名signatures中找出安全系數(shù)最高的簽名算法bestSigAlgorithm以及該算法對應(yīng)的簽名bestSigAlgorithmSignatureBytes
Ⅲ、用公鑰字節(jié)數(shù)據(jù)publicKeyBytes構(gòu)造出公鑰publicKey,然后使用公鑰publicKey對簽名bestSigAlgorithmSignatureBytes進(jìn)行解密得到被簽名的數(shù)據(jù)signedData的hash值H1,然后對被簽名的數(shù)據(jù)signedData計(jì)算得到hash值H2, 要是H1 = H2, 那么簽名驗(yàn)證通過
Ⅳ、然后讀出安全系數(shù)最高的簽名算法bestSigAlgorithm對應(yīng)的APK摘要值contentDigest
Ⅴ、接著讀取出簽名用到的證書certificates,并從第一個證書中讀取出公鑰字節(jié)數(shù)據(jù)certificatePublicKeyBytes,要是公鑰字節(jié)數(shù)據(jù)certificatePublicKeyBytes = 公鑰字節(jié)數(shù)據(jù)publicKeyBytes,那么公鑰驗(yàn)證通過
Ⅵ、開始計(jì)算對壓縮包三大組成部分:ZIP條目內(nèi)容、ZIP中央目錄、ZIP中央目錄尾部,分別分成1MB的大小(每部分最后一塊可能不足1MB), 然后計(jì)算出摘要值(注意:計(jì)算摘要之前,ZIP中央目錄尾部記錄的ZIP中央目錄開始位置偏移量要修改成APK簽名分塊開始位置的偏移量,因?yàn)榻oAPK進(jìn)行V2簽名時候,就是沒有算上加入APK簽名分塊)
【擴(kuò)展知識】
三、APK摘要計(jì)算代碼片段(關(guān)鍵部分加了注釋)
/*** 計(jì)算每一個1MB數(shù)據(jù)塊的摘要值以及所有數(shù)據(jù)塊摘要整體串聯(lián)起來的摘要值* @param executor 并行計(jì)算* @param digestAlgorithms 摘要算法集合* @param contents 包括zip條目內(nèi)容、zip中央目錄、 zip中央目錄結(jié)尾 3部分的數(shù)據(jù)* @param outputContentDigests 保存摘要算法與APK整體摘要值* @throws NoSuchAlgorithmException* @throws DigestException*/static void computeOneMbChunkContentDigests(RunnablesExecutor executor,Set<ContentDigestAlgorithm> digestAlgorithms,DataSource[] contents,Map<ContentDigestAlgorithm, byte[]> outputContentDigests)throws NoSuchAlgorithmException, DigestException {long chunkCountLong = 0;for (DataSource input : contents) {//計(jì)算每一部分按每塊1MB分割,可以分割的塊數(shù)chunkCountLong += getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);}if (chunkCountLong > Integer.MAX_VALUE) {throw new DigestException("Input too long: " + chunkCountLong + " chunks");}//記錄分割為每塊為1MB之后,總共的塊數(shù)int chunkCount = (int) chunkCountLong;List<ChunkDigests> chunkDigestsList = new ArrayList<>(digestAlgorithms.size());for (ContentDigestAlgorithm algorithms : digestAlgorithms) {//添加每個摘要算法對應(yīng)的前5個字節(jié)的數(shù)據(jù) {0x5a, 4-bytes-of-little-endian-chunk-count, digests*...}.chunkDigestsList.add(new ChunkDigests(algorithms, chunkCount));}//按順序提供切割成1MB的數(shù)據(jù)塊的提供商ChunkSupplier chunkSupplier = new ChunkSupplier(contents);//多線程并行計(jì)算切分出來數(shù)據(jù)塊的摘要值executor.execute(new RunnablesProvider() {@Overridepublic Runnable createRunnable() {return new ChunkDigester(chunkSupplier, chunkDigestsList);}});// Compute and write out final digest for each algorithm.// 對于每個摘要算法,算出整體的摘要簽名for (ChunkDigests chunkDigests : chunkDigestsList) {MessageDigest messageDigest = chunkDigests.createMessageDigest();outputContentDigests.put(chunkDigests.algorithm,messageDigest.digest(chunkDigests.concatOfDigestsOfChunks));}}ChunkDigests 這個類主要用記錄拼接進(jìn)來的每一塊的摘要值
private static class ChunkDigests {private final ContentDigestAlgorithm algorithm;private final int digestOutputSize;private final byte[] concatOfDigestsOfChunks;private ChunkDigests(ContentDigestAlgorithm algorithm, int chunkCount) {this.algorithm = algorithm;digestOutputSize = this.algorithm.getChunkDigestOutputSizeBytes();concatOfDigestsOfChunks = new byte[1 + 4 + chunkCount * digestOutputSize];// Fill the initial values of the concatenated digests of chunks, which is// {0x5a, 4-bytes-of-little-endian-chunk-count, digests*...}.concatOfDigestsOfChunks[0] = 0x5a;//將塊數(shù)chunkCount設(shè)置到concatOfDigestsOfChunks字節(jié)數(shù)組后4字節(jié)中setUnsignedInt32LittleEndian(chunkCount, concatOfDigestsOfChunks, 1);}private MessageDigest createMessageDigest() throws NoSuchAlgorithmException {return MessageDigest.getInstance(algorithm.getJcaMessageDigestAlgorithm());}private int getOffset(int chunkIndex) {return 1 + 4 + chunkIndex * digestOutputSize;}}private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {result[offset] = (byte) (value & 0xff);result[offset + 1] = (byte) ((value >> 8) & 0xff);result[offset + 2] = (byte) ((value >> 16) & 0xff);result[offset + 3] = (byte) ((value >> 24) & 0xff);}其中,對每一部分按1MB分割(最后一塊可能不足1MB),計(jì)算可以分割的塊數(shù), 實(shí)現(xiàn)邏輯如下:
/*** 計(jì)算按每塊大小為:chunksize,可以分割成多少塊* @param inputSize* @param chunkSize* @return*/private static long getChunkCount(long inputSize, long chunkSize) {return (inputSize + chunkSize - 1) / chunkSize;}ChunkSupplier這個類是用來構(gòu)造返回每一塊的數(shù)據(jù),可以多線程進(jìn)行操作
/*** Thread-safe 1MB DataSource chunk supplier. When bounds are met in a* supplied {@link DataSource}, the data from the next {@link DataSource}* are NOT concatenated. Only the next call to get() will fetch from the* next {@link DataSource} in the input {@link DataSource} array.*/private static class ChunkSupplier implements Supplier<ChunkSupplier.Chunk> {private final DataSource[] dataSources;private final int[] chunkCounts;private final int totalChunkCount;private final AtomicInteger nextIndex;private ChunkSupplier(DataSource[] dataSources) {this.dataSources = dataSources;chunkCounts = new int[dataSources.length];int totalChunkCount = 0;for (int i = 0; i < dataSources.length; i++) {long chunkCount = getChunkCount(dataSources[i].size(),CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);if (chunkCount > Integer.MAX_VALUE) {throw new RuntimeException(String.format("Number of chunks in dataSource[%d] is greater than max int.",i));}//記錄每部分的塊數(shù)chunkCounts[i] = (int)chunkCount;//記錄總的塊數(shù)totalChunkCount = (int) (totalChunkCount + chunkCount);}this.totalChunkCount = totalChunkCount;// 用于多線程nextIndex = new AtomicInteger(0);}/*** We map an integer index to the termination-adjusted dataSources 1MB chunks.* Note that {@link Chunk}s could be less than 1MB, namely the last 1MB-aligned* blocks in each input {@link DataSource} (unless the DataSource itself is* 1MB-aligned).*/@Overridepublic ChunkSupplier.Chunk get() {int index = nextIndex.getAndIncrement();if (index < 0 || index >= totalChunkCount) {return null;}int dataSourceIndex = 0;long dataSourceChunkOffset = index;for (; dataSourceIndex < dataSources.length; dataSourceIndex++) {//要是dataSourceChunkOffset沒超過dataSourceIndex指定的這一塊的塊數(shù),那么, 說明就是dataSourceIndex指定的區(qū)塊if (dataSourceChunkOffset < chunkCounts[dataSourceIndex]) {break;}// 要是dataSourceChunkOffset超過了dataSourceIndex當(dāng)前指定的這個數(shù)據(jù)源的塊數(shù),// 那么移到下一個數(shù)據(jù)源,同時dataSourceChunkOffset的計(jì)數(shù)要減去當(dāng)前這個數(shù)據(jù)源的塊數(shù)dataSourceChunkOffset -= chunkCounts[dataSourceIndex];}//剩余的大小取:1M 與 dataSourceIndex這一個數(shù)據(jù)源剩下的大小兩者的最小值long remainingSize = Math.min(dataSources[dataSourceIndex].size() -dataSourceChunkOffset * CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES,CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);final int size = (int)remainingSize;final ByteBuffer buffer = ByteBuffer.allocate(size);try {dataSources[dataSourceIndex].copyTo(dataSourceChunkOffset * CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES, size,buffer);} catch (IOException e) {throw new IllegalStateException("Failed to read chunk", e);}buffer.rewind();return new Chunk(index, buffer, size);}static class Chunk {private final int chunkIndex;private final ByteBuffer data;private final int size;private Chunk(int chunkIndex, ByteBuffer data, int size) {this.chunkIndex = chunkIndex;this.data = data;this.size = size;}}}ChunkDigester這個類主要用來計(jì)算chunk摘要,然后將計(jì)算出來的值拼接起來保存到 chunkDigest.concatOfDigestsOfChunks中
/*** A per-thread digest worker.*/private static class ChunkDigester implements Runnable {private final ChunkSupplier dataSupplier;private final List<ChunkDigests> chunkDigests;private final List<MessageDigest> messageDigests;private final DataSink mdSink;private ChunkDigester(ChunkSupplier dataSupplier, List<ChunkDigests> chunkDigests) {this.dataSupplier = dataSupplier;this.chunkDigests = chunkDigests;messageDigests = new ArrayList<>(chunkDigests.size());for (ChunkDigests chunkDigest : chunkDigests) {try {messageDigests.add(chunkDigest.createMessageDigest());} catch (NoSuchAlgorithmException ex) {throw new RuntimeException(ex);}}mdSink = DataSinks.asDataSink(messageDigests.toArray(new MessageDigest[0]));}@Overridepublic void run() {byte[] chunkContentPrefix = new byte[5];chunkContentPrefix[0] = (byte) 0xa5;try {for (ChunkSupplier.Chunk chunk = dataSupplier.get();chunk != null;chunk = dataSupplier.get()) {int size = chunk.size;if (size > CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES) {throw new RuntimeException("Chunk size greater than expected: " + size);}// 設(shè)置塊長度到chunkContentPrefix字節(jié)數(shù)組后4字節(jié)中setUnsignedInt32LittleEndian(size, chunkContentPrefix, 1);// 將chunkContentPrefix字節(jié)數(shù)組加入計(jì)算摘要mdSink.consume(chunkContentPrefix, 0, chunkContentPrefix.length);// 將塊數(shù)據(jù)加入計(jì)算摘要mdSink.consume(chunk.data);// Now finalize chunk for all algorithms.for (int i = 0; i < chunkDigests.size(); i++) {ChunkDigests chunkDigest = chunkDigests.get(i);//計(jì)算{0xa5, 4-bytes-of-little-endian-chunk-size, chunk-data}的摘要值,保存到chunkDigest.concatOfDigestsOfChunks數(shù)組中// 從chunkDigest.getOffset(chunk.chunkIndex)開始,一共chunkDigest.digestOutputSize字節(jié)空間中int actualDigestSize = messageDigests.get(i).digest(chunkDigest.concatOfDigestsOfChunks,chunkDigest.getOffset(chunk.chunkIndex),chunkDigest.digestOutputSize);if (actualDigestSize != chunkDigest.digestOutputSize) {throw new RuntimeException("Unexpected output size of " + chunkDigest.algorithm+ " digest: " + actualDigestSize);}}}} catch (IOException | DigestException e) {throw new RuntimeException(e);}}}并行計(jì)算類RunnaleExecutor用于控制多線程并行計(jì)算每一塊的摘要值,并等待所有線程計(jì)算結(jié)束才繼續(xù)后面的代碼邏輯
static final RunnablesExecutor MULTI_THREADED = new RunnablesExecutor() {private final int PARALLELISM = Math.min(32, Runtime.getRuntime().availableProcessors());private final int QUEUE_SIZE = 4;@Overridepublic void execute(RunnablesProvider provider) {final ExecutorService mExecutor =new ThreadPoolExecutor(PARALLELISM, PARALLELISM,0L, MILLISECONDS,new ArrayBlockingQueue<>(QUEUE_SIZE),new ThreadPoolExecutor.CallerRunsPolicy());//多線程同步控制器Phaser tasks = new Phaser(1);//創(chuàng)建PARALLELISM個任務(wù)并行執(zhí)行for (int i = 0; i < PARALLELISM; ++i) {Runnable task = new Runnable() {@Overridepublic void run() {//v2簽名對應(yīng)的Runable實(shí)現(xiàn)類為ApkSigningBlockUtils#ChunkDigesterRunnable r = provider.createRunnable();r.run();//當(dāng)前線程立即返回下一階段的序號,并且從Phaser中移出當(dāng)前線程,其他線程在調(diào)用arriveAndAwaitAdvance()時不需要等待當(dāng)前線程.tasks.arriveAndDeregister();}};// 線程計(jì)數(shù)parties +1tasks.register();mExecutor.execute(task);}// Waiting for the tasks to complete.// 表示當(dāng)前線程完成當(dāng)前階段的任務(wù),等待其他線程完成當(dāng)前階段的任務(wù).// 等待線程的數(shù)量就是tasks.register()注冊的數(shù)量(即parties的數(shù)值),只有調(diào)用了parties次 arriveAndDeregister(),才會繼續(xù)執(zhí)行后面的代碼tasks.arriveAndAwaitAdvance();mExecutor.shutdownNow();}};總結(jié)
以上是生活随笔為你收集整理的Android V2签名与校验原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华硕 TUF GAMING b360m
- 下一篇: Android-腾讯bugly符号表管理