1. 程式人生 > >使用 多渠道productFlavor、多客戶、多版本buildType 、多模組moudle 配置的 那些點點滴滴

使用 多渠道productFlavor、多客戶、多版本buildType 、多模組moudle 配置的 那些點點滴滴

---------------------------------------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------------------------------------- 專案 使用 多渠道 已經蠻久了, 最近又有新需求過來了,所以多渠道這塊也重新看了下。。。。

然而新需求很坑爹(或許是我能力不夠),反正 事實就是  浪費了我兩天時間, 看了N多部落格,也沒找到對應的解決方案

---------------------------------------------------------------------------------------------------------------------------------------------------------------

一、總覽

  1. buildType

  2. productFlavors
  3. sourceSets
  4. 佔位符 manifestPlaceholders   和  動態引數buildConfigField
  5. 編譯變體版本(修改名稱,自定義編譯)
  6. 多模組的動態引數配置
  7. 個人小結
  8. 疑問
  9. 拓展

   以下兩片參考文章 提供了詳細的 gradle中的相關引數介面

二、buildType  編譯型別

  1. 可以重定義 一切  defaultConfig{} 裡的引數 和 android{} 下的直接引數 (後者未完全驗證,有誤請指正)
  2. 可以定義 定位符(manifestPlaceholders) ,動態引數(buildConfigField)、簽名、混淆等等配置,
  3. 可以自定義增加 如下面(test),通過initwith,重新拷貝了一份debug的配置(當前已經定義的debug配置)
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug{

        }
        test.initWith(buildTypes.debug)
        test{

        }
    }

三、productFlavors  渠道配置 (因為一開始是因釋出渠道寫的,所以渠道號叫習慣了,並非正式翻譯)

        其實看了下百度翻譯 是產品特點,主要是 各個不同的配置。(1和2都可上面一樣)

  1. 可以重定義 一切  defaultConfig{} 裡的引數 和 android{} 下的直接引數 (後者未完全驗證,有誤請指正)
  2. 可以定義 定位符(manifestPlaceholders) ,
  3. 動態引數(buildConfigField)、
  4. 簽名、
  5. 混淆等等配置,
  6.  productFlavors 使用的時候 需要現在 defaultConfig{} 配置引數 flavorDimensions, 然後再在productFlavors定義中申明對應的緯度
 android{
   defaultConfig {
        flavorDimensions "Char","Num"
    }

   productFlavors {
        A {
            dimension "Char"
        }
        B {
            dimension "Char"
        }
        Num1{
            dimension "Num"
        }
        Num2{
            dimension "Num"
        }
    }
}

1. flavorDimensions 是個維度引數配置項,  用陣列比較好理解,

     我這裡是2個維度,所以對應二維陣列,[A,B][Num1,Num2],  

      如果是 三維, 那麼就是 對應三位陣列 [A,B] [Num1,Num2] [C1,C2]

2.對應 會編譯出來的版本 有12個, 2*2*3(3是 buildType時候自定義了一個aaa)

   版本名字順序是 根據 flavorDimensions順序 , 【維度一】【維度二】【緯度...】【buildType】

三、sourceSet  資原始檔配置

  1.   如果不設定,則是android預設的,
  2. main 代表主幹程式碼 (下面的是已經對主幹預設路徑修改過的)
  3. Num1是 分支程式碼,Num1和上面的渠道號定義保持一致。
  4.  不同的渠道號可以指向相同的路徑,即他們引用同一個檔案
  5. java檔案不會被替換,(即,"src/main/java/a.java" 和  "src/Num1/java/a.java" 是同一路徑檔案,編譯會報錯告訴你已經定義了a.java檔案) 
    1. aidl檔案和jni檔案 未驗證,還不清楚
    2. 使用:用函式呼叫:定義非main的兩個目錄下,相同結構路徑,相同檔名,相同函式名,(方法內容不同)

                             用介面呼叫:定義非main的兩個目錄下,相同結構路徑,實現相同介面 (具體類不同)

  1. res下的資原始檔,同路徑下可以被直接替換
android{    
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs']
        }
        Num1 {
            manifest.srcFile 'src/' + 'Num1' + '/BctcManifest.xml'
            assets.srcDirs = ['src/' + 'Num1' + '/assets']
            java.srcDirs = ['src/' + 'Num1' + '/java']
        }
    }
}

  根據上面的 配置, 去將程式碼 或 資原始檔等 放入對應的路徑

四、動態引數定義

動態引數定義 有兩種, 一個是 佔位符  manifestPlaceholders = []  , 一個是 buildConfigField()

  1. 佔位符 manifestPlaceholders :就是在前面程式碼裡面 直接預留一個位置,然後等編譯的時候 把資料放進去,
  2. buildConfigField :是在 BuildConfig.java 這個自動生成的檔案裡面去定義一個引數
  1. 兩個均可以在 defaultConfig 、buildtype 、productFlavors 三處定義
  2. 均可在程式碼裡使用, 
  3. 佔位符  manifestPlaceholders 可以在xml檔案中使用,如AndroidManifest.xml
manifestPlaceholders buildConfigField
在 defaultConfig{} 定義  
在 buildtype {} 定義
在 productFlavors {} 定義
 AndroidManifest.xml 使用 ×
 程式碼中  使用 (要xml定義,再程式碼獲取)

buildConfigField 使用:  在定義String型別值 需要 對  雙引號 轉義, 即  需要 "ttt", 那麼定義的時候需要把雙引號也寫進去, 寫成 " \"ttt\"  "

productFlavors {
  A {
        dimension "Char"
        buildConfigField("String", "test","\"ttt\"")
        buildConfigField("int", "test1","123")
        buildConfigField("boolean", "test2","true")
    }
}

使用:

public void test(){
 String s = BuildConfig.test;
 int s1 = BuildConfig.test1;
 boolean s2 = BuildConfig.test2;
}

執行結果: 

manifestPlaceholders   在AndroidManifest.xml中使用

定義:

  productFlavors {
        Num1 {
            dimension "Num"
            manifestPlaceholders = [test_manifestPlace1: "替代的內容1",test_manifestPlace2: "替代的內容2"]
        }
   }

  在AndroidManifest.xml裡直接使用:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="test7permission.cn.testduoqudao">
    <application>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="${test_manifestPlace1}"/>
                <action android:name="${test_manifestPlace2}"/>
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

執行結果:

manifestPlaceholders 程式碼中使用

  定義:   需要在build.gradle裡面定義初始值,再通過AndroidManifest.xml來轉達一下


    productFlavors {
        Num1 {
            dimension "Num"
            manifestPlaceholders = [test_manifestPlace1: "替代的內容1",test_manifestPlace2: "替代的內容2"]
        }
    <application
      >
        <activity android:name=".MainActivity">
            <!--//這一句起到至關重要作用-->
            <meta-data android:name="test_activity" android:value="${test_manifestPlace1}"/>
        </activity>

        <!--//這一句起到至關重要作用-->
        <meta-data android:name="test_application" android:value="${test_manifestPlace2}"/>
    </application>

在程式碼裡使用:

    public static String init(Application application) {
        String result = "";
        try {
            ApplicationInfo applicationInfo = application.getPackageManager().getApplicationInfo(application.getPackageName(), PackageManager.GET_META_DATA);
            result = applicationInfo.metaData.getString("test_application");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return result;
    }

    public static String init(Activity activity) {
        String result = "";
        try {
            ActivityInfo activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), PackageManager.GET_META_DATA);
            result = activityInfo.metaData.getString("test_activity");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return result;
    }

          (這個blog說 還可以在 service,receive 裡面使用(使用同上原理), 但是很遺憾我寫的時候報錯,有些無法引用, 所以就沒貼出來,有需要的就自行去研究下吧)

五、編譯變體版本(修改名稱,自定義編譯)

 1. 修改apk生成的名稱,效果是 app_v版本名稱_日期_維度名稱_buildType(版本號).apk

android{
 android.applicationVariants.all { variant ->
        variant.outputs.all {
            def fileName = "app_v${defaultConfig.versionName}_${releaseTime()}_${variant.flavorName}_${variant.buildType.name}(${variant.versionCode}).apk"
            outputFileName = fileName
        }
    }
}

static def releaseTime() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

2.前面因為 buildType 和 productFlavors 維度版本,這樣基本會有很多版本, 

  當我們執行 assemble 會預設遍歷所有的版本生成一遍,但是有時候有些版本是不需要的,這個時候就可以使用下面配置進行遮蔽了(下面配置是單維度的,多維度的判斷和這個有點區別)

android{    
    //執行assemble時 過濾不必要的版本
    android.variantFilter { variant ->
        if ('release'.equals(variant.buildType.name)) {
            variant.getFlavors().each() { flavor ->
                //這裡需要注意下, 多維度 和此處不一樣
                if ('Num1'.equals(flavor.name)) {
                    variant.setIgnore(true)
                }
            }
        }
    }
}

六、多模組的動態引數配置

     當主模組app配置了引數,而有其他moudle也配置了相同引數, 一兩個模組還好,還可以手動修改

   但是當模組以多,那麼每次有變動需要新增/修改,就會擔心會不會又哪裡漏掉

    所以 以下操作是針對多module同一配置引數的,包括不限於 自定義引數,defaultConfig(),引用第三方的包,各種引數設定(由於用途較多,請自行開腦洞)

1. 在根路徑下面, 新建config.gradle,(和整個專案的build.gradle同一級目錄) 

檔案下面 加了些參考配置,如果有需要保持各個moudle的版本引數都保持一致,或者 引進jar包  都保持一致的話,自行選擇性新增。

//使用 需要在build.gradle 頭部新增 引用 : apply from: "config.gradle"

ext {
    /*------------------------- 簽名配置--------------------------------------*/
    signingConfigs = [
            "filePath"     : "aaa.keystore",
            "keyAlias"     : "bbb",
            "keyPassword"  : "ccc",
            "storePassword": "dddd"
    ]
    /*------------------------- 簽名配置--------------------------------------*/

    /*------------------------- 後臺地址配置--------------------------------------*/
    url = [
            "API_HOST_DEBUG"  : "\"https://*.*.*.*/\"",
            "API_HOST_RELEASE": "\"https://*.*.*.*/\""
    ]
    url2 = [
            "API_HOST_DEBUG"  : "\"https://*.*.*.*/\"",
            "API_HOST_RELEASE": "\"https://*.*.*.*/\""
    ]
    /*------------------------- 後臺地址配置--------------------------------------*/


    /*------------------------- 測試後臺配置--------------------------------------*/
    test = [
            "API_HOST_DEBUG"  : "\"http://10.20.11.204:30160/\"",
            "API_HOST_RELEASE": "\"http://10.20.11.204:30160/\"",
            "codepath"        : "Num1"//程式碼路徑參考Num1的, 因為這裡Num1的 路徑是都放在"Num1"下面
    ]
    /*------------------------- 測試後臺配置--------------------------------------*/

    /*------------------------- 參考配置--------------------------------------*/
    /* android = [
         compileSdkVersion: 23,
         buildToolsVersion: "23.0.3",
         minSdkVersion    : 15,
         targetSdkVersion : 22,
         versionCode      : 1,
         versionName      : "1.0"
 ]

 dependencies = [
         "gson"               : "com.google.code.gson:gson:2.6.2",
         "eventbus"           : 'org.greenrobot:eventbus:3.0.0',
         "butterknife"        : 'com.jakewharton:butterknife:7.0.1',
         "support-design"     : 'com.android.support:design:24.1.1',
         "support-appcompatV7": 'com.android.support:appcompat-v7:24.1.1',
         "support-percent"    : 'com.android.support:percent:24.1.1',
         "support-multidex"   : 'com.android.support:multidex:1.0.1',
         "glide"              : 'com.github.bumptech.glide:glide:3.7.0',
         "support-v4"         : 'com.android.support:support-v4:24.1.1',
         "okhttp3"            : 'com.squareup.okhttp3:okhttp:3.3.1',
         "nineoldandroids"    : 'com.nineoldandroids:library:2.4.0'
 ]*/
}

2. 專案根目錄下的build.gradle新增一句引用

//新增的
apply from: "config.gradle"

buildscript {
    ……
}

allprojects {
    ……
}

3. 現在所有moudle模組目錄下的 build.gradle 就可以使用了

  請自行感受 rootProject.ext.url["API_HOST_DEBUG"], 這句 和 config.gradle中定義 的關係。

android {
    signingConfigs {
        main {
            File key = new File(rootProject.ext.signingConfigs["filePath"])
            keyAlias rootProject.ext.signingConfigs["keyAlias"]
            keyPassword rootProject.ext.signingConfigs["keyPassword"]
            storeFile file(key)
            storePassword rootProject.ext.signingConfigs["storePassword"]
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.main
            buildConfigField("String", "url_aaa", rootProject.ext.url["API_HOST_DEBUG"])
        }
    }
}

dependencies {
    implementation rootProject.ext.lib["gson"]
}

七、個人小結

1. 前面說到  引數配置 均可以在 defaultConfig 、buildtype 、productFlavors 三處定義,

再仔細看 這三者其實是同一層級的,如下。 那麼當他們 均 定義一個引數時, 究竟哪個生效???

android{
    defaultConfig{
    } 
    buildtype{
    } 
    productFlavors{
    }
}

結果: 後者覆蓋前面的,即現在這個順序的話是,相同定義最後生效的是:productFlavors{} 定義的值

2.在gradle中自定義一個引數 比如 int a=0, 然後依次在不同的地方進行 賦不同的值,最後的結果是什麼?

結果:永遠是 最後一次賦值,並不會因為編譯的版本不同而不同。

八、疑問

問題一:

背景:有客戶A、客戶B

          客戶A正式後臺 (URL1)和測試後臺(URL2),且地址不一樣

          客戶B後臺地址(URL3

 問題:應該怎麼動態配置?  (先別急著下定論說很容易)

分析一:最優雅的情況是,app_A_release_URL1.apk 對應 URL1 正式 後臺; app_A_debug_URL2.apk 對應 URL2測試後臺

        肯定第一時間想到的是下面的這個, 但是很遺憾下面的模式無法設定B客戶的 後臺地址 URL3,

android{
   buildType{
        release{
            url = URL1
        }
        debug{
            url = URL2
        }
    }

   productFlavors {
        A {
           客戶A
        }
        B {
           客戶B
        }
    }
}

分析二:productFlavors() 使用兩個維度的配置,然而效果還是和 分析一 一樣,無效

分析三:如果額外加個客戶A-Test , 在4個版本中只選擇2個有用的版本。那麼勢必導致,測試版本和正式版本,無法相互安裝/替換(對於交付第三方安裝到系統/測試,均有不小的阻力)

分析四:在buildType()和productFlavors() 中加入變數判斷編譯版本, 然而很遺憾我沒有找到相關變數,自己定義也失敗(參見 七.第2小點)

主要矛盾:在普通狀況下release和debug的版本必定是 相同的配置, release和debug是相對於程式碼區分,即一個除錯開發版本,一個釋出版本。

                  而如今,我想用配置一的除錯版本,來和配置二的開發版本 做驗證。

臨時解決方法: (都能解決,但都不完美 不優雅)

1. 分析三,新建一個客戶,新增配置,  

    缺點:生成apk的版本不一致,無法互相替換。如果是系統預置的應用,更是麻煩

 2. 程式碼裡進行判斷,如果是僅僅一個URL地址不同,那麼可以考慮

     缺點:其他的客戶B也必須配置Debug引數和Release引數,哪怕B客戶只有一個引數;

                當客戶A的配置引數不僅僅只有一個URL不一樣的時候, 有十幾個引數的話,那這個也很麻煩。

九、擴充套件  (先挖個坑在這吧,埋不埋的以後再說了)

---------------------------------------------------------------------------------------------------------------------------------------------------------------

~~~~~~~~~~~~~~~~~~~          終於寫好了啊  ~   ~~

                        早就想寫,也早就知道 很麻煩, 但沒想到 一寫就是整整1天半的時間。。。

                        感謝看完!  或  拉到了底部!

---------------------------------------------------------------------------------------------------------------------------------------------------------------