從命令行構(gòu)建 Android 應(yīng)用

我們已經(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)行一下試試吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末术奖,一起剝皮案震驚了整個(gè)濱河市礁遵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌采记,老刑警劉巖佣耐,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異唧龄,居然都是意外死亡兼砖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門既棺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讽挟,“玉大人,你說我怎么就攤上這事丸冕〉⒚罚” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵胖烛,是天一觀的道長(zhǎng)眼姐。 經(jīng)常有香客問我诅迷,道長(zhǎng),這世上最難降的妖魔是什么众旗? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任罢杉,我火速辦了婚禮,結(jié)果婚禮上贡歧,老公的妹妹穿的比我還像新娘滩租。我一直安慰自己,他們只是感情好艘款,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布持际。 她就那樣靜靜地躺著,像睡著了一般哗咆。 火紅的嫁衣襯著肌膚如雪蜘欲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天晌柬,我揣著相機(jī)與錄音姥份,去河邊找鬼。 笑死年碘,一個(gè)胖子當(dāng)著我的面吹牛澈歉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屿衅,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼埃难,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了涤久?” 一聲冷哼從身側(cè)響起涡尘,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎响迂,沒想到半個(gè)月后考抄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔗彤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年川梅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片然遏。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贫途,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出待侵,到底是詐尸還是另有隱情潮饱,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布诫给,位于F島的核電站香拉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏中狂。R本人自食惡果不足惜凫碌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胃榕。 院中可真熱鬧盛险,春花似錦、人聲如沸勋又。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽楔壤。三九已至鹤啡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹲嚣,已是汗流浹背递瑰。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隙畜,地道東北人抖部。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像议惰,于是被迫代替她去往敵國(guó)和親慎颗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容