Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android App自動化之使用Ant編譯項目多渠道打包

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

編輯:關於Android編程

隨著工程越來越復雜,項目越來越多,以及平台的遷移(我最近就遷了2回),還有各大市場的發布,自動化編譯android項目的需求越來越強烈,後面如果考慮做持續集成的話,會更加強烈。
經過不斷的嘗試,在ubuntu環境下,以花界為例,我將一步一步演示如何使用命令行,使用ant編譯android項目,打包多渠道APK。
要點:
(1). 編譯android的命令使用
(2). ant基本應用
(3). 多項目如何編譯(包含android library)
(4). 如何多渠道打包
ps:我將以最原始的方式來實現,而不是使用android自帶的ant編譯方式,並盡量詳細解釋,這樣有益於我們徹底搞懂android打包的基本原理。

1. Android編譯打包的整體過程
、 首先,我們假設我們現在已經有這樣的一個項目(多工程的,簡單的單工程就更簡單了):

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。
代碼模板如下:




start initing ...

finish initing.








3. 初始化
在正式打包之前,我們有必要說明一下可能需要用到的初始化變量和操作。
前面已經講述了打包的大概流程,現在,第一, 打包需要你使用哪個版本android.jar; 第二, 生成的R文件放到gen目錄下; 第三, 生成的classes文件放到bin目錄下; 第四, 生成的打包文件放到out目錄下; 第五, 生成的各市場版本放到build目錄下。目錄完全可以自定義。
所以,如下的初始化我們必須先要做好,不然後面會提示找不到目錄:









start initing ...














finish initing.

... ...

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的生成腳本:

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















注意res和../baseworld/res兩個順序不能搞反,寫在前面具有高優先級,我們當然優先使用主應用的資源了,這樣就能正確覆蓋庫應用的資源,實現重寫。
庫應用的R.java的生成腳本差不多,區別是指定庫應用的AndroidManifest.xml,以用於生成的是不同的包和目錄。
另外,aapt的使用中特別說明了,為了庫應用的資源更好的可重用,我們庫應用生成的R.java字段不需要修飾為final,加上參數--non-constant-id即可。

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
















這樣的話我們就可以生成2個正確的R.java文件了(如果你引用了兩個庫,則需要生成3個R.java,以此類推)。
結果如下:

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 -s -s -s -d bin/classes *.jar
轉化成ant腳本為:







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







6. 打包class文件為classes.dex
這步簡單,用dx命令把上步生成的classes和第三方jar包打包成一個classes.dex。
命令原型是:

//示例
//後面可以接任意個第三方jar路徑
dx --dex --output=out/classes.dex bin/classes libs/1.jar libs/2.jar
  轉化成ant腳本為:

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






7. 打包res,assets為資源壓縮包(暫且命名為res.zip)
還是使用aapt命令,如生成R.java最大的不同是參數-F,意思是生成res.zip文件。
命令原型和ant腳本差不多:

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

















8. 使用apkbuilder命令組合classes.dex,res.zip和AndroidManifest.xml為未簽名的apk
apkbuilder命令能把class類,資源等文件打包成一個未簽名的apk,原型命令和ant腳本類似:

building unsigned.apk ...








  這個命令比較簡單。

9. 簽名未簽名的apk
使用jarsigner命令對上步中產生的apk簽名。這是個傳統的java命令,非android專用。
原型命令和ant腳本差不多:



signing the unsigned apk to final product apk ...




<-- 驗證密鑰完整性的口令,創建時建立的 -->

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





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










start initing ...


















finish initing.



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
















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






















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







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







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


















building unsigned.apk ...









signing the unsigned apk to final product apk ...













done.


  在工程目錄下運行ant:

$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添加下面代碼:





  通過修改不同的Channel ID值,標識不同的渠道,有米廣告提供了一個不錯的渠道列表:http://wiki.youmi.net/PromotionChannelIDs.
實現多渠道自動打包,就是實現自動化過程中替換Channel ID,然後編譯打包。
這個替換需要用到正則表達式實現。
ant中提供的replace方法,功能太簡單了,replaceregrex又需要添加另外的jar包,而且我們後面我們實現ant傳參需要寫另外的linux shell腳本,所以我干脆使用我熟悉的sed-i命令來實現替換。
替換命令:

#-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了,其他屬性類似。
所以,在工程下面這條命令會生成:

#結合前面講打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):

#定義市場列表,以空格分割
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:

# ./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文件:

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為如下:

#!/bin/bash
#添加如下兩行簡單的代碼
#1. 獲取build.sh文件所在的目錄
#2. 進入該build.sh所在目錄,這樣執行起來就沒有問題了
basedir=$(cd "$(dirname "$0")";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腳本如下:

#!/bin/bash
#確保進入項目跟目錄
basedir=$(cd "$(dirname "$0")";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
  執行該腳本:

# ./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均正確對應,驗證通過。


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved