当前位置: 首页 > news >正文

(转)Android学习系列(31)--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。
    代码模板如下:

?
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 >
         < 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:

?
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,可以导入到手机上运行。

10. 多渠道打包
    目前主流的多渠道打包方法是在AndroidManifest.xml中的Application下添加一个渠道元数据节点。
    比如,我使用的是友盟统计,它配置AndroidManifest.XML添加下面代码:

?
1
2
3
4
< 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命令来实现替换。
    替换命令:

?
1
2
3
4
#-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了,其他属性类似。
    所以,在工程下面这条命令会生成:

?
1
2
3
#结合前面讲打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):

?
1
2
3
4
5
6
7
8
9
10
11
#定义市场列表,以空格分割
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:

?
1
2
3
4
5
6
7
8
9
10
# ./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文件:

?
1
2
3
4
5
6
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为如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/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脚本如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/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

  执行该脚本:

?
1
2
3
4
5
6
7
8
9
10
11
12
# ./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产品发布的重要环节,能大大节省人力和出错的概率。
    最近写博客的步伐慢了下来了,我决定加大力度,多写几篇。
    其实早就想做这一块的内容了,一直不得空,或者说心里不得空,前几天刚完全格式化硬盘迁移到ubuntu(抛弃了双系统), 最近又开始着手准备一套《使用vim开发android》视频教程,人又不能太过于沉溺于舒适区域,我决定逼迫一下自己,终于逼出了这篇文章。
    真是不容易,今天好热啊,火,你就是火,到处都是火 ... ...
    本人项目中的具体应用示例:https://github.com/openproject/world

转自:http://www.cnblogs.com/qianxudetianxia/archive/2012/07/04/2573687.html

转载于:https://www.cnblogs.com/greywolf/archive/2013/04/19/3030393.html

相关文章:

  • ASP.NET 用户配置 Part.2(SQLProfileProvider)
  • Mysql 主从复制实现原理
  • 网站整体变灰色
  • oracle中 all any in的用法
  • HDU-4473 Exam 数学分析
  • 通过iTunes检测更新,使用NSJSONSerialization解析JSON格式版本信息
  • jQuery页面滚动图片等元素动态加载实现
  • Java 聚合 组合 is-a has-a 关系学习
  • ZenCoding
  • nginx下使用Django
  • 五款超实用的开源SVG工具
  • solr dataimport 数据导入源码分析(十二)
  • secucrt相关技巧
  • [经典语录][电影]全民情敌/Hitch
  • iPhone私有API学习笔记
  • ES6指北【2】—— 箭头函数
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • ES6核心特性
  • Javascript设计模式学习之Observer(观察者)模式
  • Joomla 2.x, 3.x useful code cheatsheet
  • jQuery(一)
  • Laravel 菜鸟晋级之路
  • MySQL几个简单SQL的优化
  • Netty源码解析1-Buffer
  • Python - 闭包Closure
  • 排序算法学习笔记
  • 前端之Sass/Scss实战笔记
  • 如何利用MongoDB打造TOP榜小程序
  • 微信支付JSAPI,实测!终极方案
  • 移动端 h5开发相关内容总结(三)
  • UI设计初学者应该如何入门?
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • # 学号 2017-2018-20172309 《程序设计与数据结构》实验三报告
  • $GOPATH/go.mod exists but should not goland
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (十三)Flask之特殊装饰器详解
  • (四)库存超卖案例实战——优化redis分布式锁
  • (算法)Travel Information Center
  • (转)winform之ListView
  • (状压dp)uva 10817 Headmaster's Headache
  • .[backups@airmail.cc].faust勒索病毒的最新威胁:如何恢复您的数据?
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .set 数据导入matlab,设置变量导入选项 - MATLAB setvaropts - MathWorks 中国
  • @kafkalistener消费不到消息_消息队列对战之RabbitMq 大战 kafka
  • @ResponseBody