首先,需要准備的工作:
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 = "@market@";
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;
}
}
}
}