How does android package in multiple channels?

About how to multi-channel packaging, the following text explains in detail how Android realizes multi-channel packaging and fast packaging.

Multi channel packaging

Configuring gradle for multi-channel packaging Whenever an application releases a new version, we will distribute it to every application market, such as 360 mobile assistant, Xiaomi application market, Huawei application market, etc. In order to count the downloads and activity of each application market, we must use a mark to distinguish the applications distributed in these different markets, and the channel number came into being. With the increasing number of channels, more and more channel packages need to be generated. In the process of packaging, we usually use gradle. Gradle improves our packaging a lot, and multi-channel packaging can also be easily realized.

1. First, define a meta data in the AndroidManifest.xml file

<meta-data
    android:name="CHANNEL"
    android:value="${CHANNEL_VALUE}" />

2. Then set productFlavors in the gradle file

android {  
    productFlavors {
        xiaomi {
            manifestPlaceholders = [CHANNEL_VALUE: "xiaomi"]
        }
        _360 {
            manifestPlaceholders = [CHANNEL_VALUE: "_360"]
        }
        baidu {
            manifestPlaceholders = [CHANNEL_VALUE: "baidu"]
        }
        wandoujia {
            manifestPlaceholders = [CHANNEL_VALUE: "wandoujia"]
        }
    }  
}

productFlavors is to create different versions of applications in the same project. See the specific configuration information Official description.

3. Execute gradle aS to output all channel packages.

Disadvantages of implementing multi-channel packaging with gradle Although it is simple and convenient for gradle to configure multi-channel packaging, there is a fatal defect in this method, that is, it takes time. Because the AndroidManifest.xml file has been modified, all packages must be recompiled and signed. Generally speaking, it takes at least an hour for 100 channel packages. Five cups of coffee in an hour is not enough. What's more, if there is a need to fine tune the code or copywriting, I'm sorry, I have to start all over again. This is very troublesome, so is there any way to quickly complete the packaging? Let's keep looking down.

Multi channel rapid packaging

Quick packaging scheme version_ one As mentioned above, we only modified the value of a meta data in the manifest file. Is there any way to avoid rebuilding the code? The answer is yes. We can use apktool to decompile our APK files.

apktool d yourApkName build

After decoding, we will get the following files:

We found that the manifest file we need to modify is in it, so we can modify its content through the command, and then repackage it to generate a new channel package, eliminating the process of recompiling and building the code. Use the Python script to replace the channel information in the manifest file.

import re

def replace_channel(channel, manifest):
    pattern = r'(<meta-data\s+android:name="channel"\s+android:value=")(\S+)("\s+/>)'
    replacement = r"\g<1>{channel}\g<3>".format(channel=channel)
    return re.sub(pattern, replacement, manifest)

Then, the folder is repackaged through apktool to generate APK.

apktool b build your_unsigned_apk

Finally, use jarsigner to re sign apk:

jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore your_keystore_path -storepass your_storepass -signedjar your_signed_apk, your_unsigned_apk, your_alias

Through the above series of processes, we can generate different channel packages without recompiling and building the project. This will save a lot of time. However, with the increase of channel packages, re signing will take a lot of time. Can we not re sign?

After analyzing the signature algorithm, it is found that adding a blank file under the META-INF folder after the packaging process will not affect the signature result.

So we just like meta_ Just write a blank file in the inf folder to identify the channel number. Write channels in APK files through Python scripts:

import zipfile
zipped = zipfile.ZipFile(your_apk, 'a', zipfile.ZIP_DEFLATED) 
empty_channel_file = "META-INF/mtchannel_{channel}".format(channel=your_channel)
zipped.write(your_empty_file, empty_channel_file)

After execution, a blank file will be generated under the META-INF folder:

Then we read the blank file in the project:

public static String getChannel(Context context) {
        ApplicationInfo appinfo = context.getApplicationInfo();
        String sourceDir = appinfo.sourceDir;
        String ret = "";
        ZipFile zipfile = null;
        try {
            zipfile = new ZipFile(sourceDir);
            Enumeration<?> entries = zipfile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = ((ZipEntry) entries.nextElement());
                String entryName = entry.getName();
                if (entryName.startsWith("mtchannel")) {
                    ret = entryName;
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (zipfile != null) {
                try {
                    zipfile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        String[] split = ret.split("_");
        if (split != null && split.length >= 2) {
            return ret.substring(split[0].length() + 1);

        } else {
            return "";
        }
    }

In this way, for each channel package generated, you only need to copy the APK, and then add an empty file to META-INF, so that 100 channel packages can be completed in one minute. The specific reasons can be seen below here.

Quick packaging scheme version_ two The above scheme has basically solved our packaging problem perfectly. However, it hasn't lasted long. Google has updated the application signature algorithm in Android 7.0- APK Signature Scheme v2 , it is a scheme for signing all files, which can provide faster application installation time and more protection for changes to unauthorized APK files. By default, Android Gradle 2.2.0 plug-in will sign your application using APK Signature Scheme v2 and traditional signature scheme. Because the whole file is signed, the previous scheme of adding a blank file has no effect. However, this scheme is not mandatory at present. We can choose to turn it off in the gradle configuration file:

android {
    defaultConfig { ... }
    signingConfigs {
      release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
        v2SigningEnabled false
      }
    }
  }

So what is the impact of the new signature scheme on the existing channel generation scheme? The following figure is a comparison between the new application signature scheme and the old signature scheme:

The new signature scheme will add an APK Signing Block in front of the file location of the Central Directory block in the ZIP file format. Next, analyze the APK package signed by the new application signature scheme according to the ZIP file format. The whole APK (ZIP file format) will be divided into the following four blocks:

  1. Contents of ZIP entries(from offset 0 until the start of APK Signing Block)
  2. APK Signing Block
  3. ZIP Central Directory
  4. ZIP End of Central Directory

The signature information of the new application signature scheme will be saved in block 2 (APK Signing Block), while block 1 (Contents of ZIP entries), block 3 (ZIP Central Directory) and block 4 (ZIP End of Central Directory) are protected. After signing, any modification to blocks 1, 3 and 4 can not escape the inspection of the new application signature scheme. The previous channel package generation scheme is to add an empty file in the META-INF directory and use the name of the empty file as the unique identification of the channel. Previously, adding a file under META-INF does not need to re sign the application, which will save a lot of packaging time and improve the speed of playing channel packages. However, under the new application signature scheme, META-INF has been listed in the protected area. The scheme of adding an empty file to META-INF will affect blocks 1, 3 and 4. After the application signed by the new application signature scheme is processed by our old generation channel package scheme, the following errors will be reported during installation:

Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: 
Failed to collect certificates from base.apk: META-INF/CERT.SF indicates base.apk is signed using APK Signature Scheme v2, 
but no such signature was found. Signature stripped?]

Blocks 1, 3 and 4 are protected. Any modification will cause inconsistent signatures, but block 2 is not protected, so can you find a solution on block 2? Let's first look at the file structure of block 2:

The APK signaling block in block 2 consists of two 8 bytes used to mark the length of the block + the magic number of the block (APK Sig Block 42) + the data carried by the block (ID value). Let's focus on this ID value, which consists of an 8-byte length mark + 4-byte ID + its load. The signature information of V2 is saved in this block with the ID value of ID (0x7109871a). I wonder if you have noticed that this is a group of ID values, that is, it can be composed of several such ID values. Can we do some articles here?

Author: Netease Shufan Link: https://www.zhihu.com/question/29477981/answer/476712744 Source: Zhihu The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source. The authentication process for signature is as follows:

  1. Find APK signaling block. If it can be found, verify it. If the verification is successful, continue the installation. If it fails, terminate the installation
  2. If the APK Signing Block is not found, the original signature verification mechanism will be executed. If the verification is successful, the installation will continue. If it fails, the installation will be terminated

During verification, the inspection code is as follows:

public static ByteBuffer findApkSignatureSchemeV2Block(
            ByteBuffer apkSigningBlock,
            Result result) throws SignatureNotFoundException {
        checkByteOrderLittleEndian(apkSigningBlock);
        // FORMAT:
        // OFFSET       DATA TYPE  DESCRIPTION
        // * @+0  bytes uint64:    size in bytes (excluding this field)
        // * @+8  bytes pairs
        // * @-24 bytes uint64:    size in bytes (same as the one above)
        // * @-16 bytes uint128:   magic
        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);

        int entryCount = 0;
        while (pairs.hasRemaining()) {
            entryCount++;
            if (pairs.remaining() < 8) {
                throw new SignatureNotFoundException(
                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
            }
            long lenLong = pairs.getLong();
            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
                throw new SignatureNotFoundException(
                        "APK Signing Block entry #" + entryCount
                                + " size out of range: " + lenLong);
            }
            int len = (int) lenLong;
            int nextEntryPos = pairs.position() + len;
            if (len > pairs.remaining()) {
                throw new SignatureNotFoundException(
                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
                                + ", available: " + pairs.remaining());
            }
            int id = pairs.getInt();
            if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
                return getByteBuffer(pairs, len - 4);
            }
            result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id);
            pairs.position(nextEntryPos);
        }

        throw new SignatureNotFoundException(
                "No APK Signature Scheme v2 block in APK Signing Block");
    }

We can find that a key position in the above code is if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {return getByteBuffer(pairs, len - 4);}. From the source code, we can see that Android searches for APK with ID_ SIGNATURE_ SCHEME_ V2_ BLOCK_ ID value with id = 0x7109871a to obtain APK Signature Scheme v2 Block. Other ID values in this block are ignored. That is, in APK Signature Scheme v2 There is no description of the unrecognized ID in the, and there is a description of the relevant processing. Therefore, we can customize the channel by writing a custom ID value.

So the idea should be as follows:

  1. The ID value in the APK package generated by the new application signature scheme is extended to provide a user-defined ID value (channel information) and saved in the APK
  2. In the App running phase, we can find the ID value we added by ourselves through the information in the EOCD (End of central directory) and Central directory structures of ZIP, so as to realize the function of obtaining channel information

Posted by pandu345 on Fri, 26 Nov 2021 07:14:01 -0800