Android筆記之: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。
程式碼模板如下:
複製程式碼 程式碼如下:
<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目錄下。目錄完全可以自定義。
所以,如下的初始化必須先要做好,不然後面會提示找不到目錄:
複製程式碼 程式碼如下:
<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的生成指令碼:
複製程式碼 程式碼如下:
<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即可。
複製程式碼 程式碼如下:
<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,以此類推)。
結果如下:
複製程式碼 程式碼如下:
gen
└── com
└── tianxia
├── app
│ └── floworld
│ └── R.java
└── lib
└── baseworld
└── R.java
5. 編譯java檔案為class檔案
使用javac命令把src目錄,baseworld/src目錄,gen/*/R.java這些java編譯成class檔案:
命令原型是:
複製程式碼 程式碼如下:
//示例
javac -bootclasspath <android.jar> -s <src> -s <src> -s <gen> -d bin/classes *.jar
轉化成ant指令碼為:
複製程式碼 程式碼如下:
<!-- 第三方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。
命令原型是:
複製程式碼 程式碼如下:
//示例
//後面可以接任意個第三方jar路徑
dx --dex --output=out/classes.dex bin/classes libs/1.jar libs/2.jar
轉化成ant指令碼為:
複製程式碼 程式碼如下:
<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指令碼差不多:
複製程式碼 程式碼如下:
<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指令碼類似:
複製程式碼 程式碼如下:
<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指令碼差不多:
複製程式碼 程式碼如下:
<!-- 生成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為:
複製程式碼 程式碼如下:
<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>
<exec executable="dx">
<arg value="--dex" />
<arg value="--output=out/classes.dex" />
<arg value="bin/classes" />
<arg value="libs" />
</exec>
<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:
複製程式碼 程式碼如下:
$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新增下面程式碼:
複製程式碼 程式碼如下:
<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命令來實現替換。
替換命令:
複製程式碼 程式碼如下:
#-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均正確對應,驗證通過。
14. 小結
自動化編譯多渠道打包這個功能是Android產品釋出的重要環節,能大大節省人力和出錯的概率。
本人專案中的具體應用示例:https://github.com/openproject/world
您可能感興趣的文章:
怎麼釋出打包併發布自己的Android應用(APP)
Android APP與媒體儲存服務的互動
ANDROID 完美退出APP的例項程式碼
Android 避免APP啟動閃黑屏的解決辦法(Theme和Style)
Android獲取應用程式名稱(ApplicationName)示例
Android學習筆記--通過Application傳遞資料程式碼示例
Android獲取手機型號/系統版本號/App版本號等資訊例項講解
理解Android的手勢識別提高APP的使用者體驗
android FM播放時拔出耳機後FM APP自動close解決方法
Android筆記之:App除錯的幾個命令的實踐與分析
Android筆記之:App列表之下拉重新整理的使用
Android筆記之:App模組化及工程擴充套件的應用
Android筆記之:App應用之釋出各廣告平臺版本的詳解
Android筆記之:App應用之啟動介面SplashActivity的使用
修改Android App樣式風格的方法
詳細出處參考:http://www.jb51.net/article/36193.htm
經過不斷的嘗試,在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。
程式碼模板如下:
複製程式碼 程式碼如下:
<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目錄下。目錄完全可以自定義。
所以,如下的初始化必須先要做好,不然後面會提示找不到目錄:
複製程式碼 程式碼如下:
<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的生成指令碼:
複製程式碼 程式碼如下:
<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即可。
複製程式碼 程式碼如下:
<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,以此類推)。
結果如下:
複製程式碼 程式碼如下:
gen
└── com
└── tianxia
├── app
│ └── floworld
│ └── R.java
└── lib
└── baseworld
└── R.java
5. 編譯java檔案為class檔案
使用javac命令把src目錄,baseworld/src目錄,gen/*/R.java這些java編譯成class檔案:
命令原型是:
複製程式碼 程式碼如下:
//示例
javac -bootclasspath <android.jar> -s <src> -s <src> -s <gen> -d bin/classes *.jar
轉化成ant指令碼為:
複製程式碼 程式碼如下:
<!-- 第三方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。
命令原型是:
複製程式碼 程式碼如下:
//示例
//後面可以接任意個第三方jar路徑
dx --dex --output=out/classes.dex bin/classes libs/1.jar libs/2.jar
轉化成ant指令碼為:
複製程式碼 程式碼如下:
<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指令碼差不多:
複製程式碼 程式碼如下:
<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指令碼類似:
複製程式碼 程式碼如下:
<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指令碼差不多:
複製程式碼 程式碼如下:
<!-- 生成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為:
複製程式碼 程式碼如下:
<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>
<exec executable="dx">
<arg value="--dex" />
<arg value="--output=out/classes.dex" />
<arg value="bin/classes" />
<arg value="libs" />
</exec>
<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:
複製程式碼 程式碼如下:
$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新增下面程式碼:
複製程式碼 程式碼如下:
<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命令來實現替換。
替換命令:
複製程式碼 程式碼如下:
#-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均正確對應,驗證通過。
14. 小結
自動化編譯多渠道打包這個功能是Android產品釋出的重要環節,能大大節省人力和出錯的概率。
本人專案中的具體應用示例:https://github.com/openproject/world
您可能感興趣的文章:
怎麼釋出打包併發布自己的Android應用(APP)
Android APP與媒體儲存服務的互動
ANDROID 完美退出APP的例項程式碼
Android 避免APP啟動閃黑屏的解決辦法(Theme和Style)
Android獲取應用程式名稱(ApplicationName)示例
Android學習筆記--通過Application傳遞資料程式碼示例
Android獲取手機型號/系統版本號/App版本號等資訊例項講解
理解Android的手勢識別提高APP的使用者體驗
android FM播放時拔出耳機後FM APP自動close解決方法
Android筆記之:App除錯的幾個命令的實踐與分析
Android筆記之:App列表之下拉重新整理的使用
Android筆記之:App模組化及工程擴充套件的應用
Android筆記之:App應用之釋出各廣告平臺版本的詳解
Android筆記之:App應用之啟動介面SplashActivity的使用
修改Android App樣式風格的方法
詳細出處參考:http://www.jb51.net/article/36193.htm
相關文章
- Android使用Ant自動編譯簽名打包詳解Android編譯
- Android 自動編譯、打包生成apk檔案 3 - 使用SDK Ant方式Android編譯APK
- Android使用Ant進行apk多渠道打包AndroidAPK
- 使用ant優化android專案編譯速度,提高工作效率優化Android編譯
- android使用ant編譯(rem)Android編譯REM
- android Ant 批量多渠道打包 總結!Android
- iOS自動化編譯打包iOS編譯
- Android多渠道打包工具Gradle外掛使用詳解AndroidGradle
- 使用ant編譯Java檔案(一)編譯Java
- 使用ant編譯Java檔案(二)編譯Java
- ant指令碼實現的Android自動編譯指令碼Android編譯
- Window下采用ant 指令碼構建Android自動化編譯指令碼Android編譯
- 使用ANT打包Android應用Android
- Gradle for Android系列之五 多渠道打包GradleAndroid
- android自動化測試六之命令列編譯APKAndroid命令列編譯APK
- 360加固+美團walle多渠道自動化打包
- [android]android自動化測試六之命令列編譯APKAndroid命令列編譯APK
- 通過ant指令碼編譯打包android工程指令碼編譯Android
- Android App 優化之 ANR 詳解AndroidAPP優化
- Flutter之MaterialApp使用詳解FlutterAPP
- 自動化瓦力多渠道打包python指令碼Python指令碼
- Android Studio 使用Gradle多渠道打包AndroidGradle
- Android使用Gradle實現多渠道打包AndroidGradle
- Android自動打包、簽名、優化、上傳ANT指令碼Android優化指令碼
- Android學習探索之App多渠道打包及動態新增修改資源屬性AndroidAPP
- Android 詳解Gradle(3.1.4)實現多渠道打包AndroidGradle
- iOS自動化打包(fastlane使用)iOSAST
- android 使用ANT批量打包apk步驟AndroidAPK
- 使用 fastlane 實現自動化打包AST
- Android進階之Walle多渠道打包&Tinker熱修復Android
- 優化使用kotlin開發Android app的編譯速度優化KotlinAndroidAPP編譯
- Ant之build.xml詳解UIXML
- Android 多渠道打包開發記錄Android
- Android多渠道打包Android
- 利用ant編譯釋出打包jar檔案和打包api文件為rar檔案編譯JARAPI
- Flutter 多環境、多渠道自動打包Flutter
- 使用HBuilder將web專案打包成appUIWebAPP
- ReactNative專案自動化打包釋出React