最近在項(xiàng)目中遇到了一些打包的問(wèn)題讯柔,順便去了解了下打包的一些知識(shí)點(diǎn)抡蛙。這里主要介紹和總結(jié)了一下ant、build.xml的知識(shí)點(diǎn)以及構(gòu)建apk和jar包的一些注意事項(xiàng)魂迄。
Android打包
對(duì)工程代碼和資源文件使用打包工具進(jìn)行編譯粗截、混淆、簽名捣炬、優(yōu)化對(duì)齊等一系列步驟之后生成可發(fā)布到應(yīng)用市場(chǎng)的apk的構(gòu)建過(guò)程熊昌。
打包流程
大概分為以下幾個(gè)步驟
1、使用aapt工具將res資源文件生成R.java文件
2湿酸、使用aidl工具將aidl文件生成對(duì)應(yīng)java文件
3婿屹、使用javac命令編譯工程源代碼和上面兩步生成的文件,生成class文件
4稿械、通過(guò)dex工具將class文件和第三方j(luò)ar包打成dex文件
5选泻、用aapt工具將res下的資源文件編譯成二進(jìn)制文件,然后將其和上一步中的dex文件以及assets中的文件通過(guò)apkbuilder工具打包成apk文件
6美莫、通過(guò)jarsigner對(duì)apk進(jìn)行簽名
7、利用zipalign工具對(duì)apk進(jìn)行字節(jié)對(duì)齊優(yōu)化操作
打包方式 — Ant
Ant是將軟件編譯梯捕、測(cè)試厢呵、部署等步驟聯(lián)系在一起自動(dòng)化構(gòu)建工具,主要用在java工程的構(gòu)建中傀顾,所以也可以用來(lái)進(jìn)行android打包襟铭。
現(xiàn)在android開(kāi)發(fā)工具基本上都用的AS,構(gòu)建用gradle短曾,由于某些原因寒砖,項(xiàng)目組中用的還是eclipse+ant方式,所以暫時(shí)只介紹ant的構(gòu)建方式嫉拐。雖然工具不一樣哩都,但是整個(gè)構(gòu)建原理和流程還是一樣的。
Ant的默認(rèn)構(gòu)建文件為build.xml婉徘,輸入ant命令后漠嵌,ant會(huì)在當(dāng)前目錄下搜索是否有build.xml,如果有盖呼,則執(zhí)行該文件,也可以自定義構(gòu)建文件儒鹿,通過(guò)ant -f test.xml即可指定test.xml為構(gòu)建文件。
build.xml腳本
先看一個(gè)簡(jiǎn)單的build.xml
<?xml version="1.0" encoding="GBK"?>
//ant默認(rèn)構(gòu)建文件即build.xml文件中需定義一個(gè)唯一的項(xiàng)目(Project標(biāo)簽)几晤,Project下可以定義若干個(gè)目標(biāo)(target標(biāo)簽)
//project名稱為MyApp, default表示默認(rèn)的運(yùn)行target约炎,為必須屬性,如果ant命令沒(méi)有指定target時(shí)蟹瘾,則運(yùn)行default屬性中的target
//如MyApp工程目錄下直接輸入ant命令圾浅,則會(huì)直接打debug包墙贱。 basedir表示項(xiàng)目的基準(zhǔn)目錄
<project name="MyApp" default="debug" basedir=".">
//property標(biāo)簽用來(lái)設(shè)置屬性值,可以通過(guò)file標(biāo)簽來(lái)指定要加載的屬性文件的路徑贱傀,加載后屬性文件中的指定的屬性可以直接引用惨撇。
//為了方便配置,可以將環(huán)境變量聲明在build.properties中府寒,并通過(guò)file引入到build.xml中
<property file="build.properties"></property>
//property中的name表示屬性的名稱 value表示屬性值 在其他地方可以通過(guò)${屬性名}進(jìn)行引用魁衙, 類似于定義一個(gè)變量
<property name="outdir" value="bin" />
//tartget,表示一個(gè)構(gòu)建目標(biāo),也可以看成一個(gè)構(gòu)建步驟株搔, 一次構(gòu)建過(guò)程中會(huì)執(zhí)行一個(gè)或者多個(gè)構(gòu)建步驟剖淀。
//target中的depends屬性表示target之間的依賴關(guān)系,一個(gè)target可以依賴其他的target標(biāo)簽纤房,depends屬性也指定了target的執(zhí)行順序纵隔。
//ant會(huì)按照depends屬性中target的順序來(lái)依次執(zhí)行每個(gè)target。所以本文中target的執(zhí)行順序?yàn)?targetone -> targettwo -> debug
<target name="targetone">
//創(chuàng)建目錄
<mkdir dir="${outdir}"/>
</target>
//task是target中的子元素炮姨,一個(gè)target中可以有多個(gè)task捌刮,類似于target的子任務(wù),常用的task有echo舒岸、mkdir绅作、delete、javac蛾派、java等等
<target name="targettwo" depends="targetone">
//刪除目錄
<delete dir="${name}"/>
</target>
<target name="debug" depends="targettwo">
//輸出日志信息
<echo>debug target perform...</echo>
</target>
</project>
打包成apk
build腳本中俄认,一般android源工程打包成apk的執(zhí)行步驟大體如下:
gen-R->aidl->compile->obfuscate->dex->package-res-and-assets->package->jarsigner->zipalign->release
gen-R之前還有一些clean清除上次打包產(chǎn)生的文件的操作,這里不再贅述
1洪乍、gen-R
<target name="gen-R" depends="dirs">
<exec executable="${aapt}" failonerror="true">
<arg value="package" />
<arg value="-m" />
<arg value="-J" />
<arg value="${gen-dir}" />
<arg value="-M" />
<arg value="${manifest-xml}" />
<arg value="-S" />
<arg value="${resource-dir}" />
<arg value="-I" />
<arg value="${android-jar}" />
</exec>
</target>
gen-R 執(zhí)行aapt命令來(lái)編譯資源文件生成R.java文件 arg中的參數(shù)就是aapt中的命令行參數(shù)眯杏,該target其實(shí)執(zhí)行的就是如下命令
aapt package -m -J gen -M AndroidManifest.xml -S res -I android.jar
具體參數(shù)命令含義如下:
-m 使生成的包的目錄放在-J參數(shù)指定的目錄。
-J 指定生成的R.java的輸出目錄
-M AndroidManifest.xml的路徑
-S res文件夾路徑
-I 某個(gè)版本平臺(tái)的android.jar的路徑
2壳澳、aidl
<target name="aidl" depends="dirs">
<apply executable="${aidl}" failonerror="true">
<arg value="-p${android-framework}" />
<arg value="-I${srcdir}" />
<arg value="-o${gen-dir}" />
<fileset dir="${srcdir}">
<include name="**/*.aidl" />
</fileset>
</apply>
</target>
此步驟主要是生成aidl文件對(duì)應(yīng)的java文件
使用apply標(biāo)簽可以進(jìn)行批量運(yùn)行task岂贩,此步驟即用build-tools下的aidl工具對(duì)src文件夾下的所有aidl文件進(jìn)行批量轉(zhuǎn)換成java文件。
<apply>是作為<exec>的一個(gè)子類而被實(shí)現(xiàn)钾埂,所以<exec>任務(wù)的所有屬性河闰,都可以用于<apply>
3、compile
<target name="compile" depends="dirs, gen-R, aidl">
//javac標(biāo)簽用于編譯java文件生成class文件 destdir表示生成class文件的目錄
<javac encoding="UTF-8" target="1.5" debug="true" extdirs="" destdir="${outdir-classes}"
bootclasspath="${android-jar}" fork="true" memoryMaximumSize="512m" >
<src path="${srcdir-ospath}" />
<src path="${gen-dir-ospath}" />
//表示依賴庫(kù)的路徑 內(nèi)嵌在<javac>褥紫、<java>中
<classpath>
<fileset dir="${external-libs}" includes="*.jar" />
</classpath>
</javac>
</target>
compile執(zhí)行的是javac命令姜性,編碼格式指定為utf-8,target指定生成的class文件與該版本的虛擬機(jī)兼容髓考,保證在該版本的虛擬機(jī)上正常運(yùn)行部念。
debug表示是否產(chǎn)生調(diào)試信息,默認(rèn)為false。extdirs為擴(kuò)展文件的路徑儡炼,destdir指定了存放編譯后的class文件的文件夾路徑妓湘。bootclasspath指定了編譯過(guò)程中需要導(dǎo)入的class文件。fork指定是否再外部啟用一個(gè)新的JDK編譯器來(lái)執(zhí)行編譯乌询,如果為false榜贴,則javac命令和ant將在同一個(gè)進(jìn)程中執(zhí)行,并且javac命令被分配的內(nèi)存只有64MB妹田,可能會(huì)導(dǎo)致java.lang.OutOfMemoryError(OOM)錯(cuò)誤唬党,如果fork為true,則另起一個(gè)進(jìn)程來(lái)執(zhí)行javac命令鬼佣,分配的內(nèi)存大小將由memoryMaximumSize來(lái)指定驶拱。src指定了java源文件的路徑,classpath指定了依賴的第三方j(luò)ar包路徑晶衷。
4蓝纲、obfuscate
<target name="obfuscate" depends="compile">
//jar標(biāo)簽用來(lái)生成jar文件,basedir表示需要打包城jar文件的原文件目錄晌纫, destfile表示生成的jar文件
<jar basedir="${outdir-classes}" destfile="${outdir}/temp.jar"/>
//java標(biāo)簽用來(lái)執(zhí)行編譯生成的class文件 fork表示再一個(gè)新的虛擬機(jī)中運(yùn)行該類 failonerror表示當(dāng)出現(xiàn)錯(cuò)誤時(shí)是否自動(dòng)停止
<java jar="${proguard.home}/proguard.jar" fork="true" failonerror="true">
//arg標(biāo)簽用來(lái)指定參數(shù) value是命令行參數(shù)
<arg value="-injars ${outdir}/temp.jar"/>
<arg value="-outjars ${outdir}/obfuscate.jar"/>
<arg value="-libraryjars ${android-jar}"/>
<arg value="-libraryjars ${third_part_jar}"/>
<arg value="@${proguard.config}"/>
</java>
<delete file="${outdir}/temp.jar"/>
<delete dir="${outdir-classes}"/>
<mkdir dir="${outdir-classes}"/>
<unzip src="${outdir}/obfuscate.jar" dest="${outdir-classes}"/>
<delete file="${outdir}/obfuscate.jar"/>
</target>
obfuscate混淆先是執(zhí)行了jar命令税迷,將bin目錄下的class文件打包成temp.jar。然后執(zhí)行了proguard命令來(lái)壓縮缸匪、優(yōu)化和混淆操作翁狐。
-injars {class_path}指定要處理的應(yīng)用程序jar和目錄,即temp.jar
-outjars {class_path}指定處理完后要輸出的jar和目錄,即obfuscate.jar
-libraryjars {classpath}指定要處理的應(yīng)用程序jar和目錄所需要的程序庫(kù)文件,即其他依賴的第三方j(luò)ar包
混淆配置文件為proguard.config凌蔬。混淆之后刪除生成的臨時(shí)文件闯冷,并解壓obfuscate.jar到bin目錄下
5砂心、dex
<target name="dex" depends="compile, obfuscate">
<apply executable="${dx}" failonerror="true" parallel="true">
<arg value="--dex" />
<arg value="--output=${intermediate-dex-ospath}" />
<arg path="${outdir-classes-ospath}" />
<fileset dir="${external-libs}" includes="*.jar" />
</apply>
</target>
dex就是用dx.bat工具將class文件轉(zhuǎn)換成classes.dex文件,即對(duì)上一步在bin/classes目錄中生成的優(yōu)化過(guò)的class文件以及依賴的第三方j(luò)ar包進(jìn)行dex操作蛇耀,最后在bin目錄下生成classes.dex文件辩诞。Parallel用于指定將多個(gè)task并行執(zhí)行。
6纺涤、package-res-and-assets
<target name="package-res-and-assets">
<exec executable="${aapt}" failonerror="true">
<arg value="package" />
<arg value="-f" />
<arg value="-M" />
<arg value="AndroidManifest.xml" />
<arg value="-S" />
<arg value="${resource-dir}" />
<arg value="-A" />
<arg value="${asset-dir}" />
<arg value="-I" />
<arg value="${android-jar}" />
<arg value="-F" />
<arg value="${resources-package}" />
</exec>
</target>
package-res-and-assets中執(zhí)行了aapt命令译暂,來(lái)將res、assets目錄下的資源文件打包到resources.ap_
aapt package -f -M <AndroidManifest.xml路徑> -S <res路徑> -A <assert路徑> -I <android.jar路徑> -F <輸出的包目錄+包名>
7撩炊、package
<target name="package" depends="dex,package-res-and-assets">
<exec executable="${apk-builder}" failonerror="true">
<arg value="${out-unsigned-package-ospath}" />
<arg value="-u" />
<arg value="-z" />
<arg value="${resources-package-ospath}" />
<arg value="-f" />
<arg value="${dex-ospath}" />
<arg value="-rf" />
<arg value="${srcdir-ospath}" />
<arg value="-rj" />
<arg value="${external-libs-ospath}" />
<arg value="-nf" />
<arg value="${native-libs-dir-ospath}" />
</exec>
</target>
通過(guò)apkbuilder.bat工具根據(jù)classes.dex文件和resources.ap_生成未混淆的apk包
apkbuilder <輸出apk文件路徑> -z <資源文件路徑> -f <dex文件路徑> -rf <源碼目錄> -rj <第三方j(luò)ar包目錄> -nf <本地庫(kù)目錄>
8外永、jarsigner
<target name="jarsigner" depends="package">
<exec executable="${jarsigner}" failonerror="true">
<arg value="-verbose" />
<arg value="-storepass" />
<arg value="${password}" />
<arg value="-keystore" />
<arg value="${keystore.path}" />
<arg value="-signedjar" />
<arg value="${out-signed-package-ospath}" />
<arg value="${out-unsigned-package-ospath}" />
<arg value="${keystore.key}" />
</exec>
</target>
jarsigner是對(duì)上面生成的apk文件進(jìn)行簽名操作
-verbose 簽名時(shí)輸出詳細(xì)信息
-storepass 密鑰庫(kù)密碼
-keystore 密鑰庫(kù)位置
-signedjar 后面接的參數(shù)依次是 簽名后的apk、待簽名的apk拧咳、密鑰庫(kù)別名
9伯顶、zipalign
<target name="zipalign" depends="jarsigner">
<exec executable="${zipalign}" failonerror="true">
<arg value="-v" />
<arg value="-f" />
<arg value="4" />
<arg value="${out-signed-package-ospath}" />
<arg value="${zipalign-package-ospath}" />
</exec>
</target>
zipalign target通過(guò)zipalign工具對(duì)簽名后的apk包進(jìn)行字節(jié)對(duì)齊,好處是能夠減少應(yīng)用程序的RAM內(nèi)存資源消耗
-v 表示輸出詳細(xì)信息
-f 表示如果輸出文件已存在 則直接覆蓋
4 表示對(duì)齊為4個(gè)字節(jié)
10、release
<target name="release" depends="zipalign">
<!-- 刪除未簽名apk -->
<delete file="${out-unsigned-package-ospath}"/>
......
</target>
至此打一個(gè)完整的帶簽名的可發(fā)布的包的流程就結(jié)束了祭衩。執(zhí)行ant release命令即可完成打包灶体。
打包成jar
由于jar包中不能包含資源文件,所以要通過(guò)jar包提供UI視圖供第三方使用掐暮,可以通過(guò)如下方式實(shí)現(xiàn):
1蝎抽、使用硬編碼來(lái)實(shí)現(xiàn)布局文件
2、布局中的資源文件需放在assets文件夾中路克,然后打包到j(luò)ar中樟结,通過(guò)流的方式讀取。這種方式將資源文件放在assets目錄下和java代碼一起打包為jar,其他工程依賴該jar包時(shí)衷戈,可以只引用jar包狭吼,不需要再額外導(dǎo)入資源文件,在該工程編譯應(yīng)用時(shí)會(huì)將jar包assets目錄中的文件與該工程中的assets目錄中的文件合并殖妇。注意assets目錄中的文件名與所導(dǎo)入工程中的文件名稱不能重復(fù)刁笙,否則在編譯的時(shí)候會(huì)報(bào)錯(cuò)“Error generating final archive: Found duplicate file for APK”提示有重名文件。
另外谦趣,打包到j(luò)ar中的資源文件必須是編譯之后的資源文件疲吸,即編譯成二進(jìn)制文件,因?yàn)樽x取資源時(shí)是通過(guò)流的方式讀取的前鹅,所以相關(guān)的資源文件必須在編譯成二進(jìn)制文件之后再放入assets打包摘悴。
讀取方式如下
//讀取圖片
InputStream inputStream = context.getAssets().open(path);
Drawable drawable = Drawable.createFromResourceStream(
context.getResources(), value, inputStream, name);
//讀取xml圖片資源
XmlResourceParser parser = context.getAssets().openXmlResourceParser(path);
Drawable draw = Drawable.createFromXml(context.getResources(), parser);
jar包的構(gòu)建方式與apk的類似,執(zhí)行步驟大概為
aidl->compile->copy_asset->obfuscate->jarsigner
與打包成apk流程相比少了gen-R舰绘、aapt蹂喻、dex、package-res-and-assets捂寿、package口四、zipalign等操作,需要注意就是obfuscate混淆這一步秦陋,
打成jar包時(shí)obfuscate如下:
<!-- Obscure the package file. -->
<target name="obfuscate" depends="compile, copy-asset">
<echo>Obscure the class files....</echo>
<jar basedir="${outdir-classes}" destfile="${out_original_jar}">
</jar>
<java jar="${proguard.home}/proguard.jar" fork="true" failonerror="true">
<arg value="-injars ${out_original_jar}"/>
<arg value="-injars $other_jar}"/>
<arg value="-libraryjars ${android-jar}"/>
...
<arg value="-outjars ${outdir}/${out_obfuscate_jar}"/>
<arg value="@${proguard.config}"/>
</java>
</target>
obfuscate混淆先是執(zhí)行了jar命令蔓彩,將bin目錄下的class文件以及資源文件打包成jar包,然后執(zhí)行proguard命令來(lái)壓縮驳概、優(yōu)化和混淆操作赤嚼。這里需要注意的是如果該工程還依賴了其他jar包(未混淆),則打成jar的同時(shí)需要將其他jar包也引入進(jìn)來(lái)顺又,因?yàn)樽詈髮?duì)外提供的是該工程的jar包更卒。
另外需要注意的是proguard.cfg混淆文件中需要為其他jar包的類文件指明重命名類的包路徑
# Specifies to repackage all class files that are renamed, by moving them into the single given package
-repackageclasses 'com.example.otherjar'
一定要為一些重命名的class文件指明打到j(luò)ar包中的包路徑,jar包中所有的class文件需要有明確的包路徑待榔,以防被第三方apk集成編譯時(shí)逞壁,這些class文件無(wú)法-keep流济,被編譯混淆之后找不到這些類,導(dǎo)致jar包功能異常腌闯。
而增加了路徑指定后绳瘟,重命名的類就會(huì)被打到指定的包路徑下,其他地方對(duì)這些類的調(diào)用也能正常進(jìn)行姿骏。
基本上要介紹的就這么多糖声,可能會(huì)有理解錯(cuò)誤的地方,歡迎一起討論分瘦!