随着工程越来越复杂,项目越来越多,以及平台的迁移(我最近就迁了2回),还有各大市场的发布,自动化编译android项目的需求越来越强烈,后面如果考虑做持续集成的话,会更加强烈。 1. Android编译打包的整体过程 world 一个大的项目world,下面有1个基础Android Library和4个Android Project。我们要做的就是编译这4个人project成对应的一系列各市场APK。 baseworld 和Android Project,floworld: floworld/ 结构已经出来了,那么android打包主要是在做什么? 2. 建立各个工程的ant脚本文件build.xml(位置:floworld/build.xml) <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. 初始化 <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 <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两个顺序不能搞反,写在前面具有高优先级,我们当然优先使用主应用的资源了,这样就能正确覆盖库应用的资源,实现重写。 <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 -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 //示例 //后面可以接任意个第三方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) <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 <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 <!-- 生成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. 多渠道打包 <application ……> <meta-data android:value="Channel ID" android:name="UMENG_CHANNEL"/> <activity ……/> </application> 通过修改不同的Channel ID值,标识不同的渠道,有米广告提供了一个不错的渠道列表:http://wiki./PromotionChannelIDs. #-i 表示直接修改文件 #$market是Channel ID, 后面会讲到,是来自循环一个数组 #\1,\3分别表示前面的第1,3个括号的内容,这样写很简洁 sed -i "s/\(android:value=\)\"\(.*\)\"\( android:name=\"UMENG_CHANNEL\"\)/\1\"$market\"\3/g" AndroidManifest.xml 渠道修改的问题解决了。 #结合前面讲打build.xml #会在build/1.0/目录下生成floworld_1.0_appchina.apk ant -Dapk-name=floworld -Dapk-version=1.0 -Dapk-market=appchina 命令问题通过ant的参数传值也解决了。 #定义市场列表,以空格分割 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. 工程脚本的执行目录问题 #!/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) #!/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. 其他细节 14. 小结 |
|
来自: 天海544 > 《android 打包》