This is the 10th article in the Android Training Manual series. If you haven’t read the previous articles, please click here to check them out

Preliminary knowledge

  1. Understand basic Android development

How far can I go after reading this article

  1. Understand the key process of Android APK packaging
  2. Understand the principle of multi-channel packaging
  3. Understand Multiple APK, Split APKs, App Bundle derivative packaging methods

Prepare before reading

  1. Clone CommonTec project, where Simpleapk is the example used in this article.

The article gives an overview of

Most of our development is now done in Android Studio. In AS, we just hit the Run button and AS will automatically package Apk and install it on the device and Run. For us, the packaging process is a black box, all we know is Sources -> AS Compiler -> APK. In this article, we will analyze the packaging process in the middle and some problems related to packaging.

Let’s take a look at the internal structure of APK.

Android APK package structure

Let’s look at the structure of a normal APK. After an APK is packaged, there are usually several directories for storing different files. Assets native resource files are not compressed or processed by classes.dex Java code is converted to a class file using javac, and then converted to a dex file using dx. If there were more than one dex file, it would be named like this: class.dex classes2.dex classes3.dex… Class information is saved there. Lib/stores native library. So files, which are divided into different directories based on the CPU model, such as ARM, x86, etc. Res/holds the processed binary resource file. Resources.arsc holds a mapping of resource ID names and their corresponding values/paths. Meta-inf/is used to verify the APK signature, which has three important files manifest.mt, cert. SF, cert.rsa. Manifest.mf holds a summary of all files, which reads as follows:

Manifest-version: 1.0 built-in: Generated- By -ADT Created-By: Android Gradle 3.4.0 Name: AndroidManifest.xml SHA-256-Digest: QxJh66y6ssDSNFgZSlf5jIWXfRdWnqL1c3BSwSDUYLQ= Name: META-INF/android.arch.core_runtime.version SHA-256-Digest: zFL2eISLgUNzdXtGA4O/YZYOSUPCA3Na3eCjULPlCYk=Copy the code

Cert.sf saves a summary of each message in manifest.mf, which reads in part as follows:

Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA-256-Digest-Manifest: j8YGFgHsujCHud09pT6Igh21XQKSnG+Gqy8VUE55u+g=
X-Android-APK-Signed: 2

Name: AndroidManifest.xml
SHA-256-Digest: qLofC3g32qJ5LmbjO/qeccx2Ie/PPpWSEPBIUPrlKlY=

Name: META-INF/android.arch.core_runtime.version
SHA-256-Digest: I65bgli5vdqHKel7MD74YlSuuyCR/5NDrXr2kf5FigA=
Copy the code

Cert. RSA contains the signature of the cert. SF file and the certificate used for the signature.

Androidmanifest.xml is a file that everyone is familiar with, the global configuration file, but here is the compiled binary file.

Ii. APK packaging process

2.1 Original packaging process

We have seen a complete APK structure above, and the process of APK packaging is actually the process of generating the above files. Here is a flow chart that is widely circulated on the Internet.

There are mainly the following steps:

  1. Compile the resource files using AAPT/AAPT2 to generate resources. Arsc and R.java
  2. Use AIDL to process AIDL files and generate Java files
  3. Compile Java files using JAVAC to generate classes files
  4. Use DX/D8 to process the class file and generate the dex file required in the end
  5. Use Android NDK to process native code generation.so files
  6. Generate an unsigned APK using APkBuilder
  7. Apksigner is used to sign the Apk to generate the final Apk

2.2 How Do I Manually Pack packets using the CLI

The usual development is built using Gradle. Below, we do not rely on Gradle. We directly use the official packaging tool for each stage to type an APK by hand, so that we can have a better and more detailed understanding of the process. Here we use the Simpleapk project directly as an example. The official packaging tool is in the directory android_sdk/build-tools/version/.

To begin, we will create a TMP directory to store the intermediates. Create TMP /final to hold the end product.

1. AAPT2 compile resources

Using AAPT2 to process resources requires two steps, compile and link, and the compile operation is performed first. Execute the following command.

/Users/zy/ android-sdK-mac_x86 /build-tools/28.0.2/ aapT2 compile -o TMP /res --dir SRC /main/res/Copy the code

Processing a single resource using AAPT2 produces xxx.flat files, which are intermediate products of AAPT2 and can be used for subsequent incremental compilation of resources. We specify the directory of the resource directly with –dir, and the product res is a compressed package containing xxx.flat after processing all the resources. Here let’s unzip the RES package again. Execute the following command.

unzip -u tmp/res -d tmp/aapt2_res
Copy the code

When we’re done, the directory looks like this.

TMP / ├ ─ ─ aapt2_res │ └ ─ ─ XXX. Flat ├ ─ ─ final └ ─ ─ resCopy the code
2. AAPT2 link resources

AAPT2 link is a complete resource. Arsc, binary resource and R. Java after compiling the xxx.flat resource link in the previous step. Execute the following command.

/Users/zy/ android-sdK-mac_x86 /build-tools/28.0.2/ AAPT2 link-o TMP/res.apk-i /Users/zy/android-sdk-mac_x86/platforms/android-28/android.jar --manifest src/main/AndroidManifest.xml --java tmp -R tmp/aapt2_res/drawable-hdpi_ic_launcher_foreground.xml.flat -R tmp/aapt2_res/mipmap-anydpi-v26_ic_launcher_round.xml.flat -R tmp/aapt2_res/mipmap-xhdpi_ic_launcher_round.png.flat -R tmp/aapt2_res/values_colors.arsc.flat -R tmp/aapt2_res/drawable-hdpi_ic_launcher_foreground1.xml.flat -R tmp/aapt2_res/mipmap-hdpi_ic_launcher.png.flat -R tmp/aapt2_res/mipmap-xxhdpi_ic_launcher.png.flat -R tmp/aapt2_res/values_strings.arsc.flat -R tmp/aapt2_res/drawable-mdpi_ic_launcher_foreground.xml.flat -R tmp/aapt2_res/mipmap-hdpi_ic_launcher_round.png.flat -R tmp/aapt2_res/mipmap-xxhdpi_ic_launcher_round.png.flat -R tmp/aapt2_res/values_styles.arsc.flat -R tmp/aapt2_res/drawable_ic_launcher_background.xml.flat -R tmp/aapt2_res/mipmap-mdpi_ic_launcher.png.flat -R tmp/aapt2_res/mipmap-xxxhdpi_ic_launcher.png.flat -R tmp/aapt2_res/layout_activity_main.xml.flat -R tmp/aapt2_res/mipmap-mdpi_ic_launcher_round.png.flat -R tmp/aapt2_res/mipmap-xxxhdpi_ic_launcher_round.png.flat -R tmp/aapt2_res/mipmap-anydpi-v26_ic_launcher.xml.flat -R tmp/aapt2_res/mipmap-xhdpi_ic_launcher.png.flat --auto-add-overlayCopy the code

After executing the command, res.apk is generated, which contains resource. Arsc, the processed Androidmanifest.xml, and the processed binary resources. We also decompress it here and use it in the final packaging. Run the following command.

unzip -u tmp/res.apk -d tmp/final
Copy the code

At the end of this step, the directory state is as follows.

TMP / ├ ─ ─ aapt2_res │ └ ─ ─ XXX. Flat ├ ─ ─ com │ └ ─ ─ zy │ └ ─ ─ simpleapk │ └ ─ ─ R.j ava ├ ─ ─ final │ ├ ─ ─ AndroidManifest. XML │ ├ ─ ─ res │ │ ├ ─ ─ drawable │ │ │ └ ─ ─ ic_launcher_background. XML │ │ ├ ─ ─ XXX resource directory │ └ ─ ─ resources. The arsc ├ ─ ─ res └ ─ ─ res. The apkCopy the code
3. Javac generates a class file

In this step we need to process the Java file and generate the class file. Use the R.java file generated in the previous step. Execute the following command.

javac -d tmp src/main/java/com/zy/simpleapk/MainActivity.java  tmp/com/zy/simpleapk/R.java -cp /Users/zy/android-sdk-mac_x86/platforms/android-28/android.jar
Copy the code

At the end of this step, the directory state is as follows.

TMP / ├ ─ ─ aapt2_res │ └ ─ ─ XXX. Flat ├ ─ ─ com │ └ ─ ─ zy │ └ ─ ─ simpleapk │ ├ ─ ─ MainActivity. Class │ ├ ─ ─ R$color.class │ ├ ─ ─ R$drawable.class │ ├ ─ ─ R$layout.class │ ├ ─ ─ R$mipmap.class │ ├ ─ ─ R$string.class │ ├ ─ ─ R$style.class │ ├ ─ ─ R.c lass │ └ ─ ─ R.j ava ├ ─ ─ final │ ├ ─ ─ AndroidManifest. XML │ ├ ─ ─ res │ │ ├ ─ ─ drawable │ │ │ └ ─ ─ │ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├.txtCopy the code
4. D8 Compiles dex

This step is to compile the class file generated in the previous step into a dex file, using either D8 or dx, in this case D8. Execute the following command.

/Users/zy/ android-sdK-mac_x86 /build-tools/28.0.2/d8 TMP /com/zy/simpleapk/*. Class --output TMP --lib /Users/zy/android-sdk-mac_x86/platforms/android-28/android.jarCopy the code

After the command is executed, classes.dex is generated, which is the dex file that will be used in the final APK, and we will copy it to the final/ directory. Run the following command.

cp tmp/classes.dex tmp/final/classes.dex
Copy the code

At the end of this step, the directory state is as follows.

TMP / ├ ─ ─ aapt2_res │ └ ─ ─ XXX. Flat ├ ─ ─ classes. Dex ├ ─ ─ com │ └ ─ ─ zy │ └ ─ ─ simpleapk │ ├ ─ ─ MainActivity. Class │ ├ ─ ─ R$color.class │ ├ ─ ─ R$drawable.class │ ├ ─ ─ R$layout.class │ ├ ─ ─ R$mipmap.class │ ├ ─ ─ R$string.class │ ├ ─ ─ R$style.class │ ├ ─ ─ R.c lass │ └ ─ ─ R.j ava ├ ─ ─ final │ ├ ─ ─ AndroidManifest. XML │ ├ ─ ─ classes. Dex │ ├ ─ ─ res │ │ ├ ─ ─ drawable │ │ │ └ ─ ─ ic_launcher_background. XML │ │ ├ ─ ─ XXX resource directory │ └ ─ ─ resources. The arsc ├ ─ ─ res └ ─ ─ res. The apkCopy the code
5. Packaging apk

After executing the above command, all the materials needed to package APK are ready, because APK itself is zip format, here we directly package the above products with zip command, to generate final.apk. Execute the following command.

zip -r final.apk *
Copy the code
6. Apk signature

The packaged APK from the previous step cannot be installed directly because there is no signature. Here we use debug.keystore to sign final.apk. Execute the following command.

/Users/zy/ android-sdK-mac_x86 /build-tools/28.0.2/apksigner sign --ks ~/.android/debug.keystore final.apkCopy the code

You need to enter the debug.keystore password, which is Android.

In this way, the final. Apk is the apK generated manually. You can install it and try it out

2.3 AS packaging process

In normal development, we mostly just click Run in AS to compile an APK. Let’s start by looking at what happens when we click Run in AS. Click Run and open Build TAB to see the output. Gradle Task assembleDebug is executed.

Let’s add a line of output to build.gradle to see the extra parameters that AS adds to Gradle Task.

println "projectProperties: " + project.gradle.startParameter.projectProperties
Copy the code

The output is as follows:

projectProperties: [android.injected.build.density:xhdpi, android.injected.build.api:26, android.injected.invoked.from.ide:true, android.injected.build.abi:x86]
Copy the code

One of the parameters of the android. Injected. Invoked. From the ide = true indicates that the orders from the AS call Gradle. The next step is to execute Gradle’s package command. Gradle’s packaging commands execute tasks to generate the files needed for packaging. If you are not familiar with this aspect, you can take a look at the main process analysis of Android Gradle Plugin.

Three, multi-channel packaging

The packaging process of Android APK was introduced above, and I also experienced the whole process by typing APK manually. In the actual production and development process, we tend to send APK to various application markets. In many cases, we need to conduct some statistics according to the market channels, so we need to distinguish different market channels. There are many solutions to the problem of multi-channel packaging. The easiest thing to think of is to use Gradle’s Flavor and use Flavor’s features to generate different channel identifiers. However, this approach requires a build process for each channel package generated, which is very time consuming. Another way is to decompile APK using apkTool, modify resources, add channel identifiers, and repackage signatures. This method saves the build process, decompile, package, and signature steps, which are time-consuming. According to meituan’s blog, it takes nearly three hours to pack 900 channel packages.

So we need to find another way. Before we do that, let’s take a look at the way Android APK is signed. Understand the signature method, to better understand the method implementation.

Before we look at APK signatures, let’s take a look at the Zip file format, because APK is essentially a Zip file, and multichannel packaging involves Zip file formats.

3.1 Zip file format

APK is essentially a Zip file, which we can take a look at with the file command. It is a V2.0 Zip file.

The format of a Zip file is as follows:

It can be divided into three areas: Data area Central Directory End of Central Directory Record (EODR)

3.1.1 data area

This section records compressed file information and raw file data. Each file information is described by [local file header + file data + data Descriptor]. The local file header records compressed file information. The main contents are as follows:

description length content
local file header signature 4 bytes (0x04034b50) File header identifier, 0x04034B50
version needed to extract 2 bytes The lowest version required for decompression
general purpose bit flag 2 bytes Universal flag bit
compression method 2 bytes Compression way
last mod file time 2 bytes The time when the file was last modified
last mod file date 2 bytes Date the file was last modified
crc-32 4 bytes CRC – 32 check code
compressed size 4 bytes Compressed file size
uncompressed size 4 bytes Uncompressed file size
file name length 2 bytes File name length
extra field length 2 bytes Extension length
file name (variable size) The file name
extra field (variable size) Extension data

File Data Stores compressed file data

data descriptor

description length content
crc-32 4 bytes Crc32 checksum
compressed size 4 bytes Size of compressed file
uncompressed size 4 bytes Size of an uncompressed file

Indicates the end of the file. This parameter is displayed only when the third bit of the General Purpose bit Flag in the Local file header is 3. Not normally

3.1.2 Core Directory

The core directory holds more information about compressed files and support for Zip64. There are two main parts: File Header and Digital Signature

File header is a more specific description of a file. Each file header corresponds to a local file header.

description length content
central file header signature 4 bytes (0x02014b50) Core file header flag, magic number 0x02014b50
version made by 2 bytes The version used for compression
version needed to extract 2 bytes Decompress the required version
general purpose bit flag 2 bytes Universal bit mark
compression method 2 bytes Compression method
last mod file time 2 bytes The time when the file was last modified
last mod file date 2 bytes Date the file was last modified
crc-32 4 bytes CRC – 32 check code
compressed size 4 bytes Compressed file size
uncompressed size 4 bytes File size before compression
file name length 2 bytes File name length
extra field length 2 bytes Extension length
file comment length 2 bytes File comment length
disk number start 2 bytes The starting location of the file on the disk
internal file attributes 2 bytes Internal file attributes
external file attributes 4 bytes External file properties
relative offset of local header 4 bytes Offset of the corresponding Local file header
file name (variable size) The file name
extra field (variable size) Extension data
file comment (variable size) File comment

The official documentation of Digital Signature does not provide details about Digital signature, but it does mention that the core directory can be compressed and encrypted, and blind guess is used to encrypt the core directory.

description length content
header signature 4 bytes (0x05054b50)
size of data 2 bytes
signature data (variable size)
3.1.3 End of the Core Directory

The end of directory flag is used to mark the end of compressed directory data at the end of the entire file. Each Zip file has one and only one closing flag record.

description length content
end of central dir signature 4 bytes (0x06054b50) End of directory flag, magic 0x06054B50
number of this disk 2 bytes Current Disk Number
number of the disk with the start of the central directory 2 bytes Number of the disk at the start of the core directory
total number of entries in the central directory on this disk 2 bytes The number of core directories recorded on disk
total number of entries in the central directory 2 bytes Number of core directory structures
size of the central directory 4 bytes Core directory size
offset of start of central directory with respect to the starting disk number 4 bytes The offset of the starting location of the core directory
.ZIP file comment length 2 bytes Note the length of the
.ZIP file comment (variable size) The comment

This is basically a generic Zip package structure. Here’s a look at Android’s signature mechanism.

3.2 V1 signature

The mechanism of V1 signature is mainly in the three files in the meta-INF directory, manifest. MF, cert. SF, cert. RSA, which are the products of V1 signature. Manifest.mf holds a summary of all files, which reads as follows:

Manifest-version: 1.0 built-in: Generated- By -ADT Created-By: Android Gradle 3.4.0 Name: AndroidManifest.xml SHA-256-Digest: QxJh66y6ssDSNFgZSlf5jIWXfRdWnqL1c3BSwSDUYLQ= Name: META-INF/android.arch.core_runtime.version SHA-256-Digest: zFL2eISLgUNzdXtGA4O/YZYOSUPCA3Na3eCjULPlCYk=Copy the code

Cert.sf saves a summary of each message in manifest.mf, which reads in part as follows:

Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA-256-Digest-Manifest: j8YGFgHsujCHud09pT6Igh21XQKSnG+Gqy8VUE55u+g=
X-Android-APK-Signed: 2

Name: AndroidManifest.xml
SHA-256-Digest: qLofC3g32qJ5LmbjO/qeccx2Ie/PPpWSEPBIUPrlKlY=

Name: META-INF/android.arch.core_runtime.version
SHA-256-Digest: I65bgli5vdqHKel7MD74YlSuuyCR/5NDrXr2kf5FigA=
Copy the code

Cert. RSA contains the signature of the cert. SF file and the certificate used for the signature.

The main process of APK signature is as follows:

To calculate the file summary in APK, save the base64 encoding of the summary into the manifest.mf file -> Calculate the summary of the manifest.mf file, Save its base64 encoding to cert. SF file -> calculate the summary of each data block in the manifest.mf file, save its base64 encoding to cert. SF file -> calculate the summary of cert. SF file, Calculate digital signature from developer private key -> Save digital signature and developer public key to cert. RSA fileCopy the code

During APK verification, the main process is as follows:

Decrypt digital certificate in cert. RSA and sign cert. SF by CA certificate -> verify whether cert. SF file has been modified by signature -> Verify whether manifest. MF file has been modified by cert. SF -> Verify by cert. SF Whether the data item in the manifest. MF file has been modified -> Check whether the file in the APK has been modified with the manifest. MF fileCopy the code

We talked about Zip file structure above, through the above verification process, we can find that in the APK verification process, only the content of the data area is verified, the remaining two parts are not processed. Therefore, if the remaining two parts are modified, the signature verification process will not be found, and the EOCD comment field is a good choice for writing information. Therefore, channel information can be written into the annotation field of EOCD to achieve the purpose of entering channel information. That’s what Tencent VasDolly does. In addition, we can find that the APK signature verification process does not carry the signature and verification of the files in the meta-INF folder. Therefore, we can add an empty file in the meta-INF folder to carry channel information. That’s what Meituan does.

3.3 V2 signature

Everything was fine with the above Scheme, until Android 7.0 introduced a new Signature Scheme, APK Signature Scheme V2, which made the multichannel Signature Scheme invalid on V1. Let’s first look at how V2 signatures work. The APK Signing Block is added between the data area and the core directory area to record the Signing information. The structure before and after the signature is as follows.

The APK Signing Block format is as follows:

description length content
size of block 8 bytes The length of the signature block
ID-value A series of id-values are saved
size of block 8 bytes The length of the signature block is the same as the first field
magic 16 bytes Signature block marker, magic number

The APK Signing Block is used to sign the other three modules. The Signing information is stored in the value corresponding to the ID 0x7109871A in the id-value. The following logic is used to verify signatures.

Check whether the signature block V2 is contained. If the signature block V2 is contained, use the V2 signature block for authentication. If the signature block V2 is not contained, use the V1 signature block for authentication. Therefore, the method of adding EOCD annotations and meta-INF empty files in V1 scheme is invalid when V2 signature is used for validation. Looking at this, we can see that the V2 signature validates the other three module data, but not the APK Signing Block itself, where the id-value is a set of data, so we can add an ID-value that contains channel information here. This is how multi-channel packages are generated under V2 signatures. For detailed code of the above principle, you can see github.com/Tencent/Vas… And github.com/Meituan-Dia…

3.4 the V3 signature

A new signature method V3 was introduced in Android 9.0. To solve the problem of signature expiration. V3 Signing adds a signature Block to V2’s APK Signing Block, which stores the supported SDK version and key flow structure. Since no major changes have been made to the V2 structure, there will be no impact on the multi-channel packaging scheme. For more detailed information about V3 signatures, see the official documentation.

Other packing methods

4.1 Multiple APKs

The APK structure and packaging mentioned above includes all resources, including adaptation to different resolutions, adaptation to different apis, etc., but in fact, on each user device, only one of these resources will be used, and the rest of the resources will only occupy the package size. So Google also provides other ways to reduce package sizes by different resolutions, different ABI’s, and so on. After Android L, Android supports Multiple APKs, which simply means that the same application can be packaged into Multiple APKs corresponding to different devices with different resolutions, CPU architectures and API levels. Then install different APKS on different devices to reduce the volume. The documentation provides a detailed explanation, so let’s take a look at what APK looks like by configuring different resolutions. Let’s start by adding the following configuration in build.gradle.

android {
  ...
  splits {

    // Configures multiple APKs based on screen density.
    density {

      // Configures multiple APKs based on screen density.
      enable true

      // Specifies a list of screen densities Gradle should not create multiple APKs for.
      exclude "ldpi"."xxhdpi"."xxxhdpi"

      // Specifies a list of compatible screen size settings for the manifest.
      compatibleScreens 'small'.'normal'.'large'.'xlarge'}}}Copy the code

The meaning is clear enough, so I won’t explain it any more. We then compile and see that instead of one APK, four apKs are generated.

Simpleapk-universal-debug. apk is a complete APK containing all resources. The other three APKs were generated at different resolutions. It contains only the resources required for the corresponding resolution. This reduces the package size. We can see the difference by comparing the two APKs.

4.2 the Split APKs

Android L later provides Split APKs multi-APK construction mechanism, which can Split an APK into multiple APKs based on CPU architecture and screen density. When downloading an application, the corresponding APK will be installed according to the current device configuration. When Split APKs are packaged, one Base APK and multiple Split APKs are generated. Base APK contains generic resources, while Split APK is user device-specific. The user installs Base APK and its device’s corresponding Split APK, reducing the package size of other useless resources. We don’t generate Split APKs in normal development, but we can open Instant Run to compile the app. Instant Run now runs on Android 5.0 devices using Split APKs. Use Instant after the Run, in the build/intermediates/split – apk/debug/slices/below, you can see the generated split APKs, we choose one of them, in the manifest file, Defines the split tag, which represents the current Split APK number.

In the build/intermediates/instant – run – apk/debug/below, is the Base apk that contains some common resources.

Adb shell PM install-create adb shell PM install-write adb shell PM install-write Adb shell PM install-commit For details, see here.

4.3 App Bundle

App Bundle is a new dynamic framework for apps introduced at Google I/O 2018. It is a dynamic loading method based on the principle of Split APKs. The comparison with the traditional installation mode is as follows.

The App Bundle depends on the.aab file. The structure of an AAB is as follows.

Is general base/resources, is based on certain feature1 feature2 / screen density, distinguish the specific resources, including CPU architecture.

Let’s take a look at App Bundle usage using Simpleapk Demo. To use the App Bundle, you need to create a Base Module and feature Module. Our Simpleapk is the Base Module and then creates a feature module. Note that the feature Module needs to select Dynamic Feature Module.

dependencies {
    implementation fileTree(dir: 'libs'.include: ['*.jar'])
    implementation project(':simpleapk')}Copy the code

Then dynamicFeatures is configured in the base module.

android {
    bundle {
        // ...
    }
    dynamicFeatures = [":dynamic_feature"]}Copy the code

We bundleDebug command in the project, you can see, in the build/outputs/bunudle/debug directory are created XXX. Aab file, Google will Play according to XXX. Aab generate required for users to install the APK. Aab is also a zip file. The contents of the bag are as follows.

We can generate APK from.aab files using bundletool provided by Google. Execute the following command.

Java jar bundletool - all - 0.10.2. Jar build - apks - bundle = build/outputs/bundle/debug/simpleapk aab -- the output = bundle. ApksCopy the code

The generated bundle.apks file is also a zip file, so let’s unzip it.

The bundle.apks contains two directories, the Base APK and Split APK and the other one standalones/, which have the complete APK generated according to the different screen densities. Used to install App Bundles on devices that do not support them.

Of course, the App Bundle is completely dependent on Google Play, so it is still not available in China. But by understanding how this works, we can also see how Google reduces package sizes.

conclusion

The resources

Zip file format

Pkware.cachefly.net/webdocs/APP… Blog.csdn.net/a200710716/…

The signature

Source.android.com/security/ap… Source.android.com/security/ap… zhuanlan.zhihu.com/p/26674427

Android Build Tools

Tech.meituan.com/2014/06/13/… Tech.meituan.com/2017/01/13/… Developer.android.com/studio/comm…

Multiple APKs

Developer.android.com/studio/buil…

Split APKs

www.infoq.cn/article/2Mg… Blog.csdn.net/ximsfei/art…

App Bundle

Developer.android.com/guide/app-b… Developer.android.com/studio/comm…

About me