Android官方多渠道方案詳解

free46000發表於2018-12-07

簡介

實際應用開發中,不可避免的會接觸到多渠道打包,不過其實大家常用的多渠道打包其實分為兩種。第一:只是需要簡單的渠道標識,然後通過標識程式碼裡做一些必要的邏輯處理,這種情況現在網上有很多開源的方案,可以做到快速打包,這裡就不在多做介紹了。第二:需要對程式碼、資源、依賴、配置等做到更深度的定製,比如為不同的應用市場設定不同的啟動頁和logo,這種情況就可以採用官方的ProductFlavors,下面也會詳細介紹這種方案。
簡單總結下這兩種方案,第一種打包速度快,但是不夠靈活,第二種有很強的定製性,但是由於每次回重新編譯並簽名所以在打包速度上慢很多,大家可以根據需求自由選擇不同的方案,或者搭配使用。

方案介紹

構建配置

首先需要在module中的build.gradle配置你需要的渠道,渠道中可以修改一些defaultConfig中的配置

android {
    ···
    defaultConfig {
        minSdkVersion 19
        versionCode 1
        ...
    }
    
    // 渠道的維度,支援不同維度的渠道
    flavorDimensions "channel"
    productFlavors {
        common {
            dimension "channel"
        }
        xiaomi {
            minSdkVersion '21'
            versionCode 20000  + android.defaultConfig.versionCode
            versionNameSuffix "-minApi21"
            dimension "channel"
        }
        huawei {
            minSdkVersion '23'
            versionCode 20000  + android.defaultConfig.versionCode
            versionNameSuffix "-minApi23"
            dimension "channel"
        }
    }
    
    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    ...
}

複製程式碼

Gradle 會通過上面的配置建立維度 * 維度中的渠道 * 構建型別數量的構建變體。在 Gradle 為對應構建變體的APK 命名時,首先是渠道,之後是構建型別。以上面的構建配置為例,Gradle 可以使用以下命名方案建立共6個構建變體:

構建變體:[common, xiaomi, huawei][debug, release]
對應 APK:app-[common, xiaomi, huawei]-[debug, release].apk

過濾變體

Gradle 會為每個可能的組合建立構建變體。都在Android Studio -> Build Variants中顯示出來,不過某些特定的構建變體在您的專案環境中並不必要,也可能沒有意義。您可以在build.gradle 檔案中建立一個變體過濾器,以移除某些構建變體配置。

android {
    ···
    variantFilter { variant ->
        def names = variant.flavors*.name
        def buildTypeName = variant.buildType.name
        println (names + "==" + buildTypeName)
        // 這樣就會移除 commonDebug的變體
        if (buildTypeName.contains("debug") && names.contains("common")) {
            setIgnore(true)
        }
    }
    ...
}

複製程式碼

dependencies依賴

現實場景中有的時候不同的渠道,提供的功能也不盡相同,這樣就需要對不同的渠道引入不同的元件包(前提App已經進行了元件拆分),如下簡單配置就可以實現

configurations {
    // Gradle沒有提供此細粒度級別的依賴方式,需要自己配置下不然會報錯
    xiaomiDebugImplementation {}
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation('com.android.support:appcompat-v7:26.1.0')

    // 可以控制 xiaomi渠道下 的 debug 構建型別才去引入此包
    xiaomiDebugImplementation('com.xxx:xxx:1.6.0')
    debugImplementation('com.xxx:xxx:1.6.0')
    commonImplementation('com.xxx:xxx:1.6.0')
}
複製程式碼

不同渠道的獨立簽名

同上面需求,對於功能不同的安裝包,大概率是要獨立的簽名,通過簡單的配置一樣可以實現,不過對於debug的構建型別,是不支援定製簽名的,具體原因未知...

signingConfigs {
    test11 {
        storeFile file("../test11.keystore")
        storePassword 'test11'
        keyAlias 'test11'
        keyPassword 'test11'
    }
    test22 {
        storeFile file("../test22.keystore")
        storePassword 'test22'
        keyAlias 'test22'
        keyPassword 'test22'
    }
}
// 渠道的維度,支援不同維度的渠道
flavorDimensions "channel"
productFlavors {
    common {
        dimension "channel"
    }
    xiaomi {
        dimension "channel"
    }
    huawei {
        dimension "channel"
    }
}
buildTypes {
    debug {
        //debug定製簽名無效 只能指定一個或者使用預設的簽名
//            productFlavors.huawei.signingConfig signingConfigs.test11
//            productFlavors.xiaomi.signingConfig signingConfigs.test22
//            productFlavors.common.signingConfig signingConfigs.test11
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

    }
    release {
        productFlavors.huawei.signingConfig signingConfigs.test11
        productFlavors.xiaomi.signingConfig signingConfigs.test22
        productFlavors.common.signingConfig signingConfigs.test11
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
複製程式碼

Manifest配置

有時我們需要對Mainfest中的某個屬性值做些調整,如配置不同渠道資料,App的Icon,還有替換宣告Activity等等,都可以通過下面的配置實現,如果感覺這種簡單的調整還不足以滿足你的需求,可以看下方的定製源集的方案去深度的定製

// build.gradle
android {
    ···
    flavorDimensions "channel"
    productFlavors {
        common {
            dimension "channel"
            manifestPlaceholders = ["ChannelData" : "Common Meta Data",
                                    "AppIcon"     : "@mipmap/ic_common",
                                    "MainActivity":CommonActivity"]
        }
        xiaomi {
            dimension "channel"
            manifestPlaceholders = ["ChannelData" : "XiaoMi Meta Data",
                                    "AppIcon"     : "@mipmap/ic_launcher",
                                    "MainActivity":"XMActivity"]
        }
        huawei {
            dimension "channel"
            manifestPlaceholders = ["ChannelData" : "HuaWei Meta Data",
                                    "AppIcon"     : "@mipmap/ic_launcher",
                                    "MainActivity": "HWActivity"]
        }
    }
    ...
}

// Manifest 
<application
    //${AppIcon} 替換AppIcon
    android:icon="${AppIcon}"
    ... >

    //${ChannelData} 替換ChannelData
    <meta-data
        android:name="ChannelData"
        android:value="${ChannelData}"/>

    //${ChannelData} 替換宣告Activity
    <activity android:name="${MainActivity}">
        ...
    </activity>
</application>



複製程式碼

定製程式碼 資源 Manifest 等源集

有時候簡單的調整可能不足以解決實際問題,這個時候可以直接定製源集解決問題,找到youModule\src,當前目錄下有個main資料夾為我們工程的核心程式碼和資源,我們可以在同級下建立不同的渠道目錄,如:common``xiaomi等,此目錄可以放置自定義的java程式碼res資源AndroidManifestassets等。
不同變體目錄(按優先順序排列):

src/commonDebug/(構建變體源集)
src/debug/(buildTypes源集)
src/common/(productFlavors源集)
src/main/(主源集)
複製程式碼

上面列出的順序決定了在 Gradle 合併程式碼和資源時哪個源集具有較高的優先順序。如果 commonDebug/debug/ 包含相同的檔案,Gradle 將使用 commonDebug/ 源集中的檔案。同樣,Gradle 會為其他源集中的檔案賦予比 main/ 中相同檔案更高的優先順序。Gradle 在應用以下構建規則時會考慮此優先順序順序:

  • 對於java/ 下的原始碼只能有單一的類檔案
    注:對於給定的渠道目錄,如果找到兩個或兩個以上定義同一 Java 類的源集目錄,Gradle 就會引發一個構建錯誤。例如,在構建除錯 APK 時,您不能同時定義 src/common/Utility.javasrc/main/Utility.java。這是因為 Gradle 會在構建過程中檢查這兩個目錄並引發duplicate class錯誤。如果針對不同的構建型別需要不同版本的 Utility.java,您可以讓每個渠道定義其自己的檔案版本,如:src/common/Utility.javasrc/xiaomi/Utility.java,而不將其包含在 main/ 中。
  • 所有Manifest合併為單個Manifest。將按照上述列表中的相同順序指定優先順序。也就是說,某個構建型別的Manifest設定會替換某個渠道的Manifest設定
  • 同樣,values/ res/ 和 asset/ 目錄中的如果存在有兩個或兩個以上的同名資源,比如在渠道中的資源將會替換main中資源,以下對於同時存在於strings.xml的同名資源和資源圖示做個示例
// main 下的 圖示資源
main\res\mipmap-hdpi\ic_launcher.png

// 在 xiaomi 下的 圖示資源
xiaomi\res\mipmap-hdpi\ic_launcher.png 

//打包 xiaomi 渠道的時候會自動替換圖片。
複製程式碼
// main 下的 strings.xml
<resource>
    <string name="app_name">MultiChannel</string>
    <string name="string_merge">我是string,沒被合併</string>
</resource>
// 在 xiaomi 下的 strings.xml 內容為:
<resource>
    <string name="string_merge">我是xiaomi,已經合併</string>
</resource>
//當打 xiaomi 渠道包時,最終 strings.xml 會變成:
<resource>
    <string name="app_name">MultiChannel</string>
    <string name="string_merge">我是xiaomi,已經合併</string>
</resource>
複製程式碼

其他

命令構建

對於習慣於使用命令構建的同學來說有以下幾點需要補充

  • 打全部包: gradle assemble
  • 打全部 Debug 包: gradle assembleDebug ,可以簡寫為 gradle aD 或 aDebug
  • 打全部 Release 包: gradle assembleRelease,可以簡寫為 gradle aR 或 aRelease
  • 打指定 flavor 包: gradle assemble(flavor)(Debug|Release) 如:gradle assembleXiaomiDebug
  • 打包完成後安裝: gradle install(flavor)(Debug|Release)如:如:gradle installXiaomiDebug
  • 打包前先 clean 一下,在測試的時候很必要: gradle clean assembleXiaomiDebug

參考閱讀

推薦:官方文件
android.jobbole.com/84752/
juejin.im/post/58be7b…

相關文章