往往要对应二三十个渠道,按照正常方法一个一个的去生成不同渠道包的应用,不仅浪费了时间,而且大大降低了效率.
上一篇讲到使用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));
<SPAN style="COLOR: #ff0000">// 关键代码</SPAN>
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);
}
<SPAN style="COLOR: #ff0000">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常量一致
如: <meta-data android:value="@market@" android:name="UMENG_CHANNEL"/>
4. Build.xml中:
<project name="XXX" default="help">,XXX必须为Android工程名称.
如果机器没有配置过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 <methods>;
}
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
-keep classes with members class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keep classes with members class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-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 <fields>;
}
# 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<DIR> search path for import statements.
-d<FILE> generate dependency file.
-p<FILE> file created by --preprocess to import.
-o<FOLDER> base output folder for generated files.
-b fail when trying to compile a parcelable.
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 <methods>; -> 所有native的方法不能去混淆.
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
-->某些构造方法不能去混淆
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-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中,再根据自己的需要进行编辑即可。