在成为一个Android Developer后,其实一直都很好奇,一个.apk文件是怎么产生的,它是怎么从一堆.java、.xml等文件变成一个.apk的呢,随着知识体系的完善,这个问题貌似越来越清楚,但是一直未曾认认真真的梳理过一遍流程,恰好最近碰到相关问题,就借此机会总结一下,题其衣裳,以记其事~
环境准备
- Mac OS
- Java
- Android SDK
准备项目
创建工程
直接使用Android Studio创建一个新的工程,比如:HelloAPK,这样创建出来的工程应该包含如下内容:
[min@TATEMIN-MB0:] HelloAPK $ ls -al
total 72
drwxr-xr-x 14 min staff 448 5 21 10:04 .
drwxr-xr-x 33 min staff 1056 7 10 10:22 ..
-rw-r--r-- 1 min staff 208 5 21 10:04 .gitignore
drwxr-xr-x 5 min staff 160 5 21 10:04 .gradle
drwxr-xr-x 10 min staff 320 5 21 13:53 .idea
-rw-r--r-- 1 min staff 862 5 21 10:04 HelloAPK.iml
drwxr-xr-x 9 min staff 288 5 21 10:06 app
-rw-r--r-- 1 min staff 564 5 21 10:04 build.gradle
drwxr-xr-x 3 min staff 96 5 21 10:04 gradle
-rw-r--r-- 1 min staff 1073 5 21 10:04 gradle.properties
-rwxr--r-- 1 min staff 5296 5 21 10:04 gradlew
-rw-r--r-- 1 min staff 2260 5 21 10:04 gradlew.bat
-rw-r--r-- 1 min staff 432 5 21 10:04 local.properties
-rw-r--r-- 1 min staff 43 5 21 10:04 settings.gradle
但是实际上决定APK的生成主要依赖于目录app/src/main
,所以此目录就是我们编译APK的workspace。
修改工程
因为此次编译仅是向大家解释Android APK的生成过程,为了尽可能的方便,我们需要删除工程中的一些androidx的非Frameword的依赖,具体细节如下:
资源文件
-
删除
res/values/styles.xml
; -
修改
res/layout/activity_main.xml
文件:
代码文件
- 修改
java/com/min/helloapk/MainActivity.java
文件
Manifest
- 修改
AndroidManifest.xml
文件
编译项目
-
生成签名
[min@TATEMIN-MB0:] main $ keytool -genkey -alias android.keystore -keyalg RSA -validity 1000 -keystore android.keystore
-
查看签名
[min@TATEMIN-MB0:] main $ keytool -list -v -keystore android.keystore
-
生成R.java
[min@TATEMIN-MB0:] main $ aapt package -v -f -m -S res/ -J java/ -M AndroidManifest.xml -I /Users/min/Library/Android/sdk/platforms/android-26/android.jar
-
编译代码
[min@TATEMIN-MB0:] main $ mkdir gen # 创建一个中间文件目录 [min@TATEMIN-MB0:] main $ javac java/com/min/helloapk/*.java -verbose -classpath $ANDROID_HOME/platforms/android-26/android.jar -d gen
此时会在bin目录下生成如下文件:
[min@TATEMIN-MB0:] main $ tree gen/ gen/ └── com └── min └── helloapk ├── MainActivity.class ├── R$attr.class ├── R$color.class ├── R$drawable.class ├── R$layout.class ├── R$mipmap.class ├── R$string.class └── R.class
-
创建Dex文件
[min@TATEMIN-MB0:] main $ mkdir bin [min@TATEMIN-MB0:] main $ dx --dex --verbose --output=bin/classes.dex gen
此时会在bin目录下生成如下文件:
[min@TATEMIN-MB0:] main $ tree bin/ bin/ └── classes.dex
-
创建APK文件
[min@TATEMIN-MB0:] main $ aapt package -v -f -M AndroidManifest.xml -S res -I $ANDROID_HOME/platforms/android-26/android.jar -F bin/HelloAPK.unsigned.apk bin
此时会在bin目录下文件列表如下:
[min@TATEMIN-MB0:] main $ tree bin/ bin/ ├── HelloAPK.unsigned.apk └── classes.dex
-
APK签名
[min@TATEMIN-MB0:] main $ jarsigner -verbose -keystore android.keystore -signedjar bin/HelloAPK.signed.apk bin/HelloAPK.unsigned.apk android.keystore
-
zipalign APK
zipalign -v -f 4 bin/HelloAPK.signed.apk bin/HelloAPK.apk
-
安装APK
[min@TATEMIN-MB0:] main $ adb install bin/HelloAPK.apk
-
运行APK
附录
如下是整个编译工程的Workspace:
min@TATEMIN-MB0:] main $ tree
.
├── AndroidManifest.xml
├── android.keystore
├── bin
│ ├── HelloAPK.apk
│ ├── HelloAPK.signed.apk
│ ├── HelloAPK.unsigned.apk
│ └── classes.dex
├── gen
│ └── com
│ └── min
│ └── helloapk
│ ├── MainActivity.class
│ ├── R$attr.class
│ ├── R$color.class
│ ├── R$drawable.class
│ ├── R$layout.class
│ ├── R$mipmap.class
│ ├── R$string.class
│ └── R.class
├── java
│ └── com
│ └── min
│ └── helloapk
│ ├── MainActivity.java
│ └── R.java
└── res
├── drawable
│ └── ic_launcher_background.xml
├── drawable-v24
│ └── ic_launcher_foreground.xml
├── layout
│ └── activity_main.xml
├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
└── values
├── colors.xml
└── strings.xml
20 directories, 33 files