天天看點

App自動化之使用Ant編譯項目多管道打包

随着工程越來越複雜,項目越來越多,以及平台的遷移(我最近就遷了2回),還有各大市場的釋出,自動化編譯android項目的需求越來越強烈,後面如果考慮做持續內建的話,會更加強烈。

經過不斷的嘗試,在ubuntu環境下,以花界為例,我将一步一步示範如何使用指令行,使用ant編譯android項目,打包多管道APK。

要點:

(1). 編譯android的指令使用

(2). ant基本應用

(3). 多項目如何編譯(包含android library)

(4). 如何多管道打包

ps:我将以最原始的方式來實作,而不是使用android自帶的ant編譯方式,并盡量詳細解釋,這樣有益于我們徹底搞懂android打包的基本原理。

1. Android編譯打包的整體過程

使用ant,ant的參考文檔:http://ant.apache.org/manual/index.html

首先,假設現在已經有這樣的一個項目(多工程的,簡單的單工程就更簡單了):

world

├── baseworld //android library,基礎類庫,共享于其他主應用

├── floworld //android project,花界應用

├── healthworld //android project,健康視線應用

├── speciality //android project,其它應用

├── starworld //android project,其它應用

├── build.xml //ant編譯腳本,可用于整個項目的編譯,也可隻編譯某個工程

├── code_checks.xml

├── kaiyuanxiangmu_world.keystore //密鑰

└── README.md

一個大的項目world,下面有1個基礎Android Library和4個Android Project。我們要做的就是編譯這4個人project成對應的一系列各市場APK。

那麼我們在來看看baseworld和floworld的工程結構:

Android Library,baseworld:

baseworld

├── assets //assets目錄,其中檔案可能會被主應用覆寫

├── libs //存放第三方jar庫

├── res //類庫資源,其中檔案可能會被主應用覆寫

├── src //源碼,可直接供主應用使用

├── AndroidManifest.xml

├── lint.xml

├── proguard.cfg

├── project.properties

└── README.md

和Android Project,floworld:

floworld/

├── assets //assets目錄,主應用優先級高

├── build

├── data

├── libs //存放第三方jar庫

├── res //主應用資源,主應用優先級高

├── src //源碼,可直接供主應用使用

├── AndroidManifest.xml

├── build.xml //ant編譯腳本,可用于整個項目的編譯,也可隻編譯某個工程

├── default.properties

├── lint.xml

├── proguard.cfg

├── project.properties

└── README.md

結構已經出來了,那麼android打包主要是在做什麼?

說白了,先編譯java成class,再把class和jar轉化成dex,接着打包aaset和res等資源檔案為res.zip(以res.zip示例),再把dex和res.zip合并為一個未簽名apk,再對它簽名,最終是一個帶簽名的apk檔案。

當然這麼說忽略了很多細節。

下面我把這些步驟用一句話分别列舉如下,腦子裡先有一個整體的流程,後續再結合ant詳細展開:

(1). 生成用于主應用的R.java;

(2). 生成用于庫應用的R.java(如果有庫應用);

(3). 編譯所有java檔案為class檔案;

(4). 打包class檔案和jar包為classes.dex;

(5). 打包assets和res資源為資源壓縮包(如res.zip,名字可以自己定義);

(6). 組合classes.dex和res.zip生成未簽名的APK;

(7). 生成有簽名的APK;

針對多項目同步釋出和多管道打包問題,我們需要額外增加三個處理:

(1). 各個工程下建立一個build.xml,然後在整個項目的根目錄下建立一個build.xml,用于統一編譯各個工程的;

(2). 各個工程的build.xml,通過傳入市場ID和應用Version參數生成對應的版本

(3). 針對(1),(2)問題,建立一個批處理支援一鍵生成所有版本

大概流程即是如此。

2. 建立各個工程的ant腳本檔案build.xml(位置:floworld/build.xml)

因為需要建立一些基本的檔案目錄和清理上次生成的檔案,是以我們簡單的定義一下幾個目标吧:init,main,clean。

代碼模闆如下:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

<

project

default

=

"main"

basedir

=

"."

>

<!-- 初始化:建立目錄,清理目錄等 -->

<

target

name

=

"init"

>

<

echo

>start initing ... </

echo

>

<!-- ... ... -->

<

echo

>finish initing. </

echo

>

</

target

>

<!-- 打包過程,預設值 -->

<

target

name

=

"main"

depends

=

"init"

>

</

target

>

<!-- 清理不需要的生成檔案等-->

<

target

name

=

"clean"

>

</

target

>

</

project

>

3. 初始化

在正式打包之前,有必要說明一下可能需要用到的初始化變量和操作。

前面已經講述了打包的大概流程,現在,第一, 打包需要你使用哪個版本android.jar; 第二, 生成的R檔案放到gen目錄下; 第三, 生成的classes檔案放到bin目錄下; 第四, 生成的打封包件放到out目錄下; 第五, 生成的各市場版本放到build目錄下。目錄完全可以自定義。

是以,如下的初始化必須先要做好,不然後面會提示找不到目錄:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

<

project

default

=

"main"

basedir

=

"."

>

<!-- 這個是android.jar路徑,具體情況具體配置 -->

<

property

name

=

"android-jar"

value

=

"/usr/lib/android-sdk/platforms/android-10/android.jar"

/>

<!-- 用于生成多管道版本的APK檔案名,提供了預設值,後面會講到 -->

<

property

name

=

"apk-name"

value

=

"product"

/>

<

property

name

=

"apk-version"

value

=

"latest"

/>

<

property

name

=

"apk-market"

value

=

"dev"

/>

<

target

name

=

"init"

>

<

echo

>start initing ... </

echo

>

<

mkdir

dir

=

"out"

/>

<

delete

>

<

fileset

dir

=

"out"

></

fileset

>

</

delete

>

<

mkdir

dir

=

"gen"

/>

<

delete

>

<

fileset

dir

=

"gen"

></

fileset

>

</

delete

>

<

mkdir

dir

=

"bin/classes"

/>

<

delete

>

<

fileset

dir

=

"bin/classes"

></

fileset

>

</

delete

>

<!-- ${apk-version}表示版本,後面會詳細講到 -->

<

mkdir

dir

=

"build/${apk-version}"

/>

<

echo

>finish initing. </

echo

>

</

target

>

... ...

</

project

>

4. 生成R.java

Android Library和Android Project應用的R.java是來自不同的package的。比如:

(1). baseworld中導入的包是import com.tianxia.lib.baseworld.R;

(2). floworld中導入的包是import com.tianxia.lib.baseworld.R;

但是他們最終是調用統一的資源,是以這兩個R.java檔案必須一緻。

下面是主應用的R.java的生成腳本:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

<

echo

>generating R.java for project to dir gen (using aapt) ... </

echo

>

<

exec

executable

=

"aapt"

>

<

arg

value

=

"package"

/>

<!-- package表示打包-->

<

arg

value

=

"-m"

/>

<!--m,J,gen表示建立包名的目錄和R.java到gen目錄下 -->

<

arg

value

=

"-J"

/>

<

arg

value

=

"gen"

/>

<

arg

value

=

"-M"

/>

<!-- M指定AndroidManifest.xml檔案-->

<

arg

value

=

"AndroidManifest.xml"

/>

<

arg

value

=

"-S"

/>

<!-- S指定res目錄,生成對應的ID,可多個-->

<

arg

value

=

"res"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"../baseworld/res"

/>

<!-- 注意點:同時需要調用Library的res-->

<

arg

value

=

"-I"

/>

<!-- I指定android包的位置-->

<

arg

value

=

"${android-jar}"

/>

<

arg

value

=

"--auto-add-overlay"

/>

<!-- 這個重要,覆寫資源,不然報錯-->

</

exec

>

注意res和../baseworld/res兩個順序不能搞反,寫在前面具有高優先級,我們當然優先使用主應用的資源了,這樣就能正确覆寫庫應用的資源,實作重寫。

庫應用的R.java的生成腳本差不多,差別是指定庫應用的AndroidManifest.xml,以用于生成的是不同的包和目錄。

另外,aapt的使用中特别說明了,為了庫應用的資源更好的可重用,庫應用生成的R.java字段不需要修飾為final,加上參數--non-constant-id即可。

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

<

echo

>generating R.java for library to dir gen (using aapt) ... </

echo

>

<

exec

executable

=

"aapt"

>

<

arg

value

=

"package"

/>

<

arg

value

=

"-m"

/>

<

arg

value

=

"--non-constant-id"

/>

<!-- 加了這個參數-->

<

arg

value

=

"--auto-add-overlay"

/>

<

arg

value

=

"-J"

/>

<

arg

value

=

"gen"

/>

<

arg

value

=

"-M"

/>

<

arg

value

=

"../baseworld/AndroidManifest.xml"

/>

<!-- 庫應用的manifest-->

<

arg

value

=

"-S"

/>

<

arg

value

=

"res"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"../baseworld/res"

/>

<

arg

value

=

"-I"

/>

<

arg

value

=

"${android-jar}"

/>

</

exec

>

這樣的話就可以生成2個正确的R.java檔案了(如果你引用了兩個庫,則需要生成3個R.java,以此類推)。

結果如下:

?

1 2 3 4 5 6 7 8 9

gen

└── com

└── tianxia

├── app

│ └── floworld

│ └── R.java

└── lib

└── baseworld

└── R.java

5. 編譯java檔案為class檔案

使用javac指令把src目錄,baseworld/src目錄,gen/*/R.java這些java編譯成class檔案:

指令原型是:

?

1 2

//示例

javac -bootclasspath <android.jar> -s <src> -s <src> -s <gen> -d bin/classes *.jar

轉化成ant腳本為:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

<!-- 第三方jar包需要引用,用于輔助編譯 -->

<

path

id

=

"project.libs"

>

<

fileset

dir

=

"libs"

>

<

include

name

=

"*.jar"

/>

</

fileset

>

</

path

>

<

echo

>compiling java files to class files (include R.java, library and the third-party jars) ... </

echo

>

<!-- 生成的class檔案全部儲存到bin/classes目錄下 -->

<

javac

destdir

=

"bin/classes"

bootclasspath

=

"${android-jar}"

>

<

src

path

=

"../baseworld/src"

/>

<

src

path

=

"src"

/>

<

src

path

=

"gen"

/>

<

classpath

refid

=

"project.libs"

/>

</

javac

>

6. 打包class檔案為classes.dex

這步簡單,用dx指令把上步生成的classes和第三方jar包打包成一個classes.dex。

指令原型是:

?

1 2 3

//示例

//後面可以接任意個第三方jar路徑

dx --dex --output=out/classes.dex bin/classes libs/

1

.jar libs/

2

.jar

  轉化成ant腳本為:

?

1 2 3 4 5 6 7

<

echo

>packaging class files (include the third-party jars) to calsses.dex ... </

echo

>

<

exec

executable

=

"dx"

>

<

arg

value

=

"--dex"

/>

<

arg

value

=

"--output=out/classes.dex"

/>

<!-- 輸出 -->

<

arg

value

=

"bin/classes"

/>

<!-- classes檔案位置 -->

<

arg

value

=

"libs"

/>

<!-- 把libs下所有jar打包 -->

</

exec

>

7. 打包res,assets為資源壓縮包(暫且命名為res.zip)

還是使用aapt指令,如生成R.java最大的不同是參數-F,意思是生成res.zip檔案。

指令原型和ant腳本差不多:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

<

echo

>packaging resource (include res, assets, AndroidManifest.xml, etc.) to res.zip ... </

echo

>

<

exec

executable

=

"aapt"

>

<

arg

value

=

"package"

/>

<

arg

value

=

"-f"

/>

<!-- 資源覆寫重寫 -->

<

arg

value

=

"-M"

/>

<

arg

value

=

"AndroidManifest.xml"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"res"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"../baseworld/res"

/>

<

arg

value

=

"-A"

/>

<!-- 與R.java不同,需要asset目錄也打包 -->

<

arg

value

=

"assets"

/>

<

arg

value

=

"-I"

/>

<

arg

value

=

"${android-jar}"

/>

<

arg

value

=

"-F"

/>

<!-- 輸出資源壓縮包 -->

<

arg

value

=

"out/res.zip"

/>

<

arg

value

=

"--auto-add-overlay"

/>

</

exec

>

8. 使用apkbuilder指令組合classes.dex,res.zip和AndroidManifest.xml為未簽名的apk

apkbuilder指令能把class類,資源等檔案打包成一個未簽名的apk,原型指令和ant腳本類似:

?

1 2 3 4 5 6 7 8 9

<

echo

>building unsigned.apk ... </

echo

>

<

exec

executable

=

"apkbuilder"

>

<

arg

value

=

"out/unsigned.apk"

/>

<!-- 輸出 -->

<

arg

value

=

"-u"

/>

<!-- u指建立未簽名的包-->

<

arg

value

=

"-z"

/>

<!-- 資源壓縮包 -->

<

arg

value

=

"out/res.zip"

/>

<

arg

value

=

"-f"

/>

<!-- dex檔案 -->

<

arg

value

=

"out/classes.dex"

/>

</

exec

>

  這個指令比較簡單。

9. 簽名未簽名的apk

使用jarsigner指令對上步中産生的apk簽名。這是個傳統的java指令,非android專用。

原型指令和ant腳本差不多:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

<!-- 生成apk檔案到build目錄下 -->

<!-- 其中${apk-version/name/market}使用者多管道打包,後面會講到 -->

<

echo

>signing the unsigned apk to final product apk ... </

echo

>

<

exec

executable

=

"jarsigner"

>

<

arg

value

=

"-keystore"

/>

<

arg

value

=

"../xxx.keystore"

/>

<

arg

value

=

"-storepass"

/>

<

arg

value

=

"xxx"

/> <-- 驗證密鑰完整性的密碼,建立時建立的 -->

<

arg

value

=

"-keypass"

/>

<

arg

value

=

"xxx"

/> <-- 專用密鑰的密碼,就是key密碼 -->

<

arg

value

=

"-signedjar"

/>

<

arg

value

=

"build/${apk-version}/${apk-name}_${apk-version}_${apk-market}.apk"

/>

<!-- 輸出 -->

<

arg

value

=

"out/unsigned.apk"

/>

<!-- 未簽名的apk -->

<

arg

value

=

"xxx"

/>

<!-- 别名,建立時建立的 -->

</

exec

>

至此,完整具有打包功能了,最後的build.xml為:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134

<

project

default

=

"main"

basedir

=

"."

>

<

property

name

=

"apk-name"

value

=

"product"

/>

<

property

name

=

"apk-version"

value

=

"latest"

/>

<

property

name

=

"apk-market"

value

=

"dev"

/>

<

property

name

=

"android-jar"

value

=

"/usr/lib/android-sdk/platforms/android-10/android.jar"

/>

<

target

name

=

"init"

>

<

echo

>start initing ... </

echo

>

<

mkdir

dir

=

"out"

/>

<

delete

>

<

fileset

dir

=

"out"

></

fileset

>

</

delete

>

<

mkdir

dir

=

"gen"

/>

<

delete

>

<

fileset

dir

=

"gen"

></

fileset

>

</

delete

>

<

mkdir

dir

=

"bin/classes"

/>

<

delete

>

<

fileset

dir

=

"bin/classes"

></

fileset

>

</

delete

>

<

mkdir

dir

=

"build/${apk-version}"

/>

<

echo

>finish initing. </

echo

>

</

target

>

<

target

name

=

"main"

depends

=

"init"

>

<

echo

>generating R.java for project to dir gen (using aapt) ... </

echo

>

<

exec

executable

=

"aapt"

>

<

arg

value

=

"package"

/>

<

arg

value

=

"-m"

/>

<

arg

value

=

"-J"

/>

<

arg

value

=

"gen"

/>

<

arg

value

=

"-M"

/>

<

arg

value

=

"AndroidManifest.xml"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"res"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"../baseworld/res"

/>

<

arg

value

=

"-I"

/>

<

arg

value

=

"${android-jar}"

/>

<

arg

value

=

"--auto-add-overlay"

/>

</

exec

>

<

echo

>generating R.java for library to dir gen (using aapt) ... </

echo

>

<

exec

executable

=

"aapt"

>

<

arg

value

=

"package"

/>

<

arg

value

=

"-m"

/>

<

arg

value

=

"--non-constant-id"

/>

<

arg

value

=

"--auto-add-overlay"

/>

<

arg

value

=

"-J"

/>

<

arg

value

=

"gen"

/>

<

arg

value

=

"-M"

/>

<

arg

value

=

"../baseworld/AndroidManifest.xml"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"res"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"../baseworld/res"

/>

<

arg

value

=

"-I"

/>

<

arg

value

=

"${android-jar}"

/>

</

exec

>

<

path

id

=

"project.libs"

>

<

fileset

dir

=

"libs"

>

<

include

name

=

"*.jar"

/>

</

fileset

>

</

path

>

<

echo

>compiling java files to class files (include R.java, library and the third-party jars) ... </

echo

>

<

javac

destdir

=

"bin/classes"

bootclasspath

=

"${android-jar}"

>

<

src

path

=

"../baseworld/src"

/>

<

src

path

=

"src"

/>

<

src

path

=

"gen"

/>

<

classpath

refid

=

"project.libs"

/>

</

javac

>

<

echo

>packaging class files (include the third-party jars) to calsses.dex ... </

echo

>

<

apply

executable

=

"dx"

>

<

arg

value

=

"--dex"

/>

<

arg

value

=

"--output=out/classes.dex"

/>

<

arg

value

=

"bin/classes"

/>

<

arg

value

=

"libs"

/>

</

apply

>

<

echo

>packaging resource (include res, assets, AndroidManifest.xml, etc.) to res.zip ... </

echo

>

<

exec

executable

=

"aapt"

>

<

arg

value

=

"package"

/>

<

arg

value

=

"-f"

/>

<

arg

value

=

"-M"

/>

<

arg

value

=

"AndroidManifest.xml"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"res"

/>

<

arg

value

=

"-S"

/>

<

arg

value

=

"../baseworld/res"

/>

<

arg

value

=

"-A"

/>

<

arg

value

=

"assets"

/>

<

arg

value

=

"-I"

/>

<

arg

value

=

"${android-jar}"

/>

<

arg

value

=

"-F"

/>

<

arg

value

=

"out/res.zip"

/>

<

arg

value

=

"--auto-add-overlay"

/>

</

exec

>

<

echo

>building unsigned.apk ... </

echo

>

<

exec

executable

=

"apkbuilder"

>

<

arg

value

=

"out/unsigned.apk"

/>

<

arg

value

=

"-u"

/>

<

arg

value

=

"-z"

/>

<

arg

value

=

"out/res.zip"

/>

<

arg

value

=

"-f"

/>

<

arg

value

=

"out/classes.dex"

/>

</

exec

>

<

echo

>signing the unsigned apk to final product apk ... </

echo

>

<

exec

executable

=

"jarsigner"

>

<

arg

value

=

"-keystore"

/>

<

arg

value

=

"xxx.keystore"

/>

<

arg

value

=

"-storepass"

/>

<

arg

value

=

"xxxx"

/>

<

arg

value

=

"-keypass"

/>

<

arg

value

=

"xxx"

/>

<

arg

value

=

"-signedjar"

/>

<

arg

value

=

"build/${apk-version}/${apk-name}_${apk-version}_${apk-market}.apk"

/>

<

arg

value

=

"out/unsigned.apk"

/>

<

arg

value

=

"xxx"

/>

</

exec

>

<

echo

>done.</

echo

>

</

target

>

</

project

>

  在工程目錄下運作ant:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

$ant

Buildfile: build.xml

init:

[echo] start initing ...

[mkdir] Created dir: /home/openproject/world/floworld/build/latest

[echo] finish initing.

main:

[echo] generating R.java for project to dir gen (using aapt) ...

[echo] generating R.java for library to dir gen (using aapt) ...

[echo] compiling java files to class files (include R.java, library and the third-party jars) ...

[javac] Compiling 75 source files to /home/openproject/world/floworld/bin/classes

[javac] 注意:某些輸入檔案使用或覆寫了已過時的 API。

[javac] 注意:要了解詳細資訊,請使用 -Xlint:deprecation 重新編譯。

[echo] packaging class files (include the third-party jars) to calsses.dex ...

[echo] packaging resource (include res, assets, AndroidManifest.xml, etc.) to res.zip ...

[echo] building unsigned.apk ...

[exec]

[exec] THIS TOOL IS DEPRECATED. See --help for more information.

[exec]

[echo] signing the unsigned apk to final product apk ...

[echo] done.

BUILD SUCCESSFUL

Total time: 28 seconds

成功的在build/latest目錄下生成一個product_latest_dev.apk,這就是預設的生成的最終的APK,可以導入到手機上運作。

10. 多管道打包

目前主流的多管道打包方法是在AndroidManifest.xml中的Application下添加一個管道中繼資料節點。

比如,我使用的是友盟統計,它配置

AndroidManifest.XML

添加下面代碼:

?

1 2 3 4

<

application

……>

<

meta-data

android:value

=

"Channel ID"

android:name

=

"UMENG_CHANNEL"

/>

<

activity

……/>

</

application

>

  通過修改不同的Channel ID值,辨別不同的管道,有米廣告提供了一個不錯的管道清單:http://wiki.youmi.net/PromotionChannelIDs.

實作多管道自動打包,就是實作自動化過程中替換Channel ID,然後編譯打包。

這個替換需要用到正規表達式實作。

ant中提供的replace方法,功能太簡單了,replaceregrex又需要添加另外的jar包,而且我們後面我們實作ant傳參需要寫另外的linux shell腳本,是以我幹脆使用我熟悉的sed-i指令來實作替換。

替換指令:

?

1 2 3 4

#-i 表示直接修改檔案

#$market是Channel ID, 後面會講到,是來自循環一個數組

#\

1

,\

3

分别表示前面的第

1

3

個括号的内容,這樣寫很簡潔

sed -i

"s/\(android:value=\)\"\(.*\)\"\( android:name=\"UMENG_CHANNEL\"\)/\1\"$market\"\3/g"

AndroidManifest.xml

管道修改的問題解決了。

還記得前面定義的${apk-version},${apk-name},${apk-market}嗎?

ant提供了額外的參數形式可以修改build.xml中定義的屬性的值:ant -Dapk-version=1.0,則會修改${apk-version}值為1.0,而不是latest了,其他屬性類似。

是以,在工程下面這條指令會生成:

?

1 2 3

#結合前面講打build.xml

#會在build/

1.0

/目錄下生成floworld_1.0_appchina.apk

ant -Dapk-name=floworld -Dapk-version=

1.0

-Dapk-market=appchina

指令問題通過ant的參數傳值也解決了。

現在需要的是批量生産N個市場的版本,既替換AndroidManifest.xml,又生成對應的apk檔案,我結合上面說的亮點,寫了一個shell腳本(位置:world/floworld/build.sh):

?

1 2 3 4 5 6 7 8 9 10 11

#定義市場清單,以空格分割

markets

=

"dev appchina gfan"

#循環市場清單,分别傳值給各個腳本

for

market

in

$markets

do

echo packaging floworld_1.

0_

$market.apk ...

#替換AndroidManifest.xml中Channel值(針對友盟,其他同理)

sed

-

i

"s/\(android:value=\)\"\(.*\)\"\( android:name=\"UMENG_CHANNEL\"\)/\1\"$market\"\3/g"

AndroidManifest.xml

#編譯對應的版本

ant

-

Dapk

-

name

=

floworld

-

Dapk

-

version

=

1.0

-

Dapk

-

market

=

$market

done

好的,在工程目錄下執行build.sh:

?

1 2 3 4 5 6 7 8 9 10

# ./build.sh

packaging floworld_1.

0_dev

.apk ...

Buildfile: build.xml

... ...

packaging floworld_1.

0_appchina

.apk ...

Buildfile: build.xml

... ...

packaging floworld_1.

0_gfan

.apk ...

Buildfile: build.xml

... ...

  在build下生成了對應的apk檔案:

?

1 2 3 4 5 6

build

├──

1.0

│ ├── floworld_1.

0_appchina

.apk

│ ├── floworld_1.

0_dev

.apk

│ └── floworld_1.

0_gfan

.apk

└── README.md

  成功生成!

11. 工程腳本的執行目錄問題

上面的腳本執行之後的确很cool,但是有一個問題,我必須在build.sh目錄下執行,才能正确編譯,這個和build.xml中定義的相對路徑有關。

我們必須在任何目錄執行工程目錄下的build.sh都不能出錯,改進build.sh為如下:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

#!/bin/bash

#添加如下兩行簡單的代碼

#1. 擷取build.sh檔案所在的目錄

#2. 進入該build.sh所在目錄,這樣執行起來就沒有問題了

basedir

=

$(cd

"$(dirname "

$

")"

;pwd)

cd $basedir

markets

=

"dev appchina gfan"

for

market

in

$markets

do

echo packaging floworld_1.

0_

$market.apk ...

sed

-

i

"s/\(android:value=\)\"\(.*\)\"\( android:name=\"UMENG_CHANNEL\"\)/\1\"$market\"\3/g"

AndroidManifest.xml

ant

-

Dapk

-

name

=

floworld

-

Dapk

-

version

=

1.0

-

Dapk

-

market

=

$market

done

現在你在項目根目錄下執行也沒有問題:./floworld/build.sh,不會出現路徑不對,找不到檔案的錯誤了。

12. 建立整個項目的自動化編譯腳本(位置:world/build.sh)

單個工程的自動化打包沒有問題了,但是一個項目下有N個工程,他們往往需要同步釋出(或者daily build也需要同步編譯),是以有必要建立一個項目級别的編譯腳本:

build.sh(項目根目錄下,位置:/world/build.sh)

最簡單的傻瓜式的做法就是,周遊項目下的工程目錄,如果包含工程編譯的build.sh,則編譯該工程.

shell腳本如下:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

#!/bin/bash

#確定進入項目跟目錄

basedir

=

$(cd

"$(dirname "

$

")"

;pwd)

cd $basedir

#周遊項目下各工程目錄

for

file

in

.

/

*

do

if

test

-

d $

file

then

#進入工程目錄

cd $basedir

/

$

file

#查找該工程目錄下是否存在編譯腳本build.sh

if

test

-

f build.sh

then

echo found build.sh

in

project $

file

.

echo start building project $

file

...

.

/

build.sh

fi

#重要,退出工程目錄到項目根目錄下

cd $basedir

fi

done

  執行該腳本:

?

1 2 3 4 5 6 7 8 9 10 11 12

# ./build.sh

found build.sh

in

project .

/

floworld.

start building project .

/

floworld ...

packaging floworld_1.

0_dev

.apk ...

Buildfile: build.xml

...

...

found build.sh

in

project .

/

healthworld.

start building project .

/

healthworld ...

Buildfile: build.xml

...

成功自動尋找,并編譯打包。

13. 其他細節

為了盡量詳細,我一再解說,但是還有一些細節未包括其中,如編譯後清理clean目标,apk對齊優化,java代碼混淆等,請參考其他資料,在此省略。

另外,我反編譯生成的apk,檢視Androidmanifest.xml均正确對應,驗證通過。

14. 小結

自動化編譯多管道打包這個功能是Android産品釋出的重要環節,能大大節省人力和出錯的機率。

最近寫部落格的步伐慢了下來了,我決定加大力度,多寫幾篇。

其實早就想做這一塊的内容了,一直不得空,或者說心裡不得空,前幾天剛完全格式化硬碟遷移到ubuntu(抛棄了雙系統), 最近又開始着手準備一套《使用vim開發android》視訊教程,人又不能太過于沉溺于舒适區域,我決定逼迫一下自己,終于逼出了這篇文章。

真是不容易,今天好熱啊,火,你就是火,到處都是火 ... ...

本人項目中的具體應用示例:https://github.com/openproject/world