我們已經(jīng)習(xí)慣了使用集成開發(fā)環(huán)境(比如 Eclipse 與 Android Studio)來自動(dòng)編譯 Android 應(yīng)用灯谣。掌握如何使用命令行工具手動(dòng)構(gòu)建 Android 應(yīng)用也是有益的:其一提佣,我們將對(duì)整個(gè)過程有更加深刻的理解空镜,知曉從零到一之間究竟發(fā)生了哪些事情丰嘉;其二洁灵,有些情況下我們還真不得不這么做房铭。
準(zhǔn)備工作
我們假設(shè)你已經(jīng)安裝了 JDK:
> java -version
java version "1.7.0_79"
> javac
Usage: javac <options> <source files>
where possible options include:...
我們假設(shè)你已經(jīng)安裝了 Android SDK 并且設(shè)置了正確的環(huán)境變量:
> echo $ANDROID_HOME
/Applications/adt-bundle-mac/sdk
> aapt
Android Asset Packaging Tool...
搭建項(xiàng)目結(jié)構(gòu)
> mkdir -p TestApp/{src/fm/cocoa/testapp,res/{drawable,layout,values},assets,libs,obj,bin,gen,docs}
項(xiàng)目的目錄結(jié)構(gòu)如下:
TestApp
├── assets
├── bin
├── docs
├── gen
├── libs
├── obj
├── res
│ ├── drawable
│ ├── layout
│ └── values
└── src
└── fm
└── cocoa
└── testapp
關(guān)于每個(gè)目錄的意義和作用,請(qǐng)參考 Android 官網(wǎng)文檔: Managing Projects Overview瑟匆。
創(chuàng)建 AndroidManifest.xml
在工程的根目錄下手寫一個(gè) AndroidManifest.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fm.cocoa.testapp"
android:versionCode="0"
android:versionName="1.0">
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:icon="@drawable/logo"
android:label="@string/app_name">
<activity
android:name="fm.cocoa.testapp.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
這里闽坡,我們把所支持的最低 Android SDK 版本設(shè)置為 8,把目標(biāo) SDK 版本設(shè)置為 21愁溜。
編寫視圖代碼
我們?cè)?AndroidManifest.xml 文件中聲明了一個(gè) Activity,下面來實(shí)現(xiàn)它:
package fm.cocoa.testapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
String helloWorld = getResources().getString(R.string.hello_world);
textView.setText(helloWorld);
setContentView(textView);
}
}
創(chuàng)建 R.java
前面的兩個(gè)文件中涉及到兩個(gè)字符串常量和一個(gè)圖片資源:
-
@drawable/logo
(AndroidManifest.xml) -
@string/app_name
(AndriodManifest.xml) -
R.string.hello_world
(MainActivity.java)
我們把圖片 logo.png 放到 res/drawable
目錄下外厂,把兩個(gè)字符串常量寫到 res/values/strings.xml
中:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Build Android App from CMD</string>
<string name="hello_world">Hello, world!</string>
</resources>
然后就可以開心地生成 R.java 文件了:
> aapt package -v -f -S res -J gen -M AndroidManifest.xml -I $ANDROID_HOME/platforms/android-21/android.jar
>
> cat gen/R.java
/* AUTO-GENERATED FILE. DO NOT MODIFY.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package fm.cocoa.testapp;
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int logo=0x7f020000;
}
public static final class string {
public static final int app_name=0x7f030000;
public static final int hello_world=0x7f030001;
}
}
關(guān)于 aapt 的詳細(xì)介紹冕象,請(qǐng)參考 這里。我們簡(jiǎn)單介紹一下所用到的幾個(gè)命令行選項(xiàng)的意義:
-
aapt package
表示打包 Android 資源 -
-v
表示打印較為詳細(xì)的日志 -
-f
表示強(qiáng)制覆蓋已有文件 -
-S
指定了資源目錄 -
-J
指定了 R.java 的輸出目錄 -
-M
指定了 AndroidManifest.xml 文件 -
-I
指定了項(xiàng)目所依賴的庫
編譯代碼
既然源文件是 java 代碼熬尺,編譯的時(shí)候必然要用到 javac:
> javac -verbose -d obj -classpath $ANDROID_HOME/platforms/android-21/android.jar -sourcepath src src/fm/cocoa/testapp/MainActivity.java gen/R.java
所用到的命令行選項(xiàng)的意義:
-
-d
指定了所生成的字節(jié)碼的輸出目錄 -
-classpath
指定了依賴庫的路徑 -
-sourcepath
指定了源文件目錄
查看一下編譯的結(jié)果:
> tree obj/
obj/
└── fm
└── cocoa
└── testapp
├── MainActivity.class
├── R$attr.class
├── R$drawable.class
├── R$string.class
└── R.class
創(chuàng)建 DEX 文件
Android 系統(tǒng)使用 Dalvik Virtual Machine 來運(yùn)行字節(jié)碼旁振。Dalvik 并不認(rèn)識(shí) .class 格式的字節(jié)碼,因此钦睡,需要把 .class 文件翻譯成 Dalvik 可以識(shí)別的字節(jié)碼格式:
> dx --dex --verbose --output=bin/classes.dex obj libs
所用到的命令行選項(xiàng)的意義:
-
--dex
表示生成 DEX(Dalvik EXecutable)格式的字節(jié)碼文件 -
--verbose
表示打印較為詳細(xì)的日志 -
--output
指定了輸出文件的路徑
查看一下結(jié)果:
> tree bin/
bin/
└── classes.dex
生成 APK 文件
一路過關(guān)斬將墓律,終于到了生成 APK (Android Package Format)文件的時(shí)刻了膀估。
這一步還是使用 aapt:
> aapt package -v -f -M AndroidManifest.xml -S res -I $ANDROID_HOME/platforms/android-21/android.jar -F bin/TestApp-unaligned.unsigned.apk bin
我們告訴 aapt 把最終生成的 APK 文件輸出為 bin/TestApp-unaligned.unsigned.apk。現(xiàn)在耻讽,bin 目錄下已經(jīng)有了我們想要的文件:
> tree bin/
bin/
├── TestApp-unaligned.unsigned.apk
└── classes.dex
Perfect察纯!
給 APK 簽名
想要在設(shè)備上運(yùn)行,APK 文件必須得有簽名才行针肥。
首先饼记,你需要有一個(gè) keystore 文件。
如果沒有的話也不要緊慰枕,我們可以馬上創(chuàng)建一個(gè):
> mkdir keystores
> keytool -v -genkeypair -validity 10000 -dname "CN=company_name,OU=organisational_unit,O=organisation,L=location,S=state,C=country_code" -keystore keystores/testapp.keystore -storepass store_password -keypass key_password -alias key_alias -keyalg RSA
然后具则,我們用 jarsigner 給 APK 打簽名:
> jarsigner -verbose -keystore keystores/testapp.keystore -keypass key_password -signedjar bin/TestApp-unaligned.signed.apk bin/TestApp-unaligned.unsigned.apk key_alias
我們并不打算詳述 Android 應(yīng)用的簽名機(jī)制,現(xiàn)在請(qǐng)看簽名的結(jié)果:
> tree bin/
bin/
├── TestApp-unaligned.signed.apk
├── TestApp-unaligned.unsigned.apk
└── classes.dex
ZIP 對(duì)齊
這一步是可選的具帮。
事實(shí)上博肋,所謂的 APK 包就是一種特殊的 ZIP 包(你當(dāng)然可以使用 unzip 命令把它解開),我們通過上面的步驟所得到的包是沒有經(jīng)過 ZIP 對(duì)齊的蜂厅。一個(gè)命令就可以搞定 ZIP 對(duì)齊:
> zipalign -v -f 4 bin/TestApp-unaligned.signed.apk bin/TestApp.apk
所用到的命令行選項(xiàng)的意義:
-
-v
表示打印較為詳細(xì)的日志 -
-f
表示強(qiáng)制覆蓋已有文件 -
4
指定了對(duì)齊的基準(zhǔn)字節(jié)
> tree bin/
bin/
├── TestApp-unaligned.signed.apk
├── TestApp-unaligned.unsigned.apk
├── TestApp.apk
└── classes.dex
其中的 TestApp.apk 正是我們想要的最終的 Android 應(yīng)用包匪凡,大功告成!
運(yùn)行
現(xiàn)在請(qǐng)打開 Android 手機(jī)(也可以用模擬器葛峻,我們推薦 Genymotion)锹雏,運(yùn)行一下試試吧。