• android 多产品项目搭建与变体的使用


            由于公司产品的复杂性,连带着项目也跟着复杂起来,这时候也接触到了android的build 变体的使用。在这里记录一下这个过程,最初查看关于这一块的资料是在android的官网上看得,地址如下:配置 build 变体  |  Android 开发者  |  Android Developers

            首先,我们公司是有多种产品的,每一种产品的类型有相同的也有不同的,而且种类很多,每一种产品下面还会继续细分不同的特性,例如有产品1、产品2、产品3....产品n,然后产品1可能会存在特性1、特性2......特性N,每个产品之间可能有相同部分,但是不相同的地方比较多,所以每一个产品可以作为一个单独的工程来实现,相同部分就抽成一个库进行依赖到项目中来,而每个产品下的特性则可以使用变体的方式来区分,当然不是说所有的特性都使用变体,如果特性增多,这个工程也会变得非常庞大的,还是要斟酌着使用。

            针对上面所说的类型搭建项目,如下图所示:

      接着,在公司的项目中,我主要使用到了以下几方面来划分产品与特性:

    1. build配置

            当创建新模块时,Android Studio 会自动为您创建“debug”build 类型和“release”build 类型。虽然“debug”build 类型没有出现在 build 配置文件中,但 Android Studio 会使用 debuggable true 配置它,在项目app目录下的build.gradle中进行配置,配置的例子如下:

    1. android {
    2. defaultConfig {
    3. }
    4. signingConfigs {
    5. release {
    6. storeFile file(appStoreFile)
    7. storePassword 'appStorePassword'
    8. keyAlias 'appKeyAlias'
    9. keyPassword 'appKeyPassword'
    10. }
    11. debug {
    12. storeFile file(appStoreFile)
    13. storePassword 'appStorePassword'
    14. keyAlias 'appKeyAlias'
    15. keyPassword 'appKeyPassword'
    16. }
    17. }
    18. buildTypes {
    19. release {
    20. minifyEnabled true
    21. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    22. }
    23. debug {
    24. // 配置debug版本的签名
    25. signingConfig signingConfigs.debug
    26. // 是否开启代码混淆,默认false
    27. minifyEnabled false
    28. // 是否应该生成可调试的apk
    29. debuggable true
    30. // 混淆规则配置文件
    31. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    32. // 自定义buildType
    33. buildConfigField 'String', 'BASE_URL', '"http://api-debug.**/"'
    34. }
    35. /**
    36. * The `initWith` property allows you to copy configurations from other build types,
    37. * then configure only the settings you want to change. This one copies the debug build
    38. * type, and then changes the manifest placeholder and application ID.
    39. */
    40. staging {
    41. initWith debug
    42. applicationIdSuffix ".debugStaging"
    43. }
    44. }
    45. }

    一般buildType中,我们会配置项目所需要使用到的环境变量,如测试环境地址和正式环境地址等,这个环境变量可以在代码中使用的,并且在编译打包的时候可以确定对应的值, 可以通过buildConfigField 关键字来进行配置,编译过后,我们可以通过BuildConfig.BASE_URL方式在代码中调用,如下图所示:

     获取到的值,就是在build.gradle中对应build type下配置的值。

    配置完成后,sync同步一下,在gradle模块中可以看到配置的变化,当然如果点击Gradle出现Task list not build,没有Task出现,如下图 

     

     这是由于新版本的android studio默认设置了不同步task,我们可以点击Task list not build,去掉不同步的勾选,如下图:

     同步一下工程即可。我们可以在Gradle的Task-other看到这个变化,如下图:

    也可以通过build variants切换当前工程的build type,切换到release下,那么buildConfig中的值也会跟着变化,如下图所示:

    2.配置产品变种

            如果我们的产品有多种特性类型,产品总体是一样,只有部分不一样,我们可以通过配置变体要区分特性与类型,例如,音箱项目分为蓝牙音箱、wifi音箱、蓝牙wifi音箱(只是举个例子),基本主体是一样的,只有连接方式不同,并且需要分别打不同的包,我们可以在app目录下的build.gradle配置type维度的变体,如下配置:

    1. flavorDimensions "type"
    2. productFlavors {
    3. bluetooth {
    4. // Assigns this product flavor to the "version" flavor dimension.
    5. // If you are using only one dimension, this property is optional,
    6. // and the plugin automatically assigns all the module's flavors to
    7. // that dimension.
    8. dimension "type"
    9. // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.bluetooth
    10. // 不需要修改包名直接不配置即可
    11. applicationIdSuffix ".bluetooth"
    12. versionNameSuffix "-bluetooth"
    13. }
    14. wifi {
    15. dimension "type"
    16. // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.wifi
    17. applicationIdSuffix ".wifi"
    18. versionNameSuffix "-wifi"
    19. }
    20. bluetoothandwifi{
    21. dimension "type"
    22. // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.bluetoothandwifi
    23. applicationIdSuffix ".bluetoothandwifi"
    24. versionNameSuffix "-bluetoothandwifi"
    25. }
    26. }

    同步之后,可以在Gradle看到变体已经构建出来了:

     同样也可以通过build variants切换和查看当前工程的变体,如下:

     如果特性仅有打包的包名不一样,这样配置之后打出来的包就会带上对应变体设置的后缀名,但是如果代码也不一样的话,这就需要我们创建对应的变体模块来实现不一样的地方。

            首先,我们可以右键项目,new一个java folder,选择需要创建的变体模块,如下:

    创建之后,模块下面是什么都没有的,我们还需要创建目录,由于我当前项目是处于blueTooth变体模块,如下:

    所以可以直接项目右键选择package,输入包名即可,如下: 

     如果当前工程不是处于想要创建变体的模式,则创建目录的时候,是没有package的选择的,只有Directory,如下:

             注意:main模块是所有变体都能访问到,变体模块下的代码是只有当前工程处于该变体模式下才能访问。

    针对变体,一般我们有可能需要由以下的配置:

    • 每个变体app的启动入口不一样

            每一个变体创建相同路径的MainActivity类,删除main目录下的MainActivity,统一通过main目录下的清单文件作为入口,这里创建demo和full两个变体,以这两个作为例子

    app的build.gradle配置

    1. flavorDimensions "version"
    2. productFlavors {
    3. demo {
    4. // Assigns this product flavor to the "version" flavor dimension.
    5. // If you are using only one dimension, this property is optional,
    6. // and the plugin automatically assigns all the module's flavors to
    7. // that dimension.
    8. dimension "version"
    9. // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.bluetooth
    10. // 不需要修改包名直接不配置即可
    11. applicationIdSuffix ".demo"
    12. versionNameSuffix "-demo"
    13. }
    14. full {
    15. dimension "version"
    16. // 这里配置apk的包名,配置之后的包名是com.zqy.app_project_one.wifi
    17. applicationIdSuffix ".full"
    18. versionNameSuffix "-full"
    19. }
    20. }
    1. 将main模块下的MainActivity与activity_main.xml删除
    2. 变体模块下新增MainActivity与activity_main.xml

     

    1. 在main模块的清单文件设置程序的入口
    1. <application
    2. android:allowBackup="true"
    3. android:icon="@mipmap/ic_launcher"
    4. android:label="@string/app_name"
    5. android:roundIcon="@mipmap/ic_launcher_round"
    6. android:supportsRtl="true"
    7. android:theme="@style/Theme.Project">
    8. <activity
    9. android:name=".MainActivity"
    10. android:exported="true">
    11. <intent-filter>
    12. <action android:name="android.intent.action.MAIN" />
    13. <category android:name="android.intent.category.LAUNCHER" />
    14. </intent-filter>
    15. </activity>
    16. </application>

     通过build variants切换当前工程的build type,直接运行项目的代码,就可以调用对应变体下的MainActivity,当前是什么变体模式,就会使用变体目录下相同路径的类。

    • 更改默认源代码集配置

            如果您的源代码未按照 Gradle 要求的默认源代码集文件结构进行组织(如上文关于创建源代码集的部分中所述),您可以使用 sourceSets 代码块更改 Gradle 为源代码集的每个组件收集文件的位置。sourceSets 代码块必须位于 android 代码块中。您无需改变源代码文件的位置,只需向 Gradle 提供相对于模块级 build.gradle 文件的路径,Gradle 应该会在该路径下找到每个源代码集组件的文件 。

    1. sourceSets{
    2. main{
    3. java.srcDirs = ['src/main/java']
    4. // If you list multiple directories, Gradle uses all of them to collect
    5. // sources. Because Gradle gives these directories equal priority, if
    6. // you define the same resource in more than one directory, you get an
    7. // error when merging resources. The default directory is 'src/main/res'.
    8. res.srcDirs = ['src/main/res']
    9. // Note: You should avoid specifying a directory which is a parent to one
    10. // or more other directories you specify. For example, avoid the following:
    11. // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
    12. // You should specify either only the root 'other/res1' directory, or only the
    13. // nested 'other/res1/layouts' and 'other/res1/strings' directories.
    14. // For each source set, you can specify only one Android manifest.
    15. // By default, Android Studio creates a manifest for your main source
    16. // set in the src/main/ directory.
    17. manifest.srcFile 'src/main/AndroidManifest.xml'
    18. }
    19. full{
    20. java.srcDirs = ['src/full/java']
    21. // If you list multiple directories, Gradle uses all of them to collect
    22. // sources. Because Gradle gives these directories equal priority, if
    23. // you define the same resource in more than one directory, you get an
    24. // error when merging resources. The default directory is 'src/main/res'.
    25. res.srcDirs = ['src/full/res']
    26. // Note: You should avoid specifying a directory which is a parent to one
    27. // or more other directories you specify. For example, avoid the following:
    28. // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
    29. // You should specify either only the root 'other/res1' directory, or only the
    30. // nested 'other/res1/layouts' and 'other/res1/strings' directories.
    31. // For each source set, you can specify only one Android manifest.
    32. // By default, Android Studio creates a manifest for your main source
    33. // set in the src/main/ directory.
    34. manifest.srcFile 'src/full/AndroidManifest.xml'
    35. }
    36. demo{
    37. java.srcDirs = ['src/demo/java']
    38. // If you list multiple directories, Gradle uses all of them to collect
    39. // sources. Because Gradle gives these directories equal priority, if
    40. // you define the same resource in more than one directory, you get an
    41. // error when merging resources. The default directory is 'src/main/res'.
    42. res.srcDirs = ['src/demo/res']
    43. // Note: You should avoid specifying a directory which is a parent to one
    44. // or more other directories you specify. For example, avoid the following:
    45. // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
    46. // You should specify either only the root 'other/res1' directory, or only the
    47. // nested 'other/res1/layouts' and 'other/res1/strings' directories.
    48. // For each source set, you can specify only one Android manifest.
    49. // By default, Android Studio creates a manifest for your main source
    50. // set in the src/main/ directory.
    51. manifest.srcFile 'src/demo/AndroidManifest.xml'
    52. }
    53. }
    • 库的build type不随主工程的build type变化而变化

            有时候,我们项目主工程可能涉及到build type的新增,而主工程依赖了库工程,但是库工程并不需要新增这个build type类型,例如:主工程的build type如下:

    1. buildTypes {
    2. release {
    3. minifyEnabled false
    4. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    5. }
    6. debug {
    7. minifyEnabled false
    8. debuggable true
    9. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    10. }
    11. staging {
    12. initWith debug
    13. applicationIdSuffix ".debugStaging"
    14. }

    依赖的库工程的build type配置如下:

    1. buildTypes {
    2. release {
    3. minifyEnabled false
    4. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    5. }
    6. }

     这时候如果当前工程为debug或release类型编译工程是不会有问题的,这是因为不管是工程还是库,android studio在创建这些工程和库的时候都会默认创建debug或release类型,但是一旦当前工程切换到staging 类型,此时编译运行工程,就会出现以下问题:

    * What went wrong:
    Could not determine the dependencies of task ':app-project-two:compileStagingJavaWithJavac'.
    > Could not resolve all task dependencies for configuration ':app-project-two:stagingCompileClasspath'.
       > Could not resolve project :variants-res.
         Required by:
             project :app-project-two
          > No matching variant of project :variants-res was found. The consumer was configured to find an API of a component, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' but:
              - Variant 'debugApiElements' capability Project:variants-res:unspecified declares an API of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
                  - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging'
                  - Other compatible attribute:
                      - Doesn't say anything about its target Java environment (preferred optimized for Android)
              - Variant 'debugRuntimeElements' capability Project:variants-res:unspecified declares a runtime of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
                  - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging'
                  - Other compatible attribute:
                      - Doesn't say anything about its target Java environment (preferred optimized for Android)
              - Variant 'releaseApiElements' capability Project:variants-res:unspecified declares an API of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
                  - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging'
                  - Other compatible attribute:
                      - Doesn't say anything about its target Java environment (preferred optimized for Android)
              - Variant 'releaseRuntimeElements' capability Project:variants-res:unspecified declares a runtime of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
                  - Incompatible because this component declares a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release' and the consumer needed a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'staging'
                  - Other compatible attribute:
                      - Doesn't say anything about its target Java environment (preferred optimized for Android)
     

     这是由于依赖库和主工程的build type不一致的问题导致的,遇到这种问题,我们可以使用

    matchingFallbacks 关键字来设置库的build type优先级,这也是android官方给出的解决方案,如下:

    我们只需要在app的build.gradle的build type配置一下 matchingFallbacks即可,如下:

    1. buildTypes {
    2. release {
    3. .....
    4. }
    5. debug {
    6. .....
    7. }
    8. staging {
    9. ......
    10. // 关键的一行
    11. matchingFallbacks = ['debug','release']
    12. }
    13. }

  • 相关阅读:
    JPG文件头-笔记
    基于ssm+vue+elementui的的ktv点歌管理系统
    使用VBA快速完成不规则数据整理
    Android拦截HOME键
    机器学习之Sigmoid函数
    LeetCode_等差数列_中等_413.等差数列划分
    Meta Llama 3 里面装饰器
    MySQL之表的约束
    常用的排序算法(选择,冒泡,插入)
    数据库中的存储过程、游标、触发器与常用的内置函数
  • 原文地址:https://blog.csdn.net/qiuyin2015/article/details/125357149