---------------------------------------------------------------------------------------------------------------------------------------------------------------
转载请声明:本文来自 https://blog.csdn.net/shijianduan1/article/details/83410267
---------------------------------------------------------------------------------------------------------------------------------------------------------------
项目 使用 多渠道 已经蛮久了, 最近又有新需求过来了,所以多渠道这块也重新看了下。。。。
然而新需求很坑爹(或许是我能力不够),反正 事实就是 浪费了我两天时间, 看了N多博客,也没找到对应的解决方案
---------------------------------------------------------------------------------------------------------------------------------------------------------------
本片 文章的Demo(不涉及界面,只是个框架搭建) 已经上传GitHub https://github.com/sjindong/Testduoqudao
一、总览
-
buildType
- productFlavors
- sourceSets
- 占位符 manifestPlaceholders 和 动态参数buildConfigField
- 编译变体版本(修改名称,自定义编译)
- 多模块的动态参数配置
- 个人小结
- 疑问
- 拓展
以下两片参考文章 提供了详细的 gradle中的相关参数接口
参考一:Android build.gradle之buildTypes {}
参考二:Android Gradle Plugin中 ApplicationVariant文件内的可用属性
二、buildType 编译类型
- 可以重定义 一切 defaultConfig{} 里的参数 和 android{} 下的直接参数 (后者未完全验证,有误请指正)
- 可以定义 定位符(manifestPlaceholders) ,动态参数(buildConfigField)、签名、混淆等等配置,
- 可以自定义增加 如下面(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都可上面一样)
- 可以重定义 一切 defaultConfig{} 里的参数 和 android{} 下的直接参数 (后者未完全验证,有误请指正)
- 可以定义 定位符(manifestPlaceholders) ,
- 动态参数(buildConfigField)、
- 签名、
- 混淆等等配置,
- 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 资源文件配置
- 如果不设置,则是android默认的,
- main 代表主干代码 (下面的是已经对主干默认路径修改过的)
- Num1是 分支代码,Num1和上面的渠道号定义保持一致。
- 不同的渠道号可以指向相同的路径,即他们引用同一个文件
- java文件不会被替换,(即,"src/main/java/a.java" 和 "src/Num1/java/a.java" 是同一路径文件,编译会报错告诉你已经定义了a.java文件)
- aidl文件和jni文件 未验证,还不清楚
- 使用:用函数调用:定义非main的两个目录下,相同结构路径,相同文件名,相同函数名,(方法内容不同)
用接口调用:定义非main的两个目录下,相同结构路径,实现相同接口 (具体类不同)
- 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()
- 占位符 manifestPlaceholders :就是在前面代码里面 直接预留一个位置,然后等编译的时候 把数据放进去,
- buildConfigField :是在 BuildConfig.java 这个自动生成的文件里面去定义一个参数
- 两个均可以在 defaultConfig 、buildtype 、productFlavors 三处定义
- 均可在代码里使用,
- 占位符 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里直接使用:
<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不一样的时候, 有十几个参数的话,那这个也很麻烦。
十、扩展 (先挖个坑在这吧,埋不埋的以后再说了)
- 组件化/插件化 参考 android架构设计之插件化、组件化
- 在build.gradle 里面可以手动配置 参数,来将某个module,作为lib还是app 来使用,即 这里可以通过配置来 单独使用某个moudle,方便多人模块开发/测试
---------------------------------------------------------------------------------------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~ 终于写好了啊 ~ ~~
早就想写,也早就知道 很麻烦, 但没想到 一写就是整整1天半的时间。。。
感谢看完! 或 拉到了底部!
---------------------------------------------------------------------------------------------------------------------------------------------------------------