首先,需要准备的工作:
1 用户协议(可以是本地html资源,也可以是通过webview调用远程url链接地址)。
2 签名文件(打包签名文件,可以是公司以前这个项目的签名文件)。
3 程序logo图标。
4 其他东西(版本代号,应用截图,宣传文案,宣传视频,合作首发相关信息)。
需要配置的项目:
1 清理日志调用(log日志如果发版时不清处,影响应用性能),版本代号,,版本名称。
2 编译程序,签名程序(签名文件一定要保留,记住是一定)。
3 发布前彻底检查一般程序。
4 检查资源是否是最新的。
5 确保远程服务器已经准备就绪。
6 其他检查项(比如地图key,用户协议,公司以及logo)。
差异化功能的检查:
1 不同渠道应用功能的检查。
2 不同android版本的业务功能检查。
3 不同机型的业务功能检查。
代码混淆:
优点:
1 字节码优化。
2 保护代码,防止篡改和安全保护。
3 压缩APK体积,清除无用代码。
4 减少jar包体积。
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-19 将proguard.config前面的注释去掉 修改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 *; #} 如果在程序中使用了第三方的jar包,在混淆后导致出错,这时我们需要在proguard-project.txt中去进行相应的配置, 来让其在混淆时不要混淆相应的jar包。对改配置文件中的相关配置解释如下: -keep public class * extends android.app.Activity 【不进行混淆类名的类,保持其原类名和包名】 -keep public abstract interface com.asqw.android.Listener{ public protected <methods>; 【所有public protected的方法名不进行混淆】 } -keep public class com.asqw.android{ public void Start(java.lang.String); 【对该方法不进行混淆】 } -keepclasseswithmembernames class * { 【对所有类的native方法名不进行混淆】 native <methods>; } -keepclasseswithmembers class * { 【对所有类的指定方法的方法名不进行混淆】 public <init>(android.content.Context, android.util.AttributeSet); } -keepclassmembers class * extends android.app.Activity {【对所有类的指定方法的方法名不进行混淆】 public void *(android.view.View); } -keepclassmembers enum * {【对枚举类型enum的所有类的以下指定方法的方法名不进行混淆】 public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable {【对实现了Parcelable接口的所有类的类名不进行混淆,对其成员变量为Parcelable$Creator类型的成员变量的变量名不进行混淆】 public static final android.os.Parcelable$Creator *; } -keepclasseswithmembers class org.jboss.netty.util.internal.LinkedTransferQueue {【对指定类的指定变量的变量名不进行混淆】 volatile transient org.jboss.netty.util.internal.LinkedTransferQueue$Node head; volatile transient org.jboss.netty.util.internal.LinkedTransferQueue$Node tail; volatile transient int sweepVotes; } -keep public class com.unionpay.** {*; }【对com.unionpay包下所有的类都不进行混淆,即不混淆类名,也不混淆方法名和变量名】 经过上面这两部之后反编译后就能混淆了,但是四大组件还在,为什么四大组件还在呢,因为四大组件是在清单文件中进行配置的, 如果混淆后就不能根据清单文件的配置去寻找了。 如果对于一些自己的代码中要想提供出来让别人通过反射调用的方法时,我们不想让部分代码被混淆,或者是我们使用别人提供的第三方jar包, 因为第三方的jar包一般都是已经混淆过的,我们要是再混淆就会报错了,所以我们要保证这些内容不用混淆,这里我们只需修改这个文件,然后加上后面的一句话, 他就不会混淆我们给出的内容 -keepattributes *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 -keep class net.youmi.android.** { *; } 来源: <AndroidNote/代码混淆.md at master · CharonChui/AndroidNote>
混淆中如何清除日志信息:
-assumenosideeffects class android.util.Log { public static boolean isLoggable(java.lang.String, int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); }
使用这个配置时,一定要注意-dontoptimize,配置。
don‘t optimize 不要优化;将会会关闭优化,导致日志语句不会被优化掉。
ant多渠道打包:
1
配置: 通过ant脚本语言进行打包,对安卓打包进行描述。 首先下载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
配置完成后可以通过cmd窗口进行ant命令检测是否安装成功。
2 将androidManifast.xml做个拷贝为androidManifast.xml.temp文件
3 androidManifast.xml文件中要替换的字符串用@@包围
4 修改ANTTest.java工程
market.txt
K-touch
AppChina
GoogleMarket
5 修改签名信息ant.properties
例如可以修改为:
key.store = "Key的地址"
key.store.password = 123456
key.alias = mykey
key.alias.password = 123456s
6 修改local.properties
sdk.dir = ""指定sdk路径,路径之间是双斜杠
7 build文件
8 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\\VDunHeima2"; //工程apk存放地址 private final static String copyApkPath = "D:\\android\\apktest"; //打包时临时文件存放地址 private final static String signApk = "VDunHeima2-release.apk";//这里的文件名必须是准确的项目名! //明明前缀 private final static String reNameApk = "VDunHeima2_"; //要替换的字符串 private final static String placeHolder = "@[email protected]"; 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文件:读取临时文件中的@[email protected]修改为市场标识,然后写入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; } } } } }9查看apk文件