Android打包之多版本、多環境、多渠道

在平時的Android開發中,我們經常會遇到在不同網絡環境(比如:開發環境、測試環境)之間的切換、一次打多個渠道包等需求,如何優雅的管理網絡環境的配置?如何快速的打出多個渠道包?這是一個值得研究的問題。

如果每一次在不同網絡環境間切換,都需要更改代碼,然而重新打包,那未免有點低效。下面是我的實踐探索,看網上很多人都是根據buildType來切換網絡環境,感覺有點不好,因爲網絡環境可能很多種,而buildType我們一般是2種,而且,不同網絡環境的包最好能同時安裝在手機上,以便我們調試。最好,我一看這個包的名稱和圖標,就能知道這是什麼環境的包。

一、概述

1.多版本

基於buildTypes

(1)debug:調試版本,無混淆
(2)release:發佈版本,有混淆、壓縮

2.多環境

基於productFlavors

(1)develop:開發環境,開發和自測時使用
(2)check:測試環境,克隆一份生產環境的配置,在這裏測試通過後,再發布到生產環境。
之所以沒命名爲test是因爲在gradle編譯時:ProductFlavor names cannot start with 'test'
(3)product:生產環境,正式提供服務的。

3.多渠道

基於Android新的應用簽名方案APK Signature Scheme v2中的APK Signing Block區塊

我這裏使用的是美團封裝的Walle庫。使用Walle庫請確保你的Android Gradle 插件版本在2.2.0以上。

爲什麼不直接使用productFlavors來打包多渠道?因爲productFlavors打多渠道包太慢了,打30個包差不多十幾分鍾,無法忍受!

爲什麼不使用美團之前基於META-INF進行渠道標識的方案?因爲Android7.0之後的這種黑科技已經失效了!

二、示例

1、配置build.gradle

(1) 在位於項目的根目錄 build.gradle 文件中添加Walle Gradle插件的依賴, 如下:

buildscript {
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
        classpath 'com.meituan.android.walle:plugin:1.0.3'
    }
}

(2) 在當前App的 build.gradle 文件中apply這個插件,並添加上用於讀取渠道號的aar

apply plugin: 'com.android.application'
apply plugin: 'walle'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {
        release {
            keyAlias KEY_ALIAS
            keyPassword KEY_PASSWORD
            storeFile rootProject.file(KEYSTORE_FILE)
            storePassword KEYSTORE_PASSWORD
        }
    }

    buildTypes {
        //調試版本,無混淆
        debug {
            minifyEnabled false
            signingConfig signingConfigs.release
        }
        //發佈版本,有混淆
        release {
            minifyEnabled true
            zipAlignEnabled true
            shrinkResources true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    productFlavors {
        //開發環境
        develop {
            buildConfigField "int", "ENV_TYPE", "1"
            applicationId 'om.soubu.walledemo.develop'
            manifestPlaceholders = [
                    app_name: "開-WalleDemo",
                    app_icon: "@drawable/icon_develop"
            ]
        }
        //測試環境
        check {
            buildConfigField "int", "ENV_TYPE", "2"
            applicationId 'om.soubu.walledemo.check'
            manifestPlaceholders = [
                    app_name: "測-WalleDemo",
                    app_icon: "@drawable/icon_check"
            ]
        }
        //生產環境
        product {
            buildConfigField "int", "ENV_TYPE", "3"
            applicationId 'com.soubu.walledemo.product'
            manifestPlaceholders = [
                    app_name: "WalleDemo",
                    app_icon: "@drawable/icon_product"
            ]
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.1.0'
    testCompile 'junit:junit:4.12'

    compile 'com.meituan.android.walle:library:1.0.3'
}

(3) 這裏,我根據不同的環境生成了不同包名的apk,方便在手機上同時安裝多個環境的應用。爲了讓gradle動態更改apk的名稱和圖標,我們需要在manifest文件中使用{app_name}等佔位符

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.soubu.walledemo">

    <application
        android:allowBackup="true"
        android:icon="${app_icon}"
        android:label="${app_name}"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

(4) 在代碼中獲取多渠道信息

String channel = WalleChannelReader.getChannel(getApplicationContext());

(5) 在代碼中獲取多環境信息

int envType = BuildConfig.ENV_TYPE;

這裏的BuildConfig是由gradle動態生成的:

package com.soubu.walledemo;

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "om.soubu.walledemo.develop";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "develop";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
  // Fields from product flavor: develop
  public static final int ENV_TYPE = 1;
}

而ENV_TYPE這個字段其實就來自於我們的build.gradle:

    productFlavors {
        //開發環境
        develop {
            buildConfigField "int", "ENV_TYPE", "1"
            applicationId 'om.soubu.walledemo.develop'
            manifestPlaceholders = [
                    app_name: "開-WalleDemo",
                    app_icon: "@drawable/icon_develop"
            ]
        }
    {

這裏我們最好定義一個常量類區分這些環境的類型:

public class EnvType {
    public static final int DEVELOP = 1;//開發環境
    public static final int CHECK = 2;//測試環境
    public static final int PRODUCT = 3;//正式環境
}

2、打包多環境

這裏我們直接執行assemble命令,打包所有的buildType*productFlavors

或者使用命令行也可以:

gradle assemble

執行結果:26秒搞定6個包:2個版本*3個環境

這裏我們可以看到debug包都是1.4M,而release包都是0.7M,顯然,我們的混淆和壓縮配置是生效了的,雖然這裏我並沒寫混淆規則

我們分別安裝3個環境的包到自己的手機上:

看三個包的名稱和圖標都不一樣,顯然我們之前在manifest文件中配置的佔位符生效了。

然後我們點進去分別看看這3個app的區別:

這樣,我們就可以在代碼中,根據環境字段envType的不同,來選擇不同的網絡環境了。

界面的代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tvEnv = (TextView) findViewById(R.id.tv_env);
        TextView tvChannel = (TextView) findViewById(R.id.tv_channel);
        TextView tvPackage = (TextView) findViewById(R.id.tv_package);

        String channel = WalleChannelReader.getChannel(this.getApplicationContext());
        int envType = BuildConfig.ENV_TYPE;
        String packageName = getPackageName();

        switch (envType) {
            case EnvType.DEVELOP:
                tvEnv.setText("envType=" + "開發環境");
                break;
            case EnvType.CHECK:
                tvEnv.setText("envType=" + "測試環境");
                break;
            case EnvType.PRODUCT:
                tvEnv.setText("envType=" + "生產環境");
                break;
        }
        tvChannel.setText("channel=" + channel);
        tvPackage.setText("package=" + packageName);

    }
}

3、打包多渠道

在Project的根目錄下新建channel文件:

anzhi #安智
baidu #百度
huawei #華爲
oppo #oppo
wdj #豌豆莢
xiaomi #小米
yyb #應用寶

執行gradle命令:
(1) 打包文件內的渠道包

gradle assembleProductRelease -PchannelFile=channel

(2) 打包自定義數組內的渠道包

gradle assembleProductRelease -PchannelList=qihu,vivo,lenovo

關於Walle庫的更多使用:詳見Github-walle

運行結果:17秒搞定8個包:1個默認包+7個渠道包

最後,奉上源碼:WalleDemo

常見問題

1、找不到簽名文件的配置?

汗,因爲我的Demo中並沒有上傳我的jks文件,你可以添加自己的jks文件,然後在gradle.properties裏面配置好籤名文件的密碼即可

在gradle.properties添加簽名文件的配置key-value

在build.gradle中引用配置的key

2、develop、check、product,如果直接run代碼,怎麼設置默認的環境?
點擊查看AndroidStudio左下角的BuildVariants,然後選擇設置默認的run環境即可。

BuildVariants= buildTypes* productFlavors

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