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

转载请声明:本文来自 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
  manifestPlaceholders buildConfigField
在 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里直接使用:

                                                                                                                

运行结果:

manifestPlaceholders 代码中使用

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

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

在代码里使用:

    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天半的时间。。。

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

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

 

 

更多相关文章

  1. mybatisplus的坑 insert标签insert into select无参数问题的解决
  2. Python技巧匿名函数、回调函数和高阶函数
  3. python list.sort()根据多个关键字排序的方法实现
  4. android“设置”里的版本号
  5. Android(安卓)version and Linux Kernel version
  6. android中文api(89)——ViewManager
  7. opengrok setup on ubuntu for android source code browser
  8. Android调用天气预报的WebService简单例子
  9. Android开发——Android搜索框架(二)

随机推荐

  1. Android(安卓)学习笔记(封贴,放弃安卓)
  2. android实现3D效果翻页
  3. Android(安卓)ListView使用详解
  4. Android应用程序Zipalign化 -- 如何让And
  5. Android常用技术-热修复解析
  6. Android数据回传---装备选择
  7. 一起来学习Android自定义控件1
  8. Android(安卓)—— Handler进阶(未完持续)
  9. 震惊! 阿里的程序员也不过如此,竟被一个
  10. android 通知