由于公司运维需要,每次上线都需要打几十个市场版的apk文件,主要修改就只有一处,就是manifest中的一个市场标识属性,因为对ant批量打包不了解,一直使用手动打包,感觉既枯燥机械又低效,由于厌倦了手动打包,感觉再有这么多好工具的情况下,居然还手动打apk,未免显的太原始了吧,闲来无事,就研究了一下如何使用ant来批量打apk
因为已经有了使用ant打一个签名apk的基础,见博客http://blog.csdn.net/guonanyun/article/details/7382843(android使用ant自动化打包),所以一开始的想法是使用ant中的循环语法:循环遍历市场标识文件,依次执行相同命令,发现ant似乎对循环命令支持不够,再加上本身对ant不了解,只是或多或少的接触过一些,从来没有深入的研究过,只找到一种在ant中使用javascript语法来实现循环的语法,从网上找的测试程序本身是OK的,但是要想实现我的需求,需要熟悉运用ant api,所以只好作废,此时陷入了僵局
后来突发奇想,既然ant可以调用java代码,那反过来java是不是也也可以调用ant命令呀,不查不知道,一查吓一跳,网上果然有人写出了使用java调用ant脚本http://hilary3113.iteye.com/blog/1155517的博客,似乎看到了胜利的曙光.马上果断的把例子代码粘贴过来,引入相关的jar文件,自己运行一下.发现运行报错,是如下错误信息
E:\other\mapbarmaps_5.6_backup\build.xml:83: import requires support in ProjectHelper(ps:因为我的build.xml文件引入了android自身的build.xml)
后来将该错误信息作为关键字进行google搜索,找到如下博客http://yxhcquedu.iteye.com/blog/861110,也是一个测试demo,拷贝代码并做相应修改,发现出现了新的报错,
com.sun.tools.javac.Main is not on the classpath.
Perhaps JAVA_HOME does not point to the JDK.
It is currently set to "C:/Program Files/Java/jre1.5.0_11"
继续google搜索,发现网上给的比较多的解答是这种方式:http://blog.csdn.net/lhjlhj123123/article/details/5607106,尝试后发现仍然不好使,并且多次确信java环境变量是OK的,后来看了一下java project的依赖jar包,发现都是jre下面lib中的jar文件
说明一下,该图中jdk\lib下的jar包是后来引入的,一开始是没有的
会不是是引入jar包的问题呢?凡事都要尝试一下呀,.实践才是检验真理的唯一标准啊!把C:\Java\jdk1.6.0_21\lib下的jar包引入后,再次运行,OK,虽然还是不明白ant为啥会出现这种问题,但是问题总算是得到了圆满的解决,以后再去知其所以然吧
到此,使用java调用ant脚本打包算是可以正常使用了
下面,就开始考虑如何来批量打包了
首先明确一下项目需求:修改mainfest.xml中的一个属性,打成apk
其次执行步骤依次如下:
1)先修改manifest文件,考虑到若直接把占位符放到manifest文件中,第一次替换成市场标识后,还是需要再替换回占位符的,这相当于多做了一步操作,联想到昨天看的使用ant脚本替换文件中某项的例子,发现先声明一个manifest临时文件,读取该文件中的内容,替换占位符后,将新的内容再次写入manifest中是个不错的方案,尝试一下证明是OK的
ps:上述步骤使用了java中文件操作的相关知识,由于io基础不扎实,为了把重点放到ant打包上,所以继续奉行拿来主义,从网上找了java修改文件内容的相关博客,http://www.iteye.com/problems/56425,直接拷贝代码使用
2)初始化ant项目的相关配置并执行打包命令
3)将打完的apk进行重命名(加入市场标识的后缀)和拷贝操作
4)继续遍历市场标识数组或集合,依次执行步骤1),2),3)
至此,ant批量打包总算是可以正常使用了
如果要抽样测试打出的apk是否已经修改了manifest文件中指定的属性,可以反编译aok后查看,请参考如下博客:
Android APK反编译详解(附图)http://blog.csdn.net/sunboy_2050/article/details/6727581
ps :后期熟悉ant的话,可以使用纯ant脚本或者使用另一种更好的自动化打包工具(maven)
关键代码如下:
package com.cn.ant;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
public class AntTest {
private Project project;
public void init(String _buildFile, String _baseDir) throws Exception {
project = new Project();
project.init();
DefaultLogger consoleLogger = new DefaultLogger();
consoleLogger.setErrorPrintStream(System.err);
consoleLogger.setOutputPrintStream(System.out);
consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
project.addBuildListener(consoleLogger);
// Set the base directory. If none is given, "." is used.
if (_baseDir == null)
_baseDir = new String(".");
project.setBasedir(_baseDir);
if (_buildFile == null)
_buildFile = new String(projectBasePath + File.separator + "build.xml");
//ProjectHelper.getProjectHelper().parse(project, new File(_buildFile));
// 关键代码
ProjectHelper.configureProject(project, new File(_buildFile));
}
public void runTarget(String _target) throws Exception {
// Test if the project exists
if (project == null)
throw new Exception(
"No target can be launched because the project has not been initialized. Please call the 'init' method first !");
// If no target is specified, run the default one.
if (_target == null)
_target = project.getDefaultTarget();
// Run the target
project.executeTarget(_target);
}
// private final static ArrayList<String> flagList = new ArrayList<String>(); //也可以使用集合,不过需要手动添加项
private final static String[] flagList = new String[]{"htc","moto","oppo"};//此处初始化市场标识
private final static String projectBasePath = ""; //项目的根目录,需要配置
private final static String copyApkPath = ""; //要改名后拷贝的目录,需要配置
private final static String placeHolder = ""; //占位符,需要配置
public static void main(String args[]) {
//flagList.add("htc");
//flagList.add("moto");
//flagList.add("oppo");
try {
System.out.println("---------ant批量自动化打包开始----------");
for(String flag : flagList){
//先修改manifest文件:读取临时文件中的@market@修改为市场标识,然后写入manifest.xml中
String tempFilePath = projectBasePath + File.separator + "AndroidManifest.xml.temp";
String filePath = projectBasePath + File.separator + "AndroidManifest.xml";
write(filePath,read(tempFilePath, flag));
//执行打包命令
AntTest mytest = new AntTest();
mytest.init(
projectBasePath + File.separator + "build.xml",
projectBasePath);
mytest.runTarget("clean");
mytest.runTarget("release");
//打完包后执行重命名加拷贝操作
//String flag = "htc";
File file = new File(projectBasePath + File.separator +"bin"+File.pathSeparator+"MainActivity-release.apk");//bin目录下签名的apk文件
file.renameTo(new File(copyApkPath + File.separator + "MainActivity_"+flag+".apk"));
}
System.out.println("---------ant批量自动化打包结束----------");
} catch (Exception e) {
e.printStackTrace();
System.out.println("---------ant批量自动化打包中发生异常----------");
}
}
public static String read(String filePath,String replaceStr) {
BufferedReader br = null;
String line = null;
StringBuffer buf = new StringBuffer();
try {
// 根据文件路径创建缓冲输入流
br = new BufferedReader(new FileReader(filePath));
// 循环读取文件的每一行, 对需要修改的行进行修改, 放入缓冲对象中
while ((line = br.readLine()) != null) {
// 此处根据实际需要修改某些行的内容
if (line.contains(placeHolder)) {
line = line.replace(placeHolder, replaceStr);
buf.append(line);
}
else {
buf.append(line);
}
buf.append(System.getProperty("line.separator"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
if (br != null) {
try {
br.close();
} catch (IOException e) {
br = null;
}
}
}
return buf.toString();
}
/**
* 将内容回写到文件中
*
* @param filePath
* @param content
*/
public static void write(String filePath, String content) {
BufferedWriter bw = null;
try {
// 根据文件路径创建缓冲输出流
bw = new BufferedWriter(new FileWriter(filePath));
// 将内容写入文件中
bw.write(content);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
bw = null;
}
}
}
}
}