版權(quán)聲明:本文為LooperJing原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處!
通常產(chǎn)品在迭代的時(shí)候,都有測(cè)試環(huán)境和正式環(huán)境,或者說(shuō)是生產(chǎn)和開(kāi)發(fā)環(huán)境,一般軟件開(kāi)發(fā)階段都是在測(cè)試環(huán)境上運(yùn)行調(diào)試,而正式打包發(fā)布時(shí)會(huì)配置正式環(huán)境的服務(wù)器,也就是不同的接口URL和數(shù)據(jù)庫(kù)的區(qū)別。所以開(kāi)發(fā)人員經(jīng)常要在測(cè)試環(huán)境與正式環(huán)境之間來(lái)回切換,這帶來(lái)了很大不便。本文提供了三種種方式來(lái)解決這個(gè)問(wèn)題。
第一種方案:默認(rèn)情況下,項(xiàng)目的buildTypes包含debug和release兩個(gè)構(gòu)建版本,用來(lái)分別對(duì)應(yīng)測(cè)試環(huán)境和生產(chǎn)環(huán)境,設(shè)置applicationIdSuffix ,這樣測(cè)試環(huán)境和生產(chǎn)環(huán)境實(shí)際上等于是已經(jīng)分開(kāi)的兩個(gè)app了,可以實(shí)現(xiàn)在同一個(gè)設(shè)備上來(lái)安裝同一個(gè)應(yīng)用的不同版本。
第二種方案:在gradle構(gòu)建的時(shí)候,Product Flavors中定義兩個(gè)渠道,分別對(duì)應(yīng)測(cè)試環(huán)境和生產(chǎn)環(huán)境,并且每個(gè)渠道applicationId不同,也可以實(shí)現(xiàn)在同一個(gè)設(shè)備上來(lái)安裝同一個(gè)應(yīng)用的不同版本。
第三種方案:手機(jī)SDCARD中創(chuàng)建隱藏文件,在程序啟動(dòng)的時(shí)候,判斷有沒(méi)有隱藏文件,進(jìn)行測(cè)試環(huán)境和正式環(huán)境的切換,這樣只需要一個(gè)apk,通過(guò)隱藏配置文件,動(dòng)態(tài)進(jìn)行環(huán)境的切換。
方案一和方案二的本質(zhì)都是修改applicationId的值,構(gòu)建打包出不同包名的apk安裝文件。方案三不需要同時(shí)安裝兩個(gè)apk文件就可以實(shí)現(xiàn)環(huán)境分離,不足是每次切換的時(shí)候,app要關(guān)閉重啟一下。下面簡(jiǎn)述一下三種方案的實(shí)現(xiàn)。
方案一:buildTypes
在app/gradle中添加下列代碼
defaultConfig {
applicationId "com.environment.switch"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug{
applicationIdSuffix ".debug"
}
}
將debug類型的Application Id Suffix設(shè)置為“.debug”,然后在Application的onCreate中判斷是哪一個(gè)應(yīng)用,設(shè)置不同的服務(wù)器地址。
public class ServerConfigManger {
private ServerConfigManger(){
};
private static class ServerConfigMangerHolder{
private static final ServerConfigManger instance=new ServerConfigManger();
}
public static ServerConfigManger getInstance(){
return ServerConfigMangerHolder.instance;
}
public String getServerURL(Application pApplication){
//測(cè)試環(huán)境服務(wù)器地址
String urlPrefix="text.xxx.com";
ApplicationInfo applicationInfo = pApplication.getApplicationInfo();
String processName = applicationInfo.processName;
if( processName.endsWith(".debug")){
return urlPrefix;
}else {
//正式環(huán)境服務(wù)器地址
urlPrefix="release.xxx.com";
return urlPrefix;
}
}
}
@Override
public void onCreate() {
super.onCreate();
String serverURL = ServerConfigManger.getInstance().getServerURL(this);
}
構(gòu)建類型仍然是兩個(gè),debug和release,如下圖:

只是現(xiàn)在的debug對(duì)應(yīng)的是測(cè)試環(huán)境,而release對(duì)應(yīng)的是正式環(huán)境,debug應(yīng)用的包名是com.environment.switch.debug,release應(yīng)用的包名是com.environment.switch;這種方式還可以繼續(xù)優(yōu)化一下,因?yàn)檫@兩個(gè)應(yīng)用安裝之后,桌面的應(yīng)用圖標(biāo)是一樣的,下面將debug版本的應(yīng)用圖標(biāo)換掉。
在src目錄下新建一個(gè)debug目錄,將main目錄下的res目錄復(fù)制一份到debug目錄下,修改各個(gè)分辨率下的桌面Icon和strings.xml文件中的應(yīng)用名稱,如下圖:

到此第一種方案就OK了
方案二:Product Flavors
在app/gradle中添加下列代碼
productFlavors{
en1{
applicationId "com.environmentswitch_beta"
}
en2{
applicationId "com.environmentswitch"
}
}
所產(chǎn)生的構(gòu)建如下,總共4個(gè)

可以將en1的debug和release對(duì)應(yīng)測(cè)試環(huán)境,en2的debug和release對(duì)應(yīng)正式環(huán)境,我們?cè)贏pplication中的onCreate中根據(jù)運(yùn)行時(shí)選擇的是en1還是en2進(jìn)行服務(wù)器地址取出不同的值。同 步驟一;
方案二:隱藏配置文件
在SDCARD中設(shè)置隱藏文件,根據(jù)隱藏文件判斷是哪一套環(huán)境,假設(shè)存在test1,就用測(cè)試環(huán)境1,存在test2,就用測(cè)試環(huán)境2,存在test3,就用測(cè)試環(huán)境3,現(xiàn)在新建ServerBuildConfig,遍歷SDCARD進(jìn)行判斷。
public class ServerBuildConfig {
public static final int TEST_ENVIRONMENT_ONE = 0;
public static final int TEST_ENVIRONMENT_TWO = 1;
public static final int TEST_ENVIRONMENT_THREE = 2;
public static final int RELEASE_ENVIRONMENT = 3;
public static int mWhichEnvironment = 0;
static {
loadConfig();
}
private static void loadConfig() {
if(!Environment.getExternalStorageState().equals("mounted")) {
Log.w("ServerBuildConfig", "No valid SDCard, skip ServerBuildConfig!");
} else {
String buildConfigPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/.appconfig";
Log.i("ServerBuildConfig", "start to load configs for VSBuildConfig from: " + buildConfigPath);
File buildFile = new File(buildConfigPath);
if(buildFile.exists()) {
File[] listFiles = buildFile.listFiles(new ConfigFilenameFliter());
File testFile = null;
if(listFiles != null) {
for (File file : listFiles) {
if (file.getName().startsWith(".test")) {
if (testFile == null || file.lastModified() > testFile.lastModified()) {
testFile = file;
}
}
}
}
if(testFile != null) {
if(!".test".equals(testFile.getName()) && !".test1".equals(testFile.getName())) {
if(".test3".equals(testFile.getName())) {
mWhichEnvironment = 3;
} else if(".test2".equals(testFile.getName())) {
mWhichEnvironment = 2;
}
} else {
mWhichEnvironment = 1;
}
Log.i("ServerBuildConfig", "mWhichEnvironment = " + mWhichEnvironment);
}
}
}
}
private static class ConfigFilenameFliter implements FilenameFilter {
ConfigFilenameFliter() {
}
public boolean accept(File dir, String filename) {
return filename.startsWith(".");
}
}
}
然后在ServerConfigManger添加一個(gè)方法,返回不同的服務(wù)器地址
public String getServerURL(){
String urlPrefix="text1.xxx.com";
switch (ServerBuildConfig.mWhichEnvironment) {
case ServerBuildConfig.TEST_ENVIRONMENT_ONE:
urlPrefix="text1.xxx.com";
break;
case ServerBuildConfig.TEST_ENVIRONMENT_TWO:
urlPrefix="text2.xxx.com";
break;case
ServerBuildConfig.TEST_ENVIRONMENT_THREE:
urlPrefix="text3.xxx.com";
break;
case ServerBuildConfig.RELEASE_ENVIRONMENT:
urlPrefix="release.xxx.com";
break;
}
return urlPrefix;
}
在自定義的Application的onCreate中使用
String serverURL = ServerConfigManger.getInstance().getServerURL();
到此三種方案敘述完畢,對(duì)于第一種方案是最簡(jiǎn)便的,但是在有多套設(shè)置環(huán)境的情況下,第一種就不能滿足需求了,就可以采取第二種以及第三種??傊?,團(tuán)隊(duì)有一個(gè)約定,采取哪一種無(wú)關(guān)緊要。