如何构建带有Android Library Project的工程

来源:互联网 发布:鹊桥采集软件免费 编辑:程序博客网 时间:2024/06/06 02:47
最近有几个Android产品在接入持续集成的过程中涉及到Android Library Project的构建,这种情况有点特殊,以往MIG的项目很少涉及。

带有Android Library Project的工程的构建,看上去挺复杂,但掌握了下面两种方法之一,就很容易做到。

具体方法有:
一、使用Android SDK自带的构建脚本,通过传人参数来构建。
二、自定义构建脚本来实现带有Library Project的构建。

下面来具体说说两种方法
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

一、使用Android SDK自带的构建脚本,通过传人参数来构建
这种方法比较简单,但可控性不高,因为构建过程都是Android SDK自带的build.xml定义好了,我们只是传人参数,对于过程的定义与输出干预不了,比如什么文件输出到哪些目录。

首先,Android SDK自带的构建脚本位于:
C:\android-sdk-windows\tools\ant\build.xml
(ps: 假设Android SDK安装在C:\android-sdk-windows)

假设工程的目录结构是这样的:
/
|--MyProject
|--LibraryProject1
|--LibraryProject2
|--...

这里MyProject为主工程,也就是我们要Build的app,然后存在若干个Library Project,这里假设有2个LibraryProject1和LibraryProject2。

那么我们可以在根目录(/)下放一个引导脚本build.xml,内容如:

然后MyProject主工程下面放以下几个脚本(附近有模版,可以参考):
build.xml -- 构建脚本,主要是导入配置,传递参数给SDK下的build.xml文件
project.properties -- 当前工程使用的AndroidSDK版本定义,以及Library Project引用定义
local.properties -- Android SDK Home路径的定义
ant.properties -- 签名证书及证书认证的设置
当然,上面这些属性文件也可以合并处理...

几个文件的内容介绍:
build.xml  - 这个实际上也是基于模版生成的,内容非常简单,就简单的几句参数加载与脚本调用

project.properties
注意这里依次指定要引用的Library Project的相对路径。

local.properties
这里是Android SDK的Home路径。

ant.properties
签名证书与认证信息设置。

完成以上内容,整个工程的构建脚本已经完成一半了。
下面要做的事情是,把MyProject下的,上面列出的4个脚本分别拷贝到LibraryProject1和LibraryProject2下。
注意,要根据Library Project的实际情况去修改project.properties的内容,主要是SDK版本,以及标示当前模块是Library Project,如下:

以上完成后,构建脚本就可以run起来了。
构建完成后,会在MyProject下生成一个bin目录,并且构建结果存储在这里。

注意:如果要定义混淆参数,请参考C:\android-sdk-windows\tools\ant\build.xml中关于混淆的声明:
很明显,它会去找proguard.config文件,这个文件就是混淆参数的定义。


二、自定义构建脚本来实现带有Library Project的构建
这种就比较复杂,但网上有篇文章做了详细的介绍,这里转载过来。
原文出处:http://www.cnblogs.com/qianxudetianxia/archive/2012/07/04/2573687.html
以下内容只截取了原文涉及Library Project构建的部分:

随着工程越来越复杂,项目越来越多,以及平台的迁移(我最近就迁了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,可以导入到手机上运行。


2 0
原创粉丝点击