用Android SDK Build Tools手動構建APK

cdj發表於2019-04-30

我們平時都是用Android Studio進行Android應用的開發,Android Studio構建APK是通過呼叫Gradle指令碼實現的,而Gradle指令碼最終是通過呼叫Android SDK Build Tools裡的各種命令列工具實現的。

下面嘗試直接用Build Tools構建一個極簡的Hello World APK,瞭解一下這個過程和各個工具的基本用法。

整個構建過程大致分為以下幾步:

  1. 用aapt2編譯資原始檔,生成中間二進位制檔案
  2. 用aapt2連結合併中間檔案,生成不包含程式碼的APK,並生成R.java
  3. 用javac編譯java原始檔,得到.class java位元組碼檔案
  4. 用d8將.class編譯成DEX位元組碼檔案
  5. 將DEX檔案匯入APK中
  6. 對APK進行簽名

建立專案原始檔

專案的目錄結構及檔案原始碼如下:

D:\helloworld>tree /F
│ AndroidManifest.xml
│
├─compiled
│
├─java
│  └─com
│      └─cdjtest
│          └─helloworld
│                  MainActivity.java
│
└─res
    ├─drawable
    │      ic_launcher.png
    │
    ├─layout
    │      activity_main.xml
    │
    └─values
           strings.xml
複製程式碼

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cdjtest.helloworld">
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
複製程式碼

MainActivity.java

package com.cdjtest.helloworld;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
複製程式碼

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:text="Hello World!"/>
複製程式碼

strings.xml

<resources>
    <string name="app_name">helloworld</string>
</resources>
複製程式碼

用aapt2編譯資原始檔

先設定一下環境變數,將Build Tools 28.0.3的路徑加到PATH中,方便呼叫

D:\helloworld>set PATH=%PATH%;$ANDROID_HOME%\build-tools\28.0.3\
複製程式碼

編譯res目錄下的3個資原始檔,生成.flat中間二進位制檔案

D:\helloworld>aapt2 compile res\values\strings.xml -o compiled\
D:\helloworld>aapt2 compile res\layout\activity_main.xml -o compiled\
D:\helloworld>aapt2 compile res\drawable\ic_launcher.png -o compiled\
複製程式碼

連結.flat檔案,生成helloworld.unsigned.apk(還未包含DEX位元組碼),--java java引數指定在java目錄生成R.java檔案,和MainActivity.java在同一目錄

D:\helloworld>aapt2 link -o helloworld.unsigned.apk ^
    -I %ANDROID_HOME%\platforms\android-28\android.jar ^
    compiled\values_strings.arsc.flat ^
    compiled\layout_activity_main.xml.flat ^
    compiled\drawable_ic_launcher.png.flat ^
    --manifest AndroidManifest.xml --java java\
複製程式碼

用javac和d8編譯原始碼

用javac將MainActivity.java和R.java編譯成.class檔案

D:\helloworld>javac java\com\cdjtest\helloworld\*.java -classpath %ANDROID_HOME%\platforms\android-28\android.jar
複製程式碼

用d8將.class編譯成classes.dex,(d8和dx的對比可參考Jake大神的這篇文章

D:\helloworld>d8 --lib %ANDROID_HOME%\platforms\android-28\android.jar --release --output . java\com\cdjtest\helloworld\*.class
複製程式碼

classes.dex匯入APK中

D:\helloworld>aapt add helloworld.unsigned.apk classes.dex
複製程式碼

APK簽名

用zipalign優化APK,主要作用是記憶體對齊,提高執行時讀取資源的效率

D:\helloworld>zipalign -p 4 helloworld.unsigned.apk helloworld.unsigned.aligned.apk
複製程式碼

用JDK自帶的keytool工具生成keystore檔案my-release-key.jks

D:\helloworld>keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias
複製程式碼

用apksigner和my-release-key.jks簽名APK,生成helloworld-release.apk

D:\helloworld>apksigner sign --ks my-release-key.jks --out helloworld-release.apk helloworld.unsigned.aligned.apk
Keystore password for signer #1:
複製程式碼

最終的目錄結構:

D:\helloworld>tree /F
│  AndroidManifest.xml
│  classes.dex
│  helloworld-release.apk
│  helloworld.unsigned.aligned.apk
│  helloworld.unsigned.apk
│  my-release-key.jks
│
├─compiled
│      drawable_ic_launcher.png.flat
│      layout_activity_main.xml.flat
│      values_strings.arsc.flat
│
├─java
│  └─com
│      └─cdjtest
│          └─helloworld
│                  MainActivity.class
│                  MainActivity.java
│                  R$drawable.class
│                  R$layout.class
│                  R$string.class
│                  R.class
│                  R.java
│
└─res
    ├─drawable
    │      ic_launcher.png
    │
    ├─layout
    │      activity_main.xml
    │
    └─values
            strings.xml
複製程式碼

安裝APK

D:\helloworld>adb install helloworld-release.apk
Success
複製程式碼

成功執行螢幕中間可見"Hello World!"。

參考資料:

相關文章