Gradle多渠道打包(動態設定App名稱,應用圖標,替換常量,更改包名,變更渠道)

最近有個需求一次要打包9個類型的App,而且常量和String.xml都有變量。雖然之前也是一直存在變量,但是每次也僅僅只打包一個。這讓我每次改變量,打包9個。要是以後每次都打包9次,我得瘋了。
根據之前的瞭解,gradle 應該是可以解決這個問題的。所以就仔細研究了一番。


先放一個完整的 多渠道/多環境 打包的配置,然後再來講解。

實現了:

  1. 不同環境,不同包名;
  2. 不同環境,修改不同的 string.xml 資源文件;
  3. 不同環境,修改指定的常量;
  4. 不同環境,修改 AndroidManifest.xml 裏渠道變量;
  5. 不同環境,引用不同的 module。

先放一個完整的配置,可以參考:

 

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion '22.0.1'

    // 簽名文件
    signingConfigs {
        config {
            keyAlias 'lyl'
            keyPassword '123456'
            storeFile file('../lyl.jks')
            storePassword '123456'
        }
    }

    // 默認配置
    defaultConfig {
        //applicationId "com.lyl.app"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0.0"

        signingConfig signingConfigs.config
        multiDexEnabled true
        
        // gradle 3.0.0 以上需要有這個
        // flavorDimensions "app"
    }

    // 多渠道/多環境 的不同配置
    productFlavors {
        dev {
            // gradle 3.0.0 以上需要有這個
            // dimension "app"
            
            // 每個環境包名不同
            applicationId "com.lyl.dev"
            // 動態添加 string.xml 字段;
            // 注意,這裏是添加,在 string.xml 不能有這個字段,會重名!!!
            resValue "string", "app_name", "dev_myapp"
            resValue "bool", "isrRank", 'false'
            // 動態修改 常量 字段
            buildConfigField "String", "ENVIRONMENT", '"dev"'
            // 修改 AndroidManifest.xml 裏渠道變量
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "dev"]
        }
        stage {
            // gradle 3.0.0 以上需要有這個
            // dimension "app"
        
            applicationId "com.lyl.stage"

            resValue "string", "app_name", "stage_myapp"
            resValue "bool", "isrRank", 'true'

            buildConfigField "String", "ENVIRONMENT", '"stage"'

            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "stage"]
        }
        prod {
            // gradle 3.0.0 以上需要有這個
            // dimension "app"
        
            applicationId "com.lyl.prod"

            resValue "string", "app_name", "myapp"
            resValue "bool", "isrRank", 'true'

            buildConfigField "String", "ENVIRONMENT", '"prod"'

            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "prod"]
        }
    }

    dexOptions {
        incremental true
        // javaMaxHeapSize "4g"
    }

    //移除lint檢測的error
    lintOptions {
        abortOnError false
    }

    def releaseTime() {
        return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
    }

    buildTypes {
        debug {
            signingConfig signingConfigs.config
        }

        release {
            buildConfigField("boolean", "LOG_DEBUG", "false")
            minifyEnabled false
            zipAlignEnabled true
            //移除無用的resource文件
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.config

            // 批量打包(gradle 3.0.0 以下)
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        //輸出apk名稱爲:渠道名_版本名_時間.apk
                        def fileName = "${variant.productFlavors[0].name}_v${defaultConfig.versionName}_${releaseTime()}.apk"
                        output.outputFile = new File(outputFile.parent, fileName)
                    }
                }
            }
            
            // 批量打包(gradle 3.0.0 以上)
            // android.applicationVariants.all { variant ->
            // variant.outputs.all {
            //     outputFileName = // "${variant.productFlavors[0].name}_v${defaultConfig.versionName}_${releaseTime()}.apk"
            // }
        }
        }
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.facebook.android:facebook-android-sdk:4.0.0'
    compile project(':qrscan')
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile 'com.google.code.gson:gson:2.3'
    compile files('libs/android-async-http-1.4.6.jar')
    compile 'com.google.android.gms:play-services:7.5.0'
    compile 'com.android.support:support-annotations:22.1.1'
    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'de.hdodenhof:circleimageview:2.1.0'
}

接下來我們來詳細看看修改特定的字段。

不同環境的設置基本都是在 productFlavors 裏設置的,
而且在裏面你想添加多少個環境都可以。

1. 不同環境,不同包名;

 

productFlavors {
    dev {
        applicationId "com.lyl.dev"
    }

    stage {
        applicationId "com.lyl.stage"
    }

    prod {
        applicationId "com.lyl.prod"
    }
}

這裏注意,在 defaultConfig 中,大家應該都是寫了個默認的 applicationId 的。
經測試,productFlavors 設置的不同環境包名會覆蓋 defaultConfig 裏面的設置,
所以我們可以推測,它執行的順序應該是先執行默認的,然後在執行分渠道的,如果衝突,會覆蓋處理,這也很符合邏輯。

2. 不同環境,添加 string.xml 資源文件;

利用 resValue 來定義資源的值,顧名思義 res 底下的內容應該都可以創建,最後用 R.xxx.xxx 來引用。
如下就根據不同的類型,添加了不同的 app_name 字段,以及定義了 布爾值,可以通過 R.string.app_name 來引用。

注意,這裏是添加,是在 string.xml 裏面添加了一個字段app_name,所以在現有的 string.xml 中不能有這個字段,否則會報錯!!!

 

productFlavors {
    dev {
        resValue "string", "app_name", "dev_myapp"
        resValue "bool", "isrRank", 'false'
    }
    stage {
        resValue "string", "app_name", "stage_myapp"
        resValue "bool", "isrRank", 'true'
    }
    prod {
        resValue "string", "app_name", "myapp"
        resValue "bool", "isrRank", 'true'
    }
}

通過以上我們大概可以推測出 color、dimen 也可以通過類似的方法添加。

3. 不同環境,動態修改指定的常量;

使用 BuildConfig 的變量。

①定義字段

當我們定義如下字段之後,編譯後自動生成文件,在 app/build/source/BuildConfig/dev/com.lyl.dev/BuildConfig 目錄,
打開這個文件,我們就能看到我們所定義的字段了。

 

productFlavors {
    dev {
        buildConfigField "String", "ENVIRONMENT", '"dev"'
    }
    stage {
        buildConfigField "String", "ENVIRONMENT", '"stage"'
    }
    prod {
        buildConfigField "String", "ENVIRONMENT", '"prod"'
    }
}

②引用字段

在我們自己的任意的類中,來直接通過 BuildConfig 就可以調用我們定義的字段。

 

public class Constants {
    public static final String ENVIRONMENT = BuildConfig.ENVIRONMENT;

}

注意: 這裏有個小細節,看其中第三個參數,是先用了“'”,然後在用了“"”,這種語法在 Java 裏可能比較陌生,但是在很多其他語言中,這種用法是很常見的。

它的意思是 "dev" 這個整體是屬於一個字符串,至於爲什麼要這麼寫,你把單引號去掉,然後去 app/build/source/BuildConfig/dev/com.lyl.dev/BuildConfig 這個文件看一看就知道了。

4. 不同環境,修改 AndroidManifest.xml 裏渠道變量

①在 AndroidManifest.xml 裏添加渠道變量

 

<application
    android:icon="${app_icon}"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
    ...
    <meta-data
        android:name="UMENG_CHANNEL"
        android:value="${ENVIRONMENT}" />
    ...
</application>

②在 build.gradle 設置 productFlavors

 

productFlavors {
    dev {
        manifestPlaceholders = [ENVIRONMENT: "dev",
                                app_icon   : "@drawable/icon_dev"]
    }
    stage {
        manifestPlaceholders = [ENVIRONMENT: "stage",
                                app_icon   : "@drawable/icon_stage"]
    }
    prod {
        manifestPlaceholders = [ENVIRONMENT: "prod",
                                app_icon   : "@drawable/icon_prod"]
    }
}

這樣我們可以在不同環境使用不同的 key 值。

5. 不同環境,引用不同的 module

這個就很強大了,根據不同的環境,引用對應的 module。
你可以替換大量的圖片,string,color,vaule等等。

首先,要建立跟渠道對應的 module,然後再引用。
引用方式如下:

 

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    // 引用本的項目
    devCompile project(':devModule')
    stageCompile project(':stageModule')
    prodCompile project(':prodModule')

    // 也可以分渠道引用網絡的。因爲這裏都相同,所以地址也就都一樣了
    devCompile 'com.roughike:bottom-bar:2.0.2'
    stageCompile 'com.roughike:bottom-bar:2.0.2'
    prodCompile 'com.roughike:bottom-bar:2.0.2'
}

xxxCompile 代表 各個渠道的名稱。
然後把需要分渠道的文件,放到不同的 module 裏面,把主項目的文件刪掉。
千萬注意: 如果這樣做了,每次需要引用的時候,在各個渠道的 module 裏面都必須要放置文件哦,不然會找不到資源。
通過這種方式可以替換整套素材資源,具體如何使用還得看項目需求。

通過以上方式,我們基本可以 通過 gradle 動態設定應用標題,應用圖標,替換常量,設置不同包名,更改渠道等等。


打包編譯

最後,做完所有的配置之後,然後就是打包操作了。

打包某一個(日常編譯)

因爲 buildTypes 裏面有兩種,所以每個渠道都會有兩種模式。

打包所有的,就是正常打包流程

如圖所示:

圖片來源網絡

圖片來源網絡

 
打包完成之後,然後就可以在我們指定的目錄下,看到我們所生成的apk包。


使用 local.properties 存放私密配置

以上就可以基本實現 gradle 的設置,但是如果我們要將我們的項目上傳到 Github ,或者要將項目發送給別人。上面有些私密的東西就會被別人看到。比如:.jks 文件的密碼。

在項目跟目錄下,有個 local.properties 文件,我們可以使用它來存放一些私密的屬性,然後在 gradle 中讀取,而 local.properties 文件不需要上傳。
local.properties 文件裏設置如下:

 

sdk.dir=D\:\\Android\\android-sdk

gaodeKey=e348025dd034d1c347dd0345e34802

keyPassword=123456

在 build.gradle 讀取 local.properties 字段信息

 

// 加載 local.properties 資源
Properties properties = new Properties()
InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream() ;
properties.load( inputStream )

android {
    ...

    // 簽名文件
    signingConfigs {
        config {
            keyAlias 'lyl'
            // 獲取 local.properties 字段信息
            keyPassword properties.getProperty( 'keyPassword' )
            storeFile file('../lyl.jks')
            storePassword properties.getProperty( 'keyPassword' )
        }
    }

    ...
}

這樣就可以將自己想要隱藏的一些數據隱藏起來。


可能加快 Android Studio 編譯的辦法

1. 在根目錄的 build.gradle 里加上如下代碼:

 

allprojects {
    // 加上這個
    tasks.withType(JavaCompile) {
        //使在一個單獨的守護進程編譯
        options.fork = true
        //增量編譯
        options.incremental = true
    }

    repositories {
        jcenter()
    }
}

2. 在 app 級別下 build.gradle 裏 加上

 

android {
    dexOptions {
        incremental true
    }
}

最後放上一個多渠道的項目地址,可以參考:
https://github.com/Wing-Li/boon

OK。
如果本文有什麼問題,請一定指出。
O(∩_∩)O

 

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