關於Android打包

      最近在做廠商定製版的預裝APP,考慮到不同廠商的需求的差異化,和共性,所以就和組裏的人一起研究如何做到一份代碼,保持共性,但是通過開關和配置文件的方式,控制打包的時候,打出相應的APK包給對應的廠商,同時每個包的功能既有共性也有差異化的定製版部分,從而避免通過拉很多代碼分支的方式,去做差異化實現,減少維護的人力成本。

      舉個例子:目前有OPPO,中興兩個廠商,他們都要我們的APP,但是有部分需要修改,如APP的圖標,名字,APP的包名,以及升級需要接入他們自己的後臺,單獨做數據上報等差異化需求。如果是之前,我們會基於一個穩定版本,分別爲OPPO和中興拉兩個分支,然後去兩個分支上單獨開發,替換圖標,改包名,引入他們的升級sdk等。問題來了,這樣的維護成本太高了,尤其是每個廠商都要拉一個分支,10個廠商就10個分支,一個廠商上面發現的bug,需要改10個分支的代碼,簡直是恐怖。我們需要做的就是,只有一個分支,但是可以做資源替換,比如icon打包的時候動態去替換成不同的廠商的icon;升級的接入,通過開關去控制升級的方式,OPPO就走OPPO升級,中興就走中興升級,這樣打包的時候就做到了差異化,而且維護比較簡單。

      接下來一點一點說具體是怎麼實現的。

1.開關控制打包。

      一次打多個包,之前的文章裏面也提過,不過只是說了一點點。可以參看文章:瞭解build.gradle。這裏重點說下怎麼做開關控制,從而實現差異化打包。

       build.gradle裏面的buildTypesproductFlavors組合使用,可以打出多個包,比如:

buildTypes {

        debug {
            signingConfig signingConfigs.release
            debuggable true
        }

        release {
            signingConfig signingConfigs.release
            debuggable false
            minifyEnabled true
            shrinkResources false
            proguardFile 'proguard.cfg'
        }
    }
        buildTypes說明的是生成的包的類型都會有debugrelease兩種。
productFlavors {
        _default {}

        google {
            applicationId 'com.sun.google'
        }

        oppo {
            applicationId 'com.sun.oppo'
            buildConfigField "boolean", "KEEP_SHARE", "false"
        }
    }

       productFlavors說明的是會生成三個版本的包,默認的是_default,然後是googleoppo的。和buildTypes組合使用就是會生成2*3=6個包。這裏需要重點關注一個點,就是生成googleoppo包的時候,包的名字是會被替換掉的,這裏就是動態修改包名。

      那再考慮升級的策略,希望不同的廠商,都接入自己的sdk去做升級邏輯。我們想到的是利用模版設計模式,升級的主流程不變,同時有一個工廠類,根據打包的確定的是生成那個廠商的包,去創建具體的升級子類,然後走對應廠商的sdk升級。那麼這個開關是怎麼做到的呢?其實代碼裏面我們可以通過編譯成功之後的BuildConfig去獲取生成的是那個廠商的包的Flavor

if (BuildConfig.FLAVOR.equals("oppo")) {
    sOEMBaseReport = new OppoReport();
    sUpdateMethod = new OppoUpdate();
    sAppLaunchDialogHelper = new OPPOLaunchDialogHelper();
} else if (BuildConfig.FLAVOR.equals("google")) {
    sUpdateMethod = null; //google don't need to check update
    sAppLaunchDialogHelper = new AppLaunchDialogHelper();
} else {
    sUpdateMethod = new TrunkUpdate();
    sAppLaunchDialogHelper = new AppLaunchDialogHelper();
}

      這裏就是我們通過開關去做升級和數據上報等定製化模塊邏輯的差異化部分。

      當然開關還不僅這些,考慮場景,OPPO提需求說我們不想要APP支持分享邏輯,而google說我們需要支持分享,默認版本是需要支持分享邏輯的。那麼我們做差異化呢?也是BuildConfig

      我們build.gradle默認有個defaultConfig,我們可以在其中定義一個buildConfigField,類似於定義一個變量,我們可以選擇不同的包裏面,修改這個變量的值,然後在分享模塊入口的地方,加上if判斷,決定是否需要屏蔽分享入口。

defaultConfig {
        minSdkVersion 17
        targetSdkVersion 24
        applicationId "com.sun.innocentsun"
        signingConfig signingConfigs.release
        versionCode getBuildVersionCode()
        versionName getBuildVersionName()

        buildConfigField "boolean", "KEEP_SHARE", "true"
    }
    
    productFlavors {
        _default {}

        google {
            applicationId 'com.sun.google'
        }

        oppo {
            applicationId 'com.sun.oppo'
            buildConfigField "boolean", "KEEP_SHARE", "false"
        }
    }

     google的不去設置這個變量,就會和defaultConfig裏面一致。這樣就可以保證,oppo打出來的包,獲取BuildConfig.KEEP_SHARE = false。那麼我們代碼裏面就可以在入口加上一個if判斷下就搞定了。

2.資源替換。

      資源替換主要是icon和一些位置的圖標替換,以及字符串的替換,比如APP的名字等。其實還可以包括AndroidManifest清單文件的部分內容替換。

      資源替換具體的做法就是在src目錄下,新建一個文件夾,要和Flavors裏面的名字保持一致,然後新建一個子目錄res,再把需要替換的資源放進去,當然,因爲是替換資源,所以名字必須一致,也就是說,工程的res裏面的APP的圖標名字是icon.png,那麼你OPPO包需要更換APP圖標的話,更換的圖片名字必須也命名爲icon.png才能被替換掉。可以參看我的工程目錄,這裏只列了一個oppo的替換,google的也是一樣的。

      名字都一樣才能替換,不一樣的是不能被替換的。而且這個文件夾名字一定要和productFlavors裏面一致,比如這裏都是“oppo”。

      另外,APP的名字,這個需要在string.xml中也寫一個相同的名字的變量,但是賦值不同。對比圖:


      AndroidManifest清單文件裏面的內容也是可以替換的,比如把一個activity移除,然後用另外一個同名但是包名路徑不同的activity替代。

      第一段是把本來在工程主路徑下的清單文件裏面的HelloWorldActivity給取消註冊,然後引入自己路徑下的同名Activity,這樣就實現了替換。

      注意:上面說的都是替換,不是新增,如果想實現,主程序裏面沒有的資源文件,然後OPPO版本纔有的這樣新增,是不行的,只能實現替換。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章