Android Gradle基於引數化配置實現差異化構建

HappyCorn發表於2019-04-25

一、背景:
專案中有一些特殊的需求,如個別渠道整合騰訊bugly,個別渠道整合易觀統計,不同的渠道整合不同的推送策略(如Oppo渠道優先Opush推送),不同的渠道擁有不同的第三方登入整合等等。這些需求本身,往往都與外部整合進來的功能有關,且需求上,功能與渠道本身,有一定的對映關係,對於此類需求,具體專案構建時可以有如下幾種策略:
1,不同的分支管理,以對應不同的差異化實現;
2,通過變體,實現不同的差異化構建;
3,通過Android Gradle引數化配置,實現差異化構建。

二、方案利弊分析:
1,基於不同的分支管理,差異部分的程式碼直接在特殊分支中,每次需要與主分支進行合併並解決可能的合併衝突。同時,針對特殊的渠道邏輯,如果程式碼通過分支隔離,往往開發個體都是基於主分支開發,渠道的差異性邏輯處理部分容易忽略,有時候造成不必要的bug等情形,維護成本較大。

2,基於變體的差異化構建,直接使用Gradle變體方案,優勢在於變體目錄及對應的構建流程已經自動包含。對應的,不太優雅的地方在於此類需求一旦繁雜,變體的種類及對應的目錄層次相對增多,變體型別會隨著產品風味的增加而成倍數增長,在具體構建時,構建任務也會相對繁雜,且對應在build等目錄下的輸出的目錄層次也相對複雜。

3,基於Gradle的引數化配置,依據具體的需求詳情,主動配置並處理對應的差異化構建邏輯,如渠道的對映關係,不同的外部依賴,以及對應的程式碼佔位等,以此在保持原有變體不變和構建任務不變的情況下,只需通過引數化的配置,即可完成對應的差異化部分構建。

本文主要討論“通過引數化配置實現差異化構建”實現方案。 下面通過個別渠道整合bugly和易觀統計詳細討論具體的實現過程。

三,例項
1,個別渠道的bugly整合 主工程如果要整合bugly,相對非常簡單,主要包括build.gradle中引入bugly依賴,適當位置(如Application中)初始化bugly,proguard.cfg中進行bugly的混淆配置。但本例中,bugly整合不是針主工程本身,而是針對特定的渠道。具體的引數化配置實現差異化構建過程如下:
a,專案主工程中新建ext.gradle檔案,實現對渠道的邏輯對映:

ext.gradle
--------------------------
ext {
    channel = project.hasProperty('channel') ? channel : 'feature'

    addBugly = {
        def buglyChannelList = ["huawei"]
        def result = buglyChannelList.contains(channel)
        println ">>> channel:${channel},  bugly added:${result}"

        if(result) {
            return true
        }
        return false
    }

}

android {
    sourceSets {
        main{
            java {
                if(addBugly()) {
                    srcDirs "src/ext/bugly/java"
                } else {
                    srcDirs "src/mock/bugly/java"
                }
            }
        }
    }
}


dependencies {
    if (addBugly()) {
        api 'com.tencent.bugly:crashreport:latest.release'
        api 'com.tencent.bugly:nativecrashreport:latest.release'
    }
}
複製程式碼

具體的邏輯對映包括:
1.1,渠道值(channel)的接收和邏輯判斷addBugly
1.2,對應邏輯確認下(addBugly)的bugly依賴引入;
1.3,對應邏輯確認下的源集指定。

b,專案主工程中引入ext.gradle

apply from: '../ext.gradle'
複製程式碼

c,專案對應模組中,處理對應的源集邏輯(base模組為例)

base/src/main/java/com/mycorn  ---預設工程原始碼
base/src/ext/bugly/com/mycorn  ---bugly邏輯確認下的額外源集原始碼目錄
base/src/mock/bugly/com/mycorn ---通常情況下的額外源集原始碼目錄

base/src/ext/bugly/com/mycorn
---------------------------------
package com.mycorn;

import android.app.Application;
import android.util.Log;

public class BuglyHelper {
    public static final String TAG = "BuglyHelper";

    public static void initBugly(Application context) {
        Log.d(TAG, "bugly init...";
        // 初始化騰訊bugly  第三個參數列示是否處於除錯模式
        com.tencent.bugly.crashreport.CrashReport.initCrashReport(context, "bbccdd123456", false);
    }
}


base/src/mock/bugly/com/mycorn
---------------------------------
package com.mycorn;

import android.app.Application;
import android.util.Log;

public class BuglyHelper {
    public static final String TAG = "BuglyHelper";

    public static void initBugly(Application context) {
        Log.d(TAG, "bugly init...mock");
        // 實際上是空方法,主要是用於佔位
    }
}
複製程式碼

d,專案主工程下,在對應初始化bugly的地方直接寫上通用性的bugly初始化佔位邏輯

    ....
    ....
    com.mycorn.BuglyHelper.initBugly(context);
    ....
    ....
複製程式碼

e,proguard.cfg配置項,由於只是進行程式碼混淆的配置,此處可以直接放到對應模組的proguard.cfg檔案中

    ....
    ....
    # 騰訊bugly
    -dontwarn com.tencent.bugly.**
    -keep public class com.tencent.bugly.**{*;}
    ....
    ....
複製程式碼

至此,基於引數化配置實現騰訊bugly引入的差異化構建,得以完成。

其中關鍵點,在於對應的“佔位”邏輯的處理。

2,個別渠道的易觀統計整合 總體上與上述的騰訊bugly整合類似,特別之處在於易觀統計的接入專案中是直接引入的jar檔案,並在對應的AndroidManifest.xml檔案中配置了不少的如<service><receiver>及其他後設資料等配置項。
Android Gradle專案構建時,對於同一模組,可以通過sourceSets增加如原始碼及資源目錄等,但卻不能增加AndroidManifest檔案,形如manifest.srcFile的寫法當前只能是對AndroidManifest檔案的重新設定。但如果是獨立模組,或已經是獨立的外部aar等依賴引入,Android Gradle構建時會自動實現對應的AndroidManifest檔案合併。因此,為了能夠將易觀統計中的AndroidManifest配置項進行單獨隔離,需要在上例中的基礎上將易觀統計單獨隔離成獨立模組,或對應的aar檔案等(本例在於闡述具體解法,對於最新的易觀統計如果已經支援依賴引入,則不在討論範圍內)。

a,將易觀形成獨立模組,AndroidManifestlibs目錄下的jar包,proguard.cfg檔案等,實現獨自配置;

b,參照上例bugly的整合,處理對應的易觀邏輯關係

ext {
    channel = project.hasProperty('channel') ? channel : 'feature'
    addBugly = {
        def buglyChannelList = ["huawei"]
        def result = buglyChannelList.contains(channel)
        println ">>> channel:${channel},  bugly added:${result}"

        if(result) {
            return true
        }
        return false
    }

    addEguan = {
        def eguanChannelList = ["baidu"]
        def result = eguanChannelList.contains(channel)
        println ">>> channel:${channel},  eguan added:${result}"

        if(result) {
            return true
        }
        return false
    }
}

android {
    sourceSets {
        main{
            java {
                if (addBugly()) {
                    srcDirs "src/ext/bugly/java"
                } else {
                    srcDirs "src/mock/bugly/java"
                }

                if (addEguan()) {
                    srcDirs "src/ext/eguan/java"
                } else {
                    srcDirs "src/mock/eguan/java"
                }
            }
        }
    }
}


dependencies {
    if (addBugly()) {
        api 'com.tencent.bugly:crashreport:latest.release'
        api 'com.tencent.bugly:nativecrashreport:latest.release'
    }

    if (addEguan()) {
        api project(':eguan')
    }
}
複製程式碼

c,同樣的對應的目錄下形成易觀的源集邏輯,並在需要初始化的地方,改成通用的邏輯佔位寫法。

base/src/ext/eguan/com/mycorn
---------------------------------
package com.mycorn;

import android.content.Context;
import android.util.Log;

import com.eguan.monitor.EguanMonitorAgent;

public class EguanHelper {
    public static final String TAG = "EguanHelper";

    public static void initEguan(Context context) {
        Log.d(TAG, "eguan init...");
        try {
            EguanMonitorAgent.getInstance().initEguan(context, "111222333", "baidu");
        } catch (Exception e) {
            Log.d(TAG, "eguan init exception...");
        }
    }
}


base/src/mock/eguan/com/mycorn
---------------------------------
package com.mycorn;

import android.content.Context;
import android.util.Log;

public class EguanHelper {
    public static final String TAG = "EguanHelper";

    public static void initEguan(Context context) {
        Log.d(TAG, "eguan init...mock");
        // 實際上是空方法,主要是用於佔位
    }
}


複製程式碼
....
....
EguanHelper.initEguan(this);
....
....
複製程式碼

至此,完成基於引數化配置,實現特定渠道下的易觀整合的差異化構建。

四,結語
基於引數化配置實現差異化構建,需要依據實際的需求背景,分析具體的差異部分,以考慮簡便易行,同時兼顧易維護性為主,實現具體的配置過程。

相關文章