往往要对应二三十个渠道,按照正常方法一个一个的去生成不同渠道包的应用,不仅浪费了时间,而且大大降低了效率.
上一篇讲到使用Ant进行Zip/Tar包的解压缩,实际上Ant工具不仅仅具有此类功能,它更强大的地方在于自动化调用程序完成项目的编译,打包,测试等. 类似于C语言中的make脚本完成这些工作的批处理任务. 不同于MakeFile的是,Ant是纯Java编写的,因此具有很好的跨平台性.
在此我主要讲下如何自动构建工具Ant, 对应用进行批量打包, 生成对应不同市场的应用:
首先分别看一下用于打包的Java工程AntTest和需要被打包进行发布的Android工程结构:
market.txt里保存需要打包的市场标识,如:
youmeng
gfan
.......
此文件里自行根据需求添加渠道名称.
然后看一下实现批量打包AntTest类中的内容:
注意:红色标注部分需要进行修改:
[java]
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.text.SimpleDateFormat;
import java.util.Calendar;
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 String projectBasePath = "D:\\android\\workspace3\\XXX";//要打包的项目根目录
private final static String copyApkPath = "D:\\android\\apktest";//保存打包apk的根目录
private final static String signApk = "XXX-release.apk";//这里的文件名必须是准确的项目名!
private final static String reNameApk = "XXX_";//重命名的项目名称前缀(地图项目不用改)
private final static String placeHolder = "@market@";//需要修改manifest文件的地方(占位符)
< /SPAN>
public static void main(String args[]) {
long startTime = 0L;
long endTime = 0L;
long totalTime = 0L;
Calendar date = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:HH:mm:ss");
try {
System.out.println("---------ant批量自动化打包开始----------");
startTime = System.currentTimeMillis();
date.setTimeInMillis(startTime);
System.out.println("开始时间为:" + sdf.format(date.getTime()));
BufferedReader br = new BufferedReader(new FileReader("market.txt"));
String flag = null;
while ((flag = br.readLine()) != null) {
// 先修改manifest文件:读取临时文件中的@market@修改为市场标识,然后写入manifest.xml中
String tempFilePath = projectBasePath + File.separator
+ "AndroidManifest.xml.temp";
String filePath = projectBasePath + File.separator
+ "AndroidManifest.xml";
write(filePath, read(tempFilePath, flag.trim()));
// 执行打包命令
AntTest mytest = new AntTest();
mytest.init(projectBasePath + File.separator + "build.xml",
projectBasePath);
mytest.runTarget("clean");
mytest.runTarget("release");
// 打完包后执行重命名加拷贝操作
File file = new File(projectBasePath + File.separator + "bin"
+ File.separator + signApk);// bin目录下签名的apk文件
File renameFile = new File(copyApkPath + File.separator + reNameApk
+ flag + ".apk");
boolean renametag = file.renameTo(renameFile);
System.out.println("rename------>"+renametag);
System.out.println("file ------>"+file.getAbsolutePath());
System.out.println("rename------>"+renameFile.getAbsolutePath());
}
System.out.println("---------ant批量自动化打包结束----------");
endTime = System.currentTimeMillis();
date.setTimeInMillis(endTime);
System.out.println("结束时间为:" + sdf.format(date.getTime()));
totalTime = endTime - startTime;
System.out.println("耗费时间为:" + getBeapartDate(totalTime));
} catch (Exception e) {
e.printStackTrace();
System.out.println("---------ant批量自动化打包中发生异常----------");
endTime = System.currentTimeMillis();
date.setTimeInMillis(endTime);
System.out.println("发生异常时间为:" + sdf.format(date.getTime()));
totalTime = endTime - startTime;
System.out.println("耗费时间为:" + getBeapartDate(totalTime));
}
}
/**
* 根据所秒数,计算相差的时间并以**时**分**秒返回
*
* @param d1
* @param d2
* @return
*/
public static String getBeapartDate(long m) {
m = m / 1000;
String beapartdate = "";
int nDay = (int) m / (24 * 60 * 60);
int nHour = (int) (m - nDay * 24 * 60 * 60) / (60 * 60);
int nMinute = (int) (m - nDay * 24 * 60 * 60 - nHour * 60 * 60) / 60;
int nSecond = (int) m - nDay * 24 * 60 * 60 - nHour * 60 * 60 - nMinute
* 60;
beapartdate = nDay + "天" + nHour + "小时" + nMinute + "分" + nSecond + "秒";
return beapartdate;
}
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;
}
}
}
}
}
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.text.SimpleDateFormat;
import java.util.Calendar;
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 String projectBasePath = "D:\\android\\workspace3\\XXX";//要打包的项目根目录
private final static String copyApkPath = "D:\\android\\apktest";//保存打包apk的根目录
private final static String signApk = "XXX-release.apk";//这里的文件名必须是准确的项目名!
private final static String reNameApk = "XXX_";//重命名的项目名称前缀(地图项目不用改)
private final static String placeHolder = "@market@";//需要修改manifest文件的地方(占位符)
public static void main(String args[]) {
long startTime = 0L;
long endTime = 0L;
long totalTime = 0L;
Calendar date = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:HH:mm:ss");
try {
System.out.println("---------ant批量自动化打包开始----------");
startTime = System.currentTimeMillis();
date.setTimeInMillis(startTime);
System.out.println("开始时间为:" + sdf.format(date.getTime()));
BufferedReader br = new BufferedReader(new FileReader("market.txt"));
String flag = null;
while ((flag = br.readLine()) != null) {
// 先修改manifest文件:读取临时文件中的@market@修改为市场标识,然后写入manifest.xml中
String tempFilePath = projectBasePath + File.separator
+ "AndroidManifest.xml.temp";
String filePath = projectBasePath + File.separator
+ "AndroidManifest.xml";
write(filePath, read(tempFilePath, flag.trim()));
// 执行打包命令
AntTest mytest = new AntTest();
mytest.init(projectBasePath + File.separator + "build.xml",
projectBasePath);
mytest.runTarget("clean");
mytest.runTarget("release");
// 打完包后执行重命名加拷贝操作
File file = new File(projectBasePath + File.separator + "bin"
+ File.separator + signApk);// bin目录下签名的apk文件
File renameFile = new File(copyApkPath + File.separator + reNameApk
+ flag + ".apk");
boolean renametag = file.renameTo(renameFile);
System.out.println("rename------>"+renametag);
System.out.println("file ------>"+file.getAbsolutePath());
System.out.println("rename------>"+renameFile.getAbsolutePath());
}
System.out.println("---------ant批量自动化打包结束----------");
endTime = System.currentTimeMillis();
date.setTimeInMillis(endTime);
System.out.println("结束时间为:" + sdf.format(date.getTime()));
totalTime = endTime - startTime;
System.out.println("耗费时间为:" + getBeapartDate(totalTime));
} catch (Exception e) {
e.printStackTrace();
System.out.println("---------ant批量自动化打包中发生异常----------");
endTime = System.currentTimeMillis();
date.setTimeInMillis(endTime);
System.out.println("发生异常时间为:" + sdf.format(date.getTime()));
totalTime = endTime - startTime;
System.out.println("耗费时间为:" + getBeapartDate(totalTime));
}
}
/**
* 根据所秒数,计算相差的时间并以**时**分**秒返回
*
* @param d1
* @param d2
* @return
*/
public static String getBeapartDate(long m) {
m = m / 1000;
String beapartdate = "";
int nDay = (int) m / (24 * 60 * 60);
int nHour = (int) (m - nDay * 24 * 60 * 60) / (60 * 60);
int nMinute = (int) (m - nDay * 24 * 60 * 60 - nHour * 60 * 60) / 60;
int nSecond = (int) m - nDay * 24 * 60 * 60 - nHour * 60 * 60 - nMinute
* 60;
beapartdate = nDay + "天" + nHour + "小时" + nMinute + "分" + nSecond + "秒";
return beapartdate;
}
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;
}
}
}
}
}
然后是Android工程中需要进行修改的部分:
1. 修改local.properties中的sdk根目录:
sdk.dir=D:\\android\\android-sdk-windows-r17\\android-sdk-windows-r17
2. 修改ant.properties中签名文件的路径和密码(如果需要)
key.store=D:\\android\\mykeystore
key.store.password=123456
key.alias=mykey
key.alias.password=123456
3. 修改AndroidManifest.xml.temp
拷贝AndroidManifest.xml一份,命名为AndroidManifest.xml.temp
将需要替换的地方改为占位符,需与打包工程AntTest中的placeHolder常量一致
如:
4. Build.xml中:
如果机器没有配置过Ant环境变量,可根据如下步骤进行配置:
ANT环境变量设置:
Windows下ANT用到的环境变量主要有2个,ANT_HOME 、PATH。
设置ANT_HOME指向ant的安装目录。
设置方法:
ANT_HOME = D:/apache_ant_1.7.0
将%ANT_HOME%/bin; %ANT_HOME%/lib添加到环境变量的path中。
设置方法:
PATH = %ANT_HOME%/bin; %ANT_HOME%/lib
/
prog///
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# If you want to enable optimization, you should include the
# following:
# -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# -optimizationpasses 5
# -allowaccessmodification
#
# Note that you cannot just include these flags in your own
# configuration file; if you are including this file, optimization
# will be turned off. You'll need to either edit this file, or
# duplicate the contents of this file and remove the include of this
# file from your project's proguard.config path property.
-keep attributes *Annotation*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgent
-keep public class * extends android.preference.Preference
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment
-keep public class com.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keep classes with membernames class * {
native
}
-keep public class * extends android.view.View {
public
public
public
public void set*(...);
}
-keep classes with members class * {
public
}
-keep classes with members class * {
public
}
-keep class members class * extends android.app.Activity {
public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keep class members enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keep class members class **.R$* {
public static
}
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontwarn android.support.**
-keep class com.baidu.mapapi.** { *; }
-keep class com.baidu.location.** { *; }
-keep class com.android.otherpackage.** { *; }//某个包下面的类混淆
//android 手动打包
ndroid 命令行手动编译打包过程图
【详细步骤】:
1使用aapt生成R.java类文件:
例:
E:\androidDev\android-sdk-windows2.2\tools>E:\androidDev\android-sdk-windows2.2\platforms\android-3\tools\aapt.exe package -f -m -J E:\androidDev\AndroidByread\gen -S res -I E:\androidDev\android-sdk-windows2.2\platforms\android-3\android.jar -M AndroidManifest.xml
其中 -f -m -J E:\androidDev\AndroidByread\gen 代表按覆盖的形式在gen目录下生成带包路径的R.java,-S res指定资源文件 ,-I E:\androidDev\android-sdk-windows2.2\platforms\android-3\android.jar 指定使用的android类,-M AndroidManifest.xml指定程序的配置文件
aapt Usage:
2使用android SDK提供的aidl.exe把.aidl转成.java文件:
usage: aidl OPTIONS INPUT [OUTPUT]
aidl --preprocess OUTPUT INPUT...
OPTIONS:
-I
INPUT:
An aidl interface file.
OUTPUT:
The generated interface files.
3第三步 编译.java类文件生成class文件:
例:E:\Androiddev\AndroidByread>javac -encoding GB18030 -target 1.5 -bootclasspath E:\Androiddev\android-sdk-windows2.2\platforms\android-3\android.jar -d bin src\com\byread\reader\*.java gen\com\byread\reader\R.java
4使用android SDK提供的dx.bat命令行脚本生成classes.dex文件:
例:
E:\Androiddev\AndroidByread>E:\Androiddev\r\android-sdk-windows2.2\platforms\android-3\tools\dx.bat --dex --output=E:\Androiddev\AndroidByread\bin\classes.dex E:\Androiddev\AndroidByread\bin\classes
其中classes.dex为生成的目标文件,E:\Androiddev\AndroidByread\bin\classes为class文件所在目录
5使用Android SDK提供的aapt.exe生成资源包文件(包括res、assets、androidmanifest.xml等):
E:\Andorid\AndroidByread>E:\Androiddev\android-sdk-windows2.2\platforms\android-3\tools\aapt.exe package -f -M AndroidManifest.xml -S res -A assets -I E:\Androiddev\android-sdk-windows2.2\platforms\android-3\android.jar -F bin\byreadreader
将AndroidManifest.xml,res和assets文件夹中的资源文件打包生成byreadreader,用法参见1
6第六步 生成未签名的apk安装文件:
apkbuilder ${output.apk.file} -u -z ${packagedresource.file} -f ${dex.file} -rf ${source.dir} -rj ${libraries.dir}
例: E:\Adnroiddev\AndroidByread>E:\Adnroiddev\android-sdk- windows2.2\tools\apkbuilder.bat E:\Adnroiddev\byreadreader.apk –v -u -z E:\Adnroiddev\AndroidByread\bin\byreadreader -f E:\Adnroiddev\AndroidByread\bin\class.dex -rf E:\Adnroiddev\AndroidByread\src 其中E:\Adnroiddev\byreadreader.apk为生成的apk ,-z E:\Adnroiddev\AndroidByread\bin\byreadreader为资源包,E:\Adnroiddev \AndroidByread\bin\class.dex为类文件包
7使用jdk的jarsigner对未签名的包进行apk签名: use jarsigner jarsigner -keystore ${keystore} -storepass ${keystore.password} -keypass ${keypass} -signedjar ${signed.apkfile} ${unsigned.apkfile} ${keyalias} 例如: E:\Adnroiddev\android-sdk-windows2.2\tools>jarsigner –keystore E:\Adnroiddev\eclipse3.5\bbyread.keystore -storepass byread002 -keypass byread002 -signedjar E:\Adnroiddev\byread.apk E:\Adnroiddev\byreadreader.apk byread 其中–keystore E:\Adnroiddev\eclipse3.5\bbyread.keystore 为密钥文件 -storepass byread002为密钥文件密码 byread 为密钥别名 -keypass byread002为密钥别名密码,-signedjar E:\Adnroiddev\byread.apk为签名后生成的apk文件 E:\Adnroiddev\byreadreader.apk为未签名的文件。
参 考:http://asantoso.wordpress.com/2009/09/15/how-to-build-android- application-package-apk-from-the-command-line-using-the-sdk-tools-continuously-integrated-using-cruisecontrol/
///
target=android-8
proguard.config=proguard.cfg
Eclipse会通过此配置在工程目录生成proguard.cfg文件
2 . 生成keystore (如已有可直接利用)
按照下面的命令行 在D:\Program Files\Java\jdk1.6.0_07\bin>目录下,输入keytool -genkey -alias android.keystore -keyalg RSA -validity 100000 -keystore android.keystore
参数意义:-validity主要是证书的有效期,写100000天;空格,退格键 都算密码。
命令执行后会在D:\Program Files\Java\jdk1.6.0_07\bin>目录下生成 android.keystore文件。
3. 在Eclipce的操作
File -> Export -> Export Android Application -> Select project -> Using the existing keystore , and input password -> select the destinationAPK file
经过混淆后的源代码,原先的类名和方法名会被类似a,b,c。。。的字符所替换,混淆的原理其实也就是类名和方法名的映射。
但4大组件并没有混淆(所有在清单文件定义的组件不能被混淆),因为系统需要通过清单文件来查找和运行应用程序。
proguard.cfg 文件代码解读
-optimizationpasses 5 ->设置混淆的压缩比率 0 ~ 7
-dontusemixedcaseclassnames -> Aa aA
-dontskipnonpubliclibraryclasses ->如果应用程序引入的有jar包,并且想混淆jar包里面的class
-dontpreverify
-verbose ->混淆后生产映射文件 map 类名->转化后类名的映射
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* ->混淆采用的算法.
-keep public class * extends android.app.Activity ->所有activity的子类不要去混淆
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native
}
-keepclasseswithmembers class * {
public
-->某些构造方法不能去混淆
}
-keepclasseswithmembers class * {
public
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * { -> 枚举类不能去混淆.
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { -> aidl文件不能去混淆.
public static final android.os.Parcelable$Creator *;
}
/
在新版本的ADT创建项目时,混码的文件不再是proguard.cfg,而是project.properties和proguard-project.txt。
如果需要对项目进行全局混码,只需要进行一步操作:
将project.properties的中
“# proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt”的“#”去掉就可以了。
如果有一些代码不能被混淆,比如需要加入了so文件,需要调用里面的方法,那么调用JNI访问so文件的方法就不能被混码。在导出的时候,可能不会报错。但是在手机上运行的时候,需要调用so文件的时候,就会报某某方法无法找到。这个时候就需要用到proguard-project.txt。
在老版本中,创建项目的时候,会给出proguard.cfg,但是在的新版中创建项目则不会有任何提示。这个时候需要只要将proguard.cfg的内容加入到proguard-project.txt中,再根据自己的需要进行编辑即可。
///
目前国内的安卓渠道有几百家,我们要根据不同的渠道打不同渠道的apk来统计每个渠道带来的用户数,统计每个渠道用户的存活率和活跃度等等信息,但是手动对每个渠道的APK进行签名打包实在是让人感到厌烦且低效,这时我们需要一个全自动化的打包工具----ant。Android SDK的tools中已经包括了ant的打包工具,那么我们该如何运用它达到我们的要求呢?之前卤煮做这块的时候也参考了很多前辈的文章,首先向前辈们致敬,下面说下卤煮的方法,有什么不对的地方或者疑问欢迎大家留言交流。(以下步骤均在MAC系统下完成)
一.打包前准备工作
1.首先确定你的JDK版本为1.6!
2.在AndroidManifest.xml中application标签下添加一个用来识别渠道的标签:
data android:name="qudao" android:value="channel" />
3.为了让ant支持循环功能,我们要在Android SDK/tools/lib下放一个ant-contrib-1.0b3.jar包
4.项目中放置第三方jar包的文件夹必须叫libs而不是lib
二.build.xml等文件的生成和配置
1.通过终端(cmd)命令自动生成build.xml和local.properties两个文件,方法如下:
/tools/android update project -p -t
例如:
/Users/moushou/Downloads/AndroidSDK/tools/android update project -p /Users/moushou/Documents/workspace/HelloWorld -t 14
其中
执行完成截图如下:
执行完成后,Refresh你的项目就会发现项目的根目录下多了两个文件:build.xml和local.properties
其中local.properties的内容是:
# This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must *NOT* be checked into Version Control Systems, # as it contains information specific to your local configuration. # location of the SDK. This is only used by Ant # For customization when using a Version Control System, please read the # header note. sdk.dir=/Users/moushou/Downloads/AndroidSDK
project.properties的内容如下:
# This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system use, # "ant.properties", and override values to adapt the script to your # project structure. #proguard.config=proguard.cfg # Project target. target=Google Inc.:Google APIs:14 proguard.config=proguard.cfg
项目的目录结构如下图所示:
注:project.properties中target=GoogleInc.:GoogleAPIs:14代表所使用的SDK的版本,可进行手动修改。
2.手动为项目新建一个File,该文件名为:ant.properties,创建完成项目的目录结构如下图:
创建完成后在ant.properties中添加如下内容:
key.store=key.alias= key.store.password= key.alias.password= market_channels=xx,yy,zz app_version=1_0_build_0
例如:
key.store=/Users/moushou/Desktop/qianming key.alias=meilihuaduo key.store.password=123456xx key.alias.password=123456xx market_channels=anzhuoshichang,jifengshichang,baiduyingyongzhongxin app_version=1_0_build_0
其中:
keystore为签名文件的全路径。
key.alias为签名需要使用的私钥。
key.store.password为私钥库的密码。
key.alias.password为私钥的密码。
market_channels为渠道集合。
app_version为apk的版本(此字段可根据自己喜好编写)。
--------------------------------------------------------------------------------------------------
至此,除build.xml外,其余文件配置完成,夜深人静的让我有些犯困啦,明天会把build.xml中使用for循环替换AndroidManifest.xml中渠道value的方法分享给大家。
三.下面我来说下build.xml的编写方法:
1.修改build.xml的第二行,修改方法如下:
default="release">
其中name为你项目的名称,default设置为release。
2.循环替换AndroidManifest.xml中qudao的value值并进行自动签名打包,方法如下:
1 <import file="${sdk.dir}/tools/ant/build.xml" /> 23 4 5 16 17*********************** make channel ${channel} 6 78 match='channel' 9 replace='${channel}' 10 byline="false" 11 encoding="utf-8" 12 /> 13 14 15 18 19 2021 25 26*********************** replacemanifest 2223 24 27 30 31*********************** savemanifest 2829 32 34 3533 36 37 38 39
其中:
1.out.unaligned.dir的value值为apk输出文件夹的绝对路径,文件夹采用HelloWorld结合app_version命名,app_version为ant.properties中的app_version
2.out.unaligned.file的location为apk最终的输出路径,apk命名采用HelloWorld加app_version加当前的channel加android方式
3.说一下打包的过程:
(1)第36行make_channels的target是ant的入口,该target中使用foreach循环调用名为make_one_channels的target(第17行)并把market_channels集合中的每个值替换给channel
(2)make_one_channels的target指定了每次打包的过程:
savemanifest:打包前先将原始的AndroidManifest.xml复制到与项目同一层级目录下的temp下build下META-INF中
modify_update_file:匹配到AndroidManifest.xml中的channel并将其替换
release:自动编译加签名
replacemanifest:删除AndroidManifest.xml,将temp/build/META-INF中的原始AndroidManifest.xml复制回项目根目录下
deletebin:删除bin文件(注:这步很重要,否则只能打出一个渠道的APK,当时做这块的时候碰到的问题)
4.第35行taskdef标签下的classpath是ant-contrib-1.0b3.jar的绝对路径
四.打包方法的使用
打开终端(cmd),执行:
cd /Users/moushou/Documents/workspace/HelloWorld
然后执行:
ant make_channels
此时,打包就开始进行啦!当出现BUILD SUCCESSFUL代表打包成功!如下图所示:
此时你会发现你输出的文件夹中多了三个APK,如下图:
注:1.每次打包前一定要删除掉temp/build/META-INF中的AndroidManifest.xml,特别是在给不同项目做打包时
2.打包前请检查AndroidManifest.xml中qudao的value值是否为channel,特别是打包失败后再次重新打包的时候一定要将value值改为channel
3.如果打包时出现Cannot recover key错误导致BUILD FAILD的话,请检查ant.properties中
key.alias.password的值后面是否有多余的空格!有的话请把空格删除掉!
五.在代码中获取渠道值,方法如下:
try { ApplicationInfo appInfo = getPackageManager().getApplicationInfo (getPackageName(),PackageManager.GET_META_DATA); qudao = appInfo.metaData.getString("qudao"); } catch (NameNotFoundException e) { e.printStackTrace(); }
//自己测试的 demo/
ant-contrib-1.0b3.jar包 放入资源下载中
1.ant.properties 内容:注销的内容在build.xml中有介绍
key.store=E:/programe_resource/test_keystore/test_Android
key.alias=test
#key.store.password,key.alias.password is permit null,but when ant build should input password
#key.store.password=paidui
#key.alias.password=paidui
market_channels=anzhuoshichang,jifengshichang,baiduyingyongzhongxin
app_version=1_0_build_0
2.build.xml 内容:
3.local.properties内容
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
# location of the SDK. This is only used by Ant
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=E:\\develop_tools\\android\\android-sdk-windows
4.proguard-project.txt 内容,可以自己去修改
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
5.project.properties内容
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-11