三種方案實(shí)現(xiàn)Android應(yīng)用的環(huán)境分離

版權(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,如下圖:

build_type.png

只是現(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)用名稱,如下圖:


修改桌面圖標(biāo).png

到此第一種方案就OK了

方案二:Product Flavors

在app/gradle中添加下列代碼

   productFlavors{
        en1{
            applicationId "com.environmentswitch_beta"
        }
        en2{
            applicationId "com.environmentswitch"
        }
    }

所產(chǎn)生的構(gòu)建如下,總共4個(gè)


構(gòu)建類型.png

可以將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)緊要。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容