笔记,避免忘记和丢失
签名脚本
import java.nio.BufferUnderflowException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.channels.FileChannel
/**
* 多渠道写入脚本
* Created by zhangjianliang on 2018/12/28
*/
//多渠道打包相关配置
// 发布文件夹 (绝对路径)
def packageLocPath = "/var/lib/jenkins/workspace/App_dir/outputs"
def windowsPackagePath = "${rootProject.rootDir}/outputs"
// 最终发布包存放的子目录
def childPath = "publish"
// 打包日期
def releaseTime = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+8"))
// 发布包前缀
def baseAppName = "appName"
// 渠道文件名
def channelFileName = "channel.txt"
// 渠道名:渠道值
def packageChannelMap = [:]
def DEFAULT_CHARSET = "UTF-8"
/*
The v2 signature of the APK is stored as an ID-value pair with ID 0x7109871a
(https://source.android.com/security/apksigning/v2.html#apk-signing-block)
*/
def APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
// Our Channel Block ID
def APK_CHANNEL_BLOCK_ID = 0x71777777;
/**
* APK Signing Block Magic Code: magic “APK Sig Block 42” (16 bytes)
* "APK Sig Block 42" : 41 50 4B 20 53 69 67 20 42 6C 6F 63 6B 20 34 32
*/
def APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; // LITTLE_ENDIAN, High
def APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; // LITTLE_ENDIAN, Low
def APK_SIG_BLOCK_MIN_SIZE = 32;
def ZIP_EOCD_REC_MIN_SIZE = 22;
def ZIP_EOCD_REC_SIG = 0x06054b50;
def UINT16_MAX_VALUE = 0xffff;
def ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
// The format of the APK Signing Block is as follows (all numeric fields are little-endian):
// .size of block in bytes (excluding this field) (uint64)
// .Sequence of uint64-length-prefixed ID-value pairs:
// *ID (uint32)
// *value (variable-length: length of the pair - 4 bytes)
// .size of block in bytes—same as the very first field (uint64)
// .magic “APK Sig Block 42” (16 bytes)
// FORMAT:
// OFFSET DATA TYPE DESCRIPTION
// * @+0 bytes uint64: size in bytes (excluding this field)
// * @+8 bytes payload
// * @-24 bytes uint64: size in bytes (same as the one above)
// * @-16 bytes uint128: magic
// payload 有 8字节的大小,4字节的ID,还有payload的内容组成
def payloads = [];
// 读取渠道值文件闭包 "渠道名:渠道值" 一行一个渠道
def readChannelFromFile = { String path ->
def channelMap = [:]
new File(path).eachLine {
String[] channelStr = it.split(":")
def channelName = channelStr[0].trim()
def value = "";
if (channelStr.length > 1) {
value = it.split(":")[1].trim()
}
channelMap[channelName] = value
}
return channelMap
}
// 准备渠道 key-value
packageChannelMap = readChannelFromFile("${rootDir}/${channelFileName}")
// 判断是否为 Windows 系统
def isWindowsOS = {
return System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0
}
// 判断是否为 Mac 系统
def isMacOS = {
return System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0
}
/**
* Relative <em>get</em> method for reading {@code size} number of bytes from the current
* position of this buffer.
* <p>
* <p>This method reads the next {@code size} bytes at this buffer's current position,
* returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
* {@code size}, byte order set to this buffer's byte order; and then increments the position by
* {@code size}.
*/
def getByteBuffer = { final ByteBuffer source, final int size ->
if (size < 0) {
throw new IllegalArgumentException("size: " + size);
}
final int originalLimit = source.limit();
final int position = source.position();
final int limit = position + size;
if ((limit < position) || (limit > originalLimit)) {
throw new BufferUnderflowException();
}
source.limit(limit);
try {
final ByteBuffer result = source.slice();
result.order(source.order());
source.position(limit);
return result;
} finally {
source.limit(originalLimit);
}
}
/**
* Returns new byte buffer whose content is a shared subsequence of this buffer's content
* between the specified start (inclusive) and end (exclusive) positions. As opposed to
* {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
* buffer's byte order.
*/
def sliceFromTo = { final ByteBuffer source, final int start, final int end ->
if (start < 0) {
throw new IllegalArgumentException("start: " + start);
}
if (end < start) {
throw new IllegalArgumentException("end < start: " + end + " < " + start);
}
final int capacity = source.capacity();
if (end > source.capacity()) {
throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
}
final int originalLimit = source.limit();
final int originalPosition = source.position();
try {
source.position(0);
source.limit(end);
source.position(start);
final ByteBuffer result = source.slice();
result.order(source.order());
return result;
} finally {
source.position(0);
source.limit(originalLimit);
source.position(originalPosition);
}
}
/**
* https://source.android.com/security/apksigning/v2.html
* https://en.wikipedia.org/wiki/Zip_(file_format)
*/
def writeApkSigningBlock = { DataOutput dataOutput ->
long length = 24;
// 24 = 8(size of block in bytes—same as the very first field (uint64)) + 16 (magic “APK Sig Block 42” (16 bytes))
for (int index = 0; index < payloads.size(); ++index) {
final ApkSigningPayload payload = payloads.get(index);
final byte[] bytes = payload.getByteBuffer();
length += 12 + bytes.length; // 12 = 8(uint64-length-prefixed) + 4 (ID (uint32))
}
ByteBuffer byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putLong(length);
byteBuffer.flip();
dataOutput.write(byteBuffer.array());
for (int index = 0; index < payloads.size(); ++index) {
final ApkSigningPayload payload = payloads.get(index);
final byte[] bytes = payload.getByteBuffer();
byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putLong(bytes.length + (8 - 4)); // Long.BYTES - Integer.BYTES
byteBuffer.flip();
dataOutput.write(byteBuffer.array());
byteBuffer = ByteBuffer.allocate(4); // Integer.BYTES
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putInt(payload.getId());
byteBuffer.flip();
dataOutput.write(byteBuffer.array());
dataOutput.write(bytes);
}
byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putLong(length);
byteBuffer.flip();
dataOutput.write(byteBuffer.array());
byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putLong(APK_SIG_BLOCK_MAGIC_LO);
byteBuffer.flip();
dataOutput.write(byteBuffer.array());
byteBuffer = ByteBuffer.allocate(8); // Long.BYTES
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putLong(APK_SIG_BLOCK_MAGIC_HI);
byteBuffer.flip();
dataOutput.write(byteBuffer.array());
return length;
}
def findCentralDirStartOffset = { final FileChannel fileChannel, final long commentLength ->
// End of central directory record (EOCD)
// Offset Bytes Description[23]
// 0 4 End of central directory signature = 0x06054b50
// 4 2 Number of this disk
// 6 2 Disk where central directory starts
// 8 2 Number of central directory records on this disk
// 10 2 Total number of central directory records
// 12 4 Size of central directory (bytes)
// 16 4 Offset of start of central directory, relative to start of archive
// 20 2 Comment length (n)
// 22 n Comment
// For a zip with no archive comment, the
// end-of-central-directory record will be 22 bytes long, so
// we expect to find the EOCD marker 22 bytes from the end.
final ByteBuffer zipCentralDirectoryStart = ByteBuffer.allocate(4);
zipCentralDirectoryStart.order(ByteOrder.LITTLE_ENDIAN);
fileChannel.position(fileChannel.size() - commentLength - 6);
// 6 = 2 (Comment length) + 4 (Offset of start of central directory, relative to start of archive)
fileChannel.read(zipCentralDirectoryStart);
final long centralDirStartOffset = zipCentralDirectoryStart.getInt(0);
return centralDirStartOffset;
}
def getCommentLength = { final FileChannel fileChannel ->
// End of central directory record (EOCD)
// Offset Bytes Description[23]
// 0 4 End of central directory signature = 0x06054b50
// 4 2 Number of this disk
// 6 2 Disk where central directory starts
// 8 2 Number of central directory records on this di