1. 程式人生 > >支援資原始檔替換的多渠道打包外掛(四)

支援資原始檔替換的多渠道打包外掛(四)

一、寫在前面

經過前面幾篇文章的學習(什麼?你還沒看,趕緊去補補!),對gradle已經有了大致的瞭解了,當學習完後一定需要碼程式碼來鞏固一下,所謂:talk is cheap show me the code!今天就帶大家來一場gradle實戰,做一個有實用價值的自定義外掛。

不知道大家有沒有遇到過這樣的需求:公司有一款產品,而客戶需要將公司產品做制定化操作,如:修改app包名、appIcon、appName、以及引導頁等一些資原始檔,以便客戶展示他自己的廣告。這樣可能會有很多定製化產品需要去打包,以前採用一個一個手動更改並打包,一打就是一下午,隨著定製化越來越多,每次更新都要這樣打,估計都快瘋了吧。
這個時候大家首先會想到利用Android系統的多渠道打包方法productFlavors,比如這樣:

android  {
    productFlavors {
        xiaomi{
        applicationId  "com.xiaomi.cn"
        }
        google{
        applicationId  "com.google.cn"
        }
        huawei{
        applicationId  "com.huawei.cn"
        }
    }
}

這樣就可以修改包名,appName等一些需求,但是資原始檔可能就不太方便了,可能有童鞋會說,在res下面多放幾張圖片,然後利用manifestPlaceholders來修改,沒錯,這樣的方式也可以實現,不過萬一你公司的渠道定製包很很多呢?100個、500個,難道需要放那麼多張沒用的圖片進去?那app得多大。
其實主要就是這兩個問題:
1、打包時資原始檔無法自動更換
2、若手動更換,一個一個打成百上千的包那時間成本可不是蓋的
那麼今天,我們就來寫一個自動替換資原始檔的gradle外掛,徹底解決這個問題。

二、自定義外掛

首先我們需要寫個自定義外掛,具體步驟我在第一篇系列文章裡提到過,也有推薦文章,這裡我就放出外掛結構就好(文末有DEMO,大家可以有需要的話可以檢視)
自定義外掛結構
首先我們需要寫一個Plugin,並重寫他的apply方法:

public class ResourceFlavorsPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        //這裡寫自定義內容

    }
}

首先我們理一下我們思路:
1、我們需要打很多渠道包
2、每個渠道包都需要修改包名、資原始檔等
第一點我們利用AndroidDSL來實現:

android {
    flavorDimensions "define"
    productFlavors {
        "define1" {
            dimension "define"
            applicationId "com.atom.define1"
            manifestPlaceholders.put("appName", "定製1")
        }
        "define2" {
            dimension "define"
            applicationId "com.atom.define2"
            manifestPlaceholders.put("appName", "定製2")
        }
        //其他渠道省略....
    }
}

這個很簡單就不多說了,下面就到我們需要做的事情:修改資源。
我們需要在打每個渠道包之前把對應資原始檔修改成相應的,而我們每次打包都需要執行assemble系列方法,比如
打所有釋出版的包:assembleRelease
打define1的釋出版的包:assembleDefine1Release
以此類推,而根據上一篇文章我們又知道assemble依賴了許多task,這樣的話我們就好辦了,只需要在執行資源合併task之前就修改資原始檔就好了。
檢視原始碼發現preBuild這個task就是最先執行的幾個task之一,所以我們需要獲取到該task,執行他的doFirst即可

project.android.applicationVariants.all { variant ->
                String variantName = variant.name.capitalize()
                def variantFlavorName = variant.flavorName
                Task preBuild = project.tasks["pre${variantName}Build"]
                if (variantFlavorName == null || "" == variantFlavorName) {
                    return
                }
                preBuild.doFirst {
                    //在這裡替換資原始檔
                    println "${variantFlavorName} resource is changed!"
                }
            }

利用applicationVariants獲取variant,然後就能獲取到variantName也就是渠道包的打包方式,然後做一些字串拼接,就獲取到相應task名稱,然後再從Project的taskContainer裡取出就好。

下一步就需要修改資原始檔了,而gradle如何實現這一操作呢?我也不知道,這時候只能求助官方了,通過一些時間的查閱,我終於在官方文件中找到了這個方法,其實很簡單:
copy
這是在project下的一個方法,官方文件介紹的很詳細了,連demo都有,可以說相當良心了。
from和into後面分別跟原始檔和被替換的檔案就可以了,當然,資料夾也行,所以我們的程式碼就變成了這樣

project.android.applicationVariants.all { variant ->
                //...
                preBuild.doFirst {
                    project.copy {
                        from "../resourceDir/${variantFlavorName}"
                        into "../app/src/main/res"
                    }
                    println "${variantFlavorName} resource is changed!"
                }
            }

為了更好的擴充套件性,能夠在build檔案中設定原始檔位置、名稱等,我們可以用extension來操作,首先建立一個pojo類

public class FlavorType {
    /**
     * 存放渠道包圖片的路徑
     */
    String resourceDir
    /**
     * 主專案名
     */
    String appName
}

再稍微修改一下我們的程式碼:

        project.extensions.add("rfp", FlavorType)
        project.afterEvaluate {
            FlavorType ext = project.rfp
            def resourceDir = ext.resourceDir
            def appName = ext.appName

            project.android.applicationVariants.all { variant ->
                //...
                preBuild.doFirst {
                    project.copy {
                        from "../${resourceDir}/${variantFlavorName}"
                        into "../${appName}/src/main/res"
                    }
                    println "${variantFlavorName} resource is changed!"
                }
            }
        }

這樣就可以在build檔案中自定義了,就像這樣:

rfp{
    resourceDir 'definepic'
    appName 'app'
}

OK,大功告成!我們來看看最後的成果把!
這裡寫圖片描述

三、結語

本外掛最重要的是理解為何我們可以在task前做操作以及在哪個task前做操作。
如果是看完我之前三篇文章的童鞋相信很容易理解此文,其實無非就是對gradle以及groovy語法的運用而已,這個就需要時間的積累了。
好了,此係列暫時到此就結束了,需要看Demo的童鞋請點選下面的傳送們:
github地址
如果對您有幫助的話,希望給個star鼓勵一下~謝謝

最最最後:我也把該專案上傳到了jcenter,可以直接使用哦~具體參見github說明