Android多渠道productFlavors同時開發(fā)兩個類似的app

前言

最近有個需求,老板讓開發(fā)一個新的app,新的app上的功能和老的app基本上完全一致,差異化的地方很少,那按照慣性思維,復制出一個老的app,然后改改色值,icon,string不就可以了么。但是,還要求以后保持倆app數(shù)據(jù)同步,產(chǎn)品同步。換句話說,老app上有需求,新的app上也要同樣去支持。這樣的話,復制出的新app想要同步支持老app的需求,就比較難了,反之亦然。其實,google官方早就給出了類似問題的解決方案,多渠道打包。

build配置及目錄結(jié)構(gòu)

①新增productFlavors

在app的build中新增productFlavors來標記多渠道:

 flavorDimensions "app"
    productFlavors {
        main {
            applicationId "com.zdu.client"
            dimension "app"
        }
        app2 {
            applicationId "com.zdu.test"
            dimension "app"
        }
    }

flavorDimensions官方文檔上介紹的很清楚,這是一個維度標識。這么講肯定難以理解,如上代碼所示,flavorDimensions只有一個值app,這么編譯完后查看 Build Variants,如圖1:

圖1

會有四個組合,分別是main的debug和release環(huán)境以及app2的debug和release環(huán)境。那如果我們再增加一個新的維度lib,會是什么樣子的呢?上代碼:

flavorDimensions "app","lib"
    productFlavors {
        main {
            applicationId "com.zdu.client"
            dimension "app"
        }
        app2 {
            applicationId "com.zdu.test"
            dimension "app"
        }

        app3 {
            applicationId "com.zdu.test2"
            dimension "lib"
        }
    }

sync后查看Build Variants,如圖2:

圖2

變成了mainApp3的debug和release環(huán)境以及app2App3的debug和release環(huán)境。
原因就是main和app2都是使用的app維度,所以他們倆是同維度的單位,而app3是lib維度屬性,所以app3要分別和main,app2兩個渠道進行組合,形成了一個新的維度單位。
PS:這個功能,大家理解了就行,目前我沒找到可以使用的場景。一般來說,只需要保持一個維度就好。

②創(chuàng)建新渠道app2的文件目錄

首先切換到Project目錄訪問,在app-src的目錄下,也就是說和main平級的目錄下,創(chuàng)建app2文件夾,然后在app2的目錄下創(chuàng)建跟main目錄下一模一樣的文件目錄結(jié)構(gòu),如圖:

目錄結(jié)構(gòu)

基于此,準備工作就算做好,基本配置和基本結(jié)構(gòu)已經(jīng)搞定,接下來就是來了解如何去多渠道開發(fā)。

使用技巧

再看下上圖,app2和main目錄結(jié)構(gòu)是一樣的,那是不是意味著,app2和main是平級的?切換到app2分支的時候就會走app2的java代碼和res的資源呢?
先回答第一個問題:

app2和main是平級的?

app2和main并不是平級,相反的,app2是main的附屬,main是公共代碼資源庫,app2的所有缺失的java和res資源都會去main下找公共資源,所以我們切換到app2渠道下,可以直接運行app,除了applicationId不同之外,app不會有任何變化。

main是公共代碼資源庫,這句話的意思是說,無論有多少個渠道,main下的java和res都是最基本的存在,類似于所有其他的渠道都在引用main這個庫的意思。這和我們開發(fā)引用一個庫是類似的原理,只是完全反轉(zhuǎn)過來,我們開發(fā)一個庫,是app來引用這個庫,而多渠道下都在一個app下,其他渠道以類似引用的方式來使用main下的java和res。

切換到app2分支的時候就會走app2的java代碼和res的資源呢?

如果理解了第一個問題,那第二個問題也就比較好理解了。app2作為main的附屬,切換到app2分支后,會將app2下的java代碼和res合并到main下編譯運行。

隨之又會有一個新的問題,java代碼和res資源是如何合并的?

java代碼和res資源是如何合并的?

java代碼的合并比較簡單,舉個簡單的例子,如圖:

我們在app2創(chuàng)建如圖的目錄結(jié)構(gòu),編譯運行后,相當于在main下也創(chuàng)建了一樣的目錄結(jié)構(gòu),將app2下的代碼復制一份到對應的目錄結(jié)構(gòu)下。如果在app2和main的相同目錄結(jié)構(gòu)都創(chuàng)建一樣的類會怎樣?如圖

那么這就要求我們渠道下的java目錄結(jié)構(gòu)和類名不能和main公共資源下的完全一致。

res資源的合并相對來說就是真正的合并了,但drawable,layout,和values下的合并還有所不同。

drawable合并

drawable的合并只需要命名一致,并對比main項目中圖片放置的位置放到tea項目的對應位置即可完成替換。


圖片替換要注意兩點:第一,目前和命名一致;第二:main下有幾套圖片,app2下就要有幾套圖片,可以多但不能少。
app2下新增一個main沒有的圖片,代碼中去引用了的話,切換到main渠道下會報錯找不到該資源文件,這個問題稍后講解。

layout合并

laout布局文件跟drawable圖片合并一樣,也是要求命名一致,但涉及到布局文件中的id的處理,要求比較嚴格,如果相同的功能只是布局位置,字體大小,色值等調(diào)整,那么id必須一致,因為同一個java文件引用不同渠道下的layout布局,如果id不同,切換渠道肯定報錯;如果app2中新增一個id,而又在java代碼中引用了,那么切換到main渠道下也會報錯,因為main渠道下的layout沒有這個id,這塊的處理稍后再說。

string,color合并

string和color等類似獨一份的資源文件合并又有所不同,簡單的說就是,相同命名的string和color會被替換,不同命名的會新增。如圖:


image.png

image.png

相同的app_name就會被替換成MyApp2的名稱。
不同命名的會新增,也會有l(wèi)ayout布局id類似的問題,如果main下string.xml沒有相同命名的資源,同時又在java代碼中引用了,一樣會出問題,這塊稍后一起講解。

java代碼的差異化處理

java代碼的差異化處理是重中之重,再怎么相似的倆app,總有些個別地方邏輯不同的地方。我這邊提供兩種處理差異化代碼的方式:

main下公共代碼庫差異化處理

兩個app共用一套代碼的前提下,在main下進行代碼區(qū)分,這種情況需要做渠道區(qū)分,BuildConfig類中已經(jīng)有渠道區(qū)分常量:BuildConfig.FLAVOR
那么在代碼中就可以判斷:

        if ("main".equals(BuildConfig.FLAVOR)) {
            // 處理main下邏輯
        } else if ("app2".equals(BuildConfig.FLAVOR)) {
            // 處理app2下邏輯
        }

這里是建議大家寫一個工具類,不然每個差異化的地方都要這么判斷很蠢的。

public class FlavorUtils {
    
    public static boolean isMain() {
        return "main".equals(BuildConfig.FLAVOR);
    }

  
    public static boolean isApp2() {
        return "app2".equals(BuildConfig.FLAVOR);
    }
}

差異化不多的情況下,這種寫法是最方便的,也是最效率的,唯一的壞處就是在于要多判斷。
注:這種差異化處理是將main和app2分別當做一個獨立的渠道,但因為main還是公共代碼庫,所以切換到app2下進行編譯,會同時編譯app2和main下的java代碼,這種情況下main代碼中引用app2的類是沒有問題的。
但如果切換到main渠道下去編譯,你會發(fā)現(xiàn)編譯后提示找不到app2下類的錯誤,那是因為切換到main渠道下,只會編譯main下java代碼,不會編譯app2的java代碼,自然就找不到對應app2下的類了。解決方式也有:

sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
            java.srcDirs = ['src/main/java','src/app2/java']
        }
        app2 {
            java.srcDirs = ['src/app2/java']
        }
    }

配置main下的java.srcDirs編譯目錄,切換到main渠道后同時編譯main/java和app2/java,就可以了。

分離公共代碼庫,每個app創(chuàng)建對應的渠道

在前文中,我們都是把main當做一個單獨的app渠道,app2作為第二個渠道,現(xiàn)在的方式就是,將main的渠道單獨分離出來,創(chuàng)建app1渠道。將app1和app2差異的類從main下剪切出來同時復制到對應的app1和app2下,單獨去開發(fā)對應的渠道代碼,互相不干擾。
這樣,main的功能性就只是公共代碼資源庫的職能,不能再作為一個單獨的渠道去編譯運行了。但同時,build也需要修改下:

sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
            java.srcDirs = ['src/main/java']
        }
        app1 {
            java.srcDirs = ['src/app1/java']
        }
        app2 {
            java.srcDirs = ['src/app2/java']
        }
    }

各自編譯各自的java代碼。
app1和app2下相同的類也不會報錯:


原因很簡單,因為編譯了app1渠道,沒有編譯app2渠道,自然不會出現(xiàn)類沖突的問題。
注:這種java代碼的差異化處理需要注意,main只能引用app1和app2下路徑和類名一致的java類,互相切換渠道才不會報錯,如果main只引用了app1中有的類,而app2下沒有這個類,那切換到app2渠道下肯定要報錯了。

gradle使用技巧

上面那些可以讓我們順利的寫代碼,但還不夠。比如環(huán)境配置,簽名配置,不同渠道下的各種三方key值,甚至不同環(huán)境都會有不同的key值等等,這些在正式開發(fā)中,肯定會遇到的。下面就給大家詳細的介紹下,遇到這些問題,該怎么去處理。

三方key值配置

三方key值一般都是寫在AndroidManifest中的,如:

        <!--微信id-->
        <meta-data
            android:name="WEIXIN_ID"
            android:value="******************" />

單渠道下,我們可以直接把id寫在AndroidManifest下,多渠道下,就需要改造一番:

        <!--微信id-->
        <meta-data
            android:name="WEIXIN_ID"
            android:value="${WX_KEY}" />

gradle中這樣配置:

productFlavors {
        main {
            applicationId "com.zdu.client"
            dimension "app"
            manifestPlaceholders = [
                    WX_KEY            : "*************",
            ]
        }
        app2 {
            applicationId "com.zdu.test"
            dimension "app"
            manifestPlaceholders = [
                    WX_KEY            : "%%%%%%%%%%%%%%%%%",
            ]
        }

    }

這樣配置之后,就能分渠道加載不同的key值。

簽名配置

多渠道下,僅支持debug多簽名配置,不支持release的多簽名配置,換句話說,release下只能配置一個簽名。
首先,新增一個debug簽名:

signingConfigs {
        release {
            keyAlias KEY_ALIAS
            keyPassword KEY_PASSWORD
            storeFile STORE_FILE
            storePassword STORE_PASSWORD
        }
        debug {
            keyAlias KEY_ALIAS
            keyPassword KEY_PASSWORD
            storeFile STORE_FILE
            storePassword STORE_PASSWORD
        }
        app2Debug {
            keyAlias KEY_ALIAS
            keyPassword KEY_PASSWORD
            storeFile STORE_FILE
            storePassword STORE_PASSWORD
        }
    }

然后配置簽名引用:

 buildTypes {
        debug {
            // main簽名
            productFlavors.main.signingConfig signingConfigs.debug
            //app2簽名
            productFlavors.app2.signingConfig signingConfigs.app2Debug
        }
}

由于只能配置debug環(huán)境的簽名,不能配置release的簽名,就導致不能多渠道多簽名開發(fā),只能共同使用一個簽名。當然非要多簽名開發(fā)也是可以的,就是每次換渠道手動改gradle文件,無非就是比較麻煩罷了。
不過話又說回來了,同一個公司的產(chǎn)品使用同一個簽名文件是很常見的事件,能省去很多麻煩。

不同環(huán)境下的key值配置

這個技巧挺實用的,比如各種統(tǒng)計三方的key,往往都是測試環(huán)境和正式環(huán)境不同,這個時候就需要這種來配置了。
正常開發(fā),一般最少會有倆環(huán)境,咱們先模擬一番:

buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }
        release { 
            signingConfig signingConfigs.release
        }
    }

在這里面如果想跟設(shè)置簽名的那種直接配置各種參數(shù)是不行的,這里就不舉例子了,不信的話各位可以試試。
這里要使用另外一種方式:

 android.applicationVariants.all { variant ->
        println(variant.name)
        if (variant.name == 'mainDebug') {
            buildConfigField "Integer", "ENV", "2"
        }

        if (variant.name == 'mainRelease') {
            buildConfigField "Integer", "ENV", "0"
        }
        if (variant.name == 'app2Debug') {
            buildConfigField "Integer", "ENV", "2"
        }

        if (variant.name == 'app2Release') {
            buildConfigField "Integer", "ENV", "0"

        }
    }


variant.name就是圖里面對應的名稱,環(huán)境不同,只需要在這個判斷里寫對應環(huán)境的值,然后在BuildConfig.ENV就能使用不同的值了。

結(jié)語

多渠道開發(fā)算是小眾開發(fā)方式,也正因為小眾,網(wǎng)上也沒有太多太詳細的資料。我寫這篇文章除了當做資料記憶之外,也希望能幫助到需要此功能的朋友們可以愉快的開發(fā)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

  • 文章:黃2 編輯:磚頭哥 1 ·. 我是個鄉(xiāng)下人。 我們家離鄉(xiāng)里有一公里。 鄉(xiāng)里有一個電影院。偶爾會放電影。一般...
    言午許墨閱讀 354評論 0 0
  • 森林深處晨起的迷霧讓我懷念已逝的慕夏,但枯蔫凋謝的鮮花又想讓我逃離。不想悲憤的去展示消極的一面,只想將自身正能量給...
    F腐物百曉生閱讀 1,476評論 1 4
  • 選擇自己的生活方式,與他人無關(guān) 快樂是內(nèi)心的一種感受 每個個體都是不同的,工作、生活方式也是不盡相同的。 自食其力...
    秋天的果實閱讀 317評論 0 0
  • 世事休驚聲悄悄。雄姿自有黃金繞。 歷史已成塵外道。誰知曉。 只看輪轉(zhuǎn)春秋老。 百萬秦兵何處好。人間惡起皆難掃。 獨...
    塵埃落定1閱讀 337評論 4 14
  • 如果一切遇見都需要醞釀 如果所有相識都需要標好駐點才能起航 如果每次邂逅都有所準備 才不至于慌慌張張 那么 我又該...
    小生的詩啊閱讀 3,037評論 92 56

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