Android项目自动批量打包之程序实现

  http://blog.sina.com.cn/s/blog_6d8459b901011ddc.html


因为需要记录不同渠道下apk的使用情况,每一个渠道对应的apk都需要附带自己的渠道号。而当你手动完成92个渠道的打包时,切腹的心都有了!!


现在的任务就是:实现项目的自动更改配置文件的渠道号,自动完成打包,签名工作。 

开始的解决方案是,使用ant完成自动打包。据说这个方案是可行的,尝试了N多次build.xml的配置,始终运行出错,以后再慢慢研究吧。

下面详细讲解我的实现方案:
首先普及下,如何手动实现一个android项目的编译,打包,签名的过程。
为了看到效果,先把eclipse的自动编译开关去掉。
新建一个Android工程。里面除了源码什么都没有了,eclipse也没有帮你编译生成R文件和class文件。
开启命令行:
第一步:使用aapt工具生成R文件
命令行进入工程目录:
aapt package -f -m -J gen\(指定R文件存放目录) -S res\ -M AndroidManifest.xml -I ../android-8/android.jar 
  参数的意思应该不用解释了。执行命令,会看到再gen/目录下生成了对应资源的R文件。
第二步:使用javac编译java文件
javac -encoding utf-8 -target 1.5 -d bin/ src/../../../*.java gen/*.java -bootclasspath ../{sdk-path}/android-8/android.jar 

第三步:class文件已经生成,使用dx工具将所有class打包生成dex文件。
dx.bat --dex --output=bin/classes.dex bin/classes/
如果有第三方jar包,需要将jar一并打入到dex文件中。 在命令后面再附带jar的路径。如:
dx.bat --dex --output=bin/classes.dex bin/classes/ libs/***.jar

第四步:dex已经有了,但这些都是java文件编译而成。所以还需要将对应的资源文件打包生成resources.ap_ 文件。依然使用aapt工具
aapt package -f -M AndroidManifest.xml -S res/ -A assets/ -I ../{sdk-path}/android-8/android.jar -F bin/resources.ap_
第五步:将resource和dex封装到一起。生成未签名apk文件。使用apkbuilder工具
apkbuilder bin/unsigned.apk -u -z bin/resources.ap_ -f bin/classes.dex -rf src/
如果引入了第三方jar包,还需要加入jar包的引用。在命令的最后加上: -rj libs/

第六步:对未签名的apk进行签名。使用jarsigner 工具
jarsigner -keystore {keystore path} -storepass *** -keypass {alias pass} -signedjar e:\signed.apk bin/unsigned.apk {alias name}

到此,手动对一个Android工程的编译,打包,签名就完成了。 而我的方案也就出来了。 每个不同的渠道包有一个不同的渠道属性。 可以通过文件流操作循环改变这个这个属性的值。 每次改变完成之后,在程序中调用命令,完成项目的打包签名工作。下面看具体实现:
我的项目中渠道号是写在strings.xml文件中。其实任何地方都可以,只要你的工程能引用到。
因为eclipse能够自动编译,所以我们省去了很多麻烦。不需要手动生成R文件,不需要手动编译java文件。 

因为需要替换strings文件中的字段,在strings中设置一个标记变量%channel%,当读取到这个标记时,将标记替换成指定的值。先将strings文件做一个临时备份。读取临时文件,读取的同时修改渠道号并将新的文件写入到res/values/strings.xml 目录下。(读取时,因为strings文件有很多中文,注意编码问题)
下面看具体代码实现:
public static final String PROJECT_PATH = "D:\\workspace\\Idea123_v3\\"; //工程的具体路径。后面很多地方会用到

public static void main(String[] args) { 
  int minChannel = 10, maxChannel = 20;   // 设置最小渠道,最大渠道号
   try { 
  Process process = Runtime.getRuntime().exec("D:\\android-sdk-windows\\platform-tools\\dx.bat --dex --output="+PROJECT_PATH+"bin\\classes.dex "+PROJECT_PATH+"bin\\classes\\   +" PROJECT_PATH"+libs\\pinyin4j-2.5.0.jar" ); process.waitFor(); process.destroy();   // 先完成class--》dex的打包。因为java文件未曾改变过,所以打包一次就够。
  replaceChannel(minChannel, maxChannel); // 替换渠道号
System.out.println("batch pack finished!"); 
  } catch (IOException e) { 
  e.printStackTrace(); 
  } catch (InterruptedException e) { e.printStackTrace(); } }
 


public static void replaceChannel(int minChannel, int maxChannel) {
try {
String outPath = PROJECT_PATH + "res\\values\\strings.xml"; //输出文件位置
String tempPath = "e:\\temp.xml"; //临时文件位置
BufferedReader reader;
BufferedWriter writer;
for (int channelid = minChannel; channelid <= maxChannel; ++channelid) {
String line = null;
reader = new BufferedReader(new FileReader(tempPath));
writer = new BufferedWriter(new FileWriter(outPath));
while ((line = reader.readLine()) != null) {
if (line.contains("%channel%"))
line = line.replace("%channel%", channelid + "");//设置渠道号
writer.write(line + "\r\n");
writer.flush();
}
writer.close();
reader.close();
generatePackage(channelid); // 一次渠道号的更改完成。可以进行打包了。
}
} catch (Exception e) {
e.printStackTrace();
}
}


public static void generatePackage(int channelid) {
try {
Process process = null ;
process = Runtime.getRuntime()
.exec("D:\\android-sdk-windows\\platform-tools\\aapt package -f -M "+PROJECT_PATH+"AndroidManifest.xml -S "+PROJECT_PATH+"res -A "+PROJECT_PATH+"assets -I D:\\android-sdk-windows\\platforms\\android-8\\android.jar -F "+PROJECT_PATH+"bin\\resources.ap_"); // 将资源文件打包resources.ap_
process.waitFor();
process.destroy();
process= Runtime.getRuntime()
.exec("D:\\android-sdk-windows\\tools\\apkbuilder.bat "+PROJECT_PATH+"bin\\Idea123_unsigned.apk -u -z "+PROJECT_PATH+"bin\\resources.ap_ -f "+PROJECT_PATH+"bin\\classes.dex -rf "+PROJECT_PATH+"src -rj "+ PROJECT_PATH+"\\libs"); // 生成未签名的apk
process.waitFor();
process.destroy();
process = Runtime.getRuntime()
.exec("jarsigner -keystore E:\\idea123 -storepass *** -keypass *** -signedjar E:\\test_apk\\Idea123_"+channelid+".apk "+PROJECT_PATH+"bin\\Idea123_unsigned.apk idea123"); //对apk进行签名
process.waitFor();
process.destroy();
System.out.println(channelid+" finished..."); // 一条渠道的打包完成。文件会输出到指定目录
} catch (Exception e) {
e.printStackTrace();
}
}

ok! 批量打包的简单实现就此完成。代码有待优化,欢迎指正。


这短短一个月就像历经了一年那么漫长。生活的变化总是发生的太快太突然,几乎让我无暇应接。最无奈的是发现所有的事决定权都不在自己手上,你只能被迫接受。唯一能安慰自己的就是,既然过去的不能被改变,那我必须轻松的面对将来。

你可能感兴趣的:(Android项目自动批量打包之程序实现)