Android - 使用 多渠道productFlavor、多客户、多版本buildType 、多模块moudle 配置的 那些点点滴滴

---------------------------------------------------------------------------------------------------------------------------------------------------------------

转载请声明:本文来自 https://blog.csdn.net/shijianduan1/article/details/83410267

---------------------------------------------------------------------------------------------------------------------------------------------------------------
项目 使用 多渠道 已经蛮久了, 最近又有新需求过来了,所以多渠道这块也重新看了下。。。。

然而新需求很坑爹(或许是我能力不够),反正 事实就是  浪费了我两天时间, 看了N多博客,也没找到对应的解决方案

---------------------------------------------------------------------------------------------------------------------------------------------------------------

本片 文章的Demo(不涉及界面,只是个框架搭建) 已经上传GitHub https://github.com/sjindong/Testduoqudao 

 

一、总览

  1. buildType

  2. productFlavors
  3. sourceSets
  4. 占位符 manifestPlaceholders   和  动态参数buildConfigField
  5. 编译变体版本(修改名称,自定义编译)
  6. 多模块的动态参数配置
  7. 个人小结
  8. 疑问
  9. 拓展

   以下两片参考文章 提供了详细的 gradle中的相关参数接口

     参考一:Android build.gradle之buildTypes {} 

      参考二:Android Gradle Plugin中 ApplicationVariant文件内的可用属性

二、buildType  编译类型

  1. 可以重定义 一切  defaultConfig{} 里的参数 和 android{} 下的直接参数 (后者未完全验证,有误请指正)
  2. 可以定义 定位符(manifestPlaceholders) ,动态参数(buildConfigField)、签名、混淆等等配置,
  3. 可以自定义增加 如下面(test),通过initwith,重新拷贝了一份debug的配置(当前已经定义的debug配置)
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug{

        }
        test.initWith(buildTypes.debug)
        test{

        }
    }

 

三、productFlavors  渠道配置 (因为一开始是因发布渠道写的,所以渠道号叫习惯了,并非正式翻译)

        其实看了下百度翻译 是产品特点,主要是 各个不同的配置。(1和2都可上面一样)

  1. 可以重定义 一切  defaultConfig{} 里的参数 和 android{} 下的直接参数 (后者未完全验证,有误请指正)
  2. 可以定义 定位符(manifestPlaceholders) ,
  3. 动态参数(buildConfigField)、
  4. 签名、
  5. 混淆等等配置,
  6.  productFlavors 使用的时候 需要现在 defaultConfig{} 配置参数 flavorDimensions, 然后再在productFlavors定义中申明对应的纬度
 android{
   defaultConfig {
        flavorDimensions "Char","Num"
    }

   productFlavors {
        A {
            dimension "Char"
        }
        B {
            dimension "Char"
        }
        Num1{
            dimension "Num"
        }
        Num2{
            dimension "Num"
        }
    }
}

1. flavorDimensions 是个维度参数配置项,  用数组比较好理解,

     我这里是2个维度,所以对应二维数组,[A,B][Num1,Num2],  

      如果是 三维, 那么就是 对应三位数组 [A,B] [Num1,Num2] [C1,C2]

2.对应 会编译出来的版本 有12个, 2*2*3(3是 buildType时候自定义了一个aaa)

   版本名字顺序是 根据 flavorDimensions顺序 , 【维度一】【维度二】【纬度...】【buildType】

三、sourceSet  资源文件配置

  1.   如果不设置,则是android默认的,
  2. main 代表主干代码 (下面的是已经对主干默认路径修改过的)
  3. Num1是 分支代码,Num1和上面的渠道号定义保持一致。
  4.  不同的渠道号可以指向相同的路径,即他们引用同一个文件
  5. java文件不会被替换,(即,"src/main/java/a.java" 和  "src/Num1/java/a.java" 是同一路径文件,编译会报错告诉你已经定义了a.java文件) 
    1. aidl文件和jni文件 未验证,还不清楚
    2. 使用:用函数调用:定义非main的两个目录下,相同结构路径,相同文件名,相同函数名,(方法内容不同)

                             用接口调用:定义非main的两个目录下,相同结构路径,实现相同接口 (具体类不同)

  1. res下的资源文件,同路径下可以被直接替换
android{    
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs']
        }
        Num1 {
            manifest.srcFile 'src/' + 'Num1' + '/BctcManifest.xml'
            assets.srcDirs = ['src/' + 'Num1' + '/assets']
            java.srcDirs = ['src/' + 'Num1' + '/java']
        }
    }
}

  根据上面的 配置, 去将代码 或 资源文件等 放入对应的路径

 

四、动态参数定义

动态参数定义 有两种, 一个是 占位符  manifestPlaceholders = []  , 一个是 buildConfigField()

  1. 占位符 manifestPlaceholders :就是在前面代码里面 直接预留一个位置,然后等编译的时候 把数据放进去,
  2. buildConfigField :是在 BuildConfig.java 这个自动生成的文件里面去定义一个参数

 

  1. 两个均可以在 defaultConfig 、buildtype 、productFlavors 三处定义
  2. 均可在代码里使用, 
  3. 占位符  manifestPlaceholders 可以在xml文件中使用,如AndroidManifest.xml
 manifestPlaceholdersbuildConfigField
在 defaultConfig{} 定义 
在 buildtype {} 定义
在 productFlavors {} 定义
 AndroidManifest.xml 使用×
 代码中  使用(要xml定义,再代码获取)

 

 

buildConfigField 使用:  在定义String类型值 需要 对  双引号 转义, 即  需要 "ttt", 那么定义的时候需要把双引号也写进去, 写成 " \"ttt\"  "

productFlavors {
  A {
        dimension "Char"
        buildConfigField("String", "test","\"ttt\"")
        buildConfigField("int", "test1","123")
        buildConfigField("boolean", "test2","true")
    }
}

使用:

public void test(){
 String s = BuildConfig.test;
 int s1 = BuildConfig.test1;
 boolean s2 = BuildConfig.test2;
}

运行结果: 

manifestPlaceholders   在AndroidManifest.xml中使用

定义:

  productFlavors {
        Num1 {
            dimension "Num"
            manifestPlaceholders = [test_manifestPlace1: "替代的内容1",test_manifestPlace2: "替代的内容2"]
        }
   }

  在AndroidManifest.xml里直接使用:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="test7permission.cn.testduoqudao">
    <application>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="${test_manifestPlace1}"/>
                <action android:name="${test_manifestPlace2}"/>
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

运行结果:

manifestPlaceholders 代码中使用

  定义:   需要在build.gradle里面定义初始值,再通过AndroidManifest.xml来转达一下


    productFlavors {
        Num1 {
            dimension "Num"
            manifestPlaceholders = [test_manifestPlace1: "替代的内容1",test_manifestPlace2: "替代的内容2"]
        }
    <application
      >
        <activity android:name=".MainActivity">
            <!--//这一句起到至关重要作用-->
            <meta-data android:name="test_activity" android:value="${test_manifestPlace1}"/>
        </activity>

        <!--//这一句起到至关重要作用-->
        <meta-data android:name="test_application" android:value="${test_manifestPlace2}"/>
    </application>

在代码里使用:

    public static String init(Application application) {
        String result = "";
        try {
            ApplicationInfo applicationInfo = application.getPackageManager().getApplicationInfo(application.getPackageName(), PackageManager.GET_META_DATA);
            result = applicationInfo.metaData.getString("test_application");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return result;
    }

    public static String init(Activity activity) {
        String result = "";
        try {
            ActivityInfo activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), PackageManager.GET_META_DATA);
            result = activityInfo.metaData.getString("test_activity");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return result;
    }

参考:Android Gradle manifestPlaceholders自定义变量取值

          (这个blog说 还可以在 service,receive 里面使用(使用同上原理), 但是很遗憾我写的时候报错,有些无法引用, 所以就没贴出来,有需要的就自行去研究下吧)

 

五、变体版本jar引用

         既然代码、配置都可以做变体区分,那么jar包引用没有理由不可以根据变体区分

         这里有两种方法(个人尝试结果),如下

     方法一:变体单个引用jar

 dependencies {
    AImplementation(name: 'aaa', ext: 'aar')//无日志,本地包
    BImplementation 'aaa@aar'//有日志,远程包
}

     方法二:使用if else 判断变体引用jar包

 dependencies {
    if (A) {  //变体类型
        implementation(name: 'aaa', ext: 'aar')//无日志,本地包
    } else {
        implementation 'aaa@aar'//有日志,远程包
    }
}

   两方法各有优劣,各自的适用场景,
   方法一:适合那些变体单独引用的jar包,

   方法二:适合同一jar包那些版本之间有冲突的,为了兼容性

      (我就是遇到这个情况,有个变体必须要无日志的,而之前的jar包不提供关闭日志的借口)

    

六、编译变体版本(修改名称,自定义编译)

 1. 修改apk生成的名称,效果是 app_v版本名称_日期_维度名称_buildType(版本号).apk

android{
 android.applicationVariants.all { variant ->
        variant.outputs.all {
            def fileName = "app_v${defaultConfig.versionName}_${releaseTime()}_${variant.flavorName}_${variant.buildType.name}(${variant.versionCode}).apk"
            outputFileName = fileName
        }
    }
}

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

 

2.前面因为 buildType 和 productFlavors 维度版本,这样基本会有很多版本, 

  当我们执行 assemble 会默认遍历所有的版本生成一遍,但是有时候有些版本是不需要的,这个时候就可以使用下面配置进行屏蔽了(下面配置是单维度的,多维度的判断和这个有点区别)

android{    
    //执行assemble时 过滤不必要的版本
    android.variantFilter { variant ->
        if ('release'.equals(variant.buildType.name)) {
            variant.getFlavors().each() { flavor ->
                //这里需要注意下, 多维度 和此处不一样
                if ('Num1'.equals(flavor.name)) {
                    variant.setIgnore(true)
                }
            }
        }
    }
}

七、多模块的动态参数配置

     当主模块app配置了参数,而有其他moudle也配置了相同参数, 一两个模块还好,还可以手动修改

   但是当模块以多,那么每次有变动需要新增/修改,就会担心会不会又哪里漏掉

    所以 以下操作是针对多module同一配置参数的,包括不限于 自定义参数,defaultConfig(),引用第三方的包,各种参数设置(由于用途较多,请自行开脑洞)

1. 在根路径下面, 新建config.gradle,(和整个项目的build.gradle同一级目录) 

文件下面 加了些参考配置,如果有需要保持各个moudle的版本参数都保持一致,或者 引进jar包  都保持一致的话,自行选择性添加。

//使用 需要在build.gradle 头部添加 引用 : apply from: "config.gradle"

ext {
    /*------------------------- 签名配置--------------------------------------*/
    signingConfigs = [
            "filePath"     : "aaa.keystore",
            "keyAlias"     : "bbb",
            "keyPassword"  : "ccc",
            "storePassword": "dddd"
    ]
    /*------------------------- 签名配置--------------------------------------*/

    /*------------------------- 后台地址配置--------------------------------------*/
    url = [
            "API_HOST_DEBUG"  : "\"https://*.*.*.*/\"",
            "API_HOST_RELEASE": "\"https://*.*.*.*/\""
    ]
    url2 = [
            "API_HOST_DEBUG"  : "\"https://*.*.*.*/\"",
            "API_HOST_RELEASE": "\"https://*.*.*.*/\""
    ]
    /*------------------------- 后台地址配置--------------------------------------*/


    /*------------------------- 测试后台配置--------------------------------------*/
    test = [
            "API_HOST_DEBUG"  : "\"http://10.20.11.204:30160/\"",
            "API_HOST_RELEASE": "\"http://10.20.11.204:30160/\"",
            "codepath"        : "Num1"//代码路径参考Num1的, 因为这里Num1的 路径是都放在"Num1"下面
    ]
    /*------------------------- 测试后台配置--------------------------------------*/

    /*------------------------- 参考配置--------------------------------------*/
    /* android = [
         compileSdkVersion: 23,
         buildToolsVersion: "23.0.3",
         minSdkVersion    : 15,
         targetSdkVersion : 22,
         versionCode      : 1,
         versionName      : "1.0"
 ]

 dependencies = [
         "gson"               : "com.google.code.gson:gson:2.6.2",
         "eventbus"           : 'org.greenrobot:eventbus:3.0.0',
         "butterknife"        : 'com.jakewharton:butterknife:7.0.1',
         "support-design"     : 'com.android.support:design:24.1.1',
         "support-appcompatV7": 'com.android.support:appcompat-v7:24.1.1',
         "support-percent"    : 'com.android.support:percent:24.1.1',
         "support-multidex"   : 'com.android.support:multidex:1.0.1',
         "glide"              : 'com.github.bumptech.glide:glide:3.7.0',
         "support-v4"         : 'com.android.support:support-v4:24.1.1',
         "okhttp3"            : 'com.squareup.okhttp3:okhttp:3.3.1',
         "nineoldandroids"    : 'com.nineoldandroids:library:2.4.0'
 ]*/
}

2. 项目根目录下的build.gradle添加一句引用

//添加的
apply from: "config.gradle"

buildscript {
    ……
}

allprojects {
    ……
}

3. 现在所有moudle模块目录下的 build.gradle 就可以使用了

  请自行感受 rootProject.ext.url["API_HOST_DEBUG"], 这句 和 config.gradle中定义 的关系。

android {
    signingConfigs {
        main {
            File key = new File(rootProject.ext.signingConfigs["filePath"])
            keyAlias rootProject.ext.signingConfigs["keyAlias"]
            keyPassword rootProject.ext.signingConfigs["keyPassword"]
            storeFile file(key)
            storePassword rootProject.ext.signingConfigs["storePassword"]
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.main
            buildConfigField("String", "url_aaa", rootProject.ext.url["API_HOST_DEBUG"])
        }
    }
}

dependencies {
    implementation rootProject.ext.lib["gson"]
}

 

八、个人小结

1. 前面说到  参数配置 均可以在 defaultConfig 、buildtype 、productFlavors 三处定义,

再仔细看 这三者其实是同一层级的,如下。 那么当他们 均 定义一个参数时, 究竟哪个生效???

android{
    defaultConfig{
    } 
    buildtype{
    } 
    productFlavors{
    }
}

结果: 后者覆盖前面的,即现在这个顺序的话是,相同定义最后生效的是:productFlavors{} 定义的值

 

2.在gradle中自定义一个参数 比如 int a=0, 然后依次在不同的地方进行 赋不同的值,最后的结果是什么?

结果:永远是 最后一次赋值,并不会因为编译的版本不同而不同。

 

九、疑问

问题一:

背景:有客户A、客户B

          客户A正式后台 (URL1)和测试后台(URL2),且地址不一样

          客户B后台地址(URL3

 问题:应该怎么动态配置?  (先别急着下定论说很容易)

分析一:最优雅的情况是,app_A_release_URL1.apk 对应 URL1 正式 后台; app_A_debug_URL2.apk 对应 URL2测试后台

        肯定第一时间想到的是下面的这个, 但是很遗憾下面的模式无法设置B客户的 后台地址 URL3,

android{
   buildType{
        release{
            url = URL1
        }
        debug{
            url = URL2
        }
    }

   productFlavors {
        A {
           客户A
        }
        B {
           客户B
        }
    }
}

分析二:productFlavors() 使用两个维度的配置,然而效果还是和 分析一 一样,无效

分析三:如果额外加个客户A-Test , 在4个版本中只选择2个有用的版本。那么势必导致,测试版本和正式版本,无法相互安装/替换(对于交付第三方安装到系统/测试,均有不小的阻力)

分析四:在buildType()和productFlavors() 中加入变量判断编译版本, 然而很遗憾我没有找到相关变量,自己定义也失败(参见 七.第2小点)

主要矛盾:在普通状况下release和debug的版本必定是 相同的配置, release和debug是相对于代码区分,即一个调试开发版本,一个发布版本。

                  而如今,我想用配置一的调试版本,来和配置二的开发版本 做验证。

 

临时解决方法: (都能解决,但都不完美 不优雅)

1. 分析三,新建一个客户,添加配置,  

    缺点:生成apk的版本不一致,无法互相替换。如果是系统预置的应用,更是麻烦

 2. 代码里进行判断,如果是仅仅一个URL地址不同,那么可以考虑

     缺点:其他的客户B也必须配置Debug参数和Release参数,哪怕B客户只有一个参数;

                当客户A的配置参数不仅仅只有一个URL不一样的时候, 有十几个参数的话,那这个也很麻烦。

 

十、扩展  (先挖个坑在这吧,埋不埋的以后再说了)

  1. 组件化/插件化   参考 android架构设计之插件化、组件化
  2. 在build.gradle 里面可以手动配置 参数,来将某个module,作为lib还是app 来使用,即 这里可以通过配置来 单独使用某个moudle,方便多人模块开发/测试

 

---------------------------------------------------------------------------------------------------------------------------------------------------------------

~~~~~~~~~~~~~~~~~~~          终于写好了啊  ~   ~~

                        早就想写,也早就知道 很麻烦, 但没想到 一写就是整整1天半的时间。。。

                        感谢看完!  或  拉到了底部!

---------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值