Android 页面代码粒度化管理进阶

距离上次发布的有关代码目录结构的文章 《掘金之路(八)页面代码粒度化管理》 已经快5年了,这些时间里我开发的项目一般都是基于这种目录结构,当然在此基础上也有所改进,这就是写这篇文章的原因了。

如果问我经历过的最复杂的项目是什么,我想应该是一个扫读笔项目,这个项目的复杂不在于功能上的难易,而是在于代码结构上的复杂。想象一下,这个项目有n多个客户,每个客户可能同时有几款不同型号的 Android 定制设备,有小方块屏的(类似手表屏幕大小),有长屏的(比如128*96等几个尺寸)、有宽屏的,Android 系统覆盖 Android 4.4 - Android 11,当然还会涉及到定制系统的许多调用差异(比如有些系统集成了扫描 OCR 能力,可以对外提供调用,有些没有,需要自己集成第三方 OCR 去实现),最后还有客户的部分定制化需求(比如界面或功能)。

问题来了,如果你负责开发维护这个项目,你怎么来搭建一个架构,要求满足以下3点:

1、代码目录结构要清晰,随着项目功能增长也能保持清晰,方便以后开发和维护;

2、能够最大化的复用相同代码,避免维护多份相同代码;

3、能够灵活的处理差异化需求代码

你可以先思考,不用着急,思考完再往下看。

思考中...

下面先贴上我的答案:PinDemoProject,当然你可能会有更好的答案,欢迎在评论里一起分享。

代码结构的配置逻辑在根目录的 build.gradle.kts,这里就不贴上全部配置代码了。接下来只简单介绍 flavor 的配置项信息及对应的效果,不会说到各个配置的具体实现,如果对实现有兴趣的话还是需要去看 build.gradle.kts 里的实现代码。

flavor 的配置项信息如下:

kotlin 复制代码
/**
 * flavor 参数配置:
 * applicationId:应用ID,没有配置则使用默认值
 * screenOrientation:屏幕方向
 * supportFocus:是否支持焦点(有物理方向按键或确认键等功能键则表示支持)
 * modules:指定要引入的业务模块以及差异化页面(pins-diff)
 *    - pinsVersion:引入 pins-${pinsVersion} 目录下的所有页面
 *    - diffPins:指定需要引入 pins-diff 目录下的差异化页面
 * versionCode:版本号
 * versionName:版本名称
 * abiFilters:指定保留的ABI类型
 * systemApiLevel:设备的系统版本
 * signingConfig:签名配置
 */
private val flavorConfigs = mapOf(
    "flavorA" to mapOf(
        "screenOrientation" to "landscape",
        "supportFocus" to false,
        "modules" to mapOf(
            "m_core" to emptyMap<String, Any>(),
            "m_user" to mapOf(
                //user 模块使用 v1 版本:
                "pinsVersion" to "v1",
                "diffPins" to emptyArray<String>()
            ),
        ),
        "versionCode" to 1,
        "versionName" to "1.0.0",
        "systemApiLevel" to 21,
    ),
    "flavorB" to mapOf(
        "applicationId" to "cbfg.pin.flavorB",
        "screenOrientation" to "landscape",
        "supportFocus" to false,
        "modules" to mapOf(
            "m_core" to emptyMap<String, Any>(),
            "m_user" to mapOf(
                //user 模块使用 v2 版本:
                "pinsVersion" to "v2",
                //引入 pins-diff 中的 p_vip 页:
                "diffPins" to arrayOf("p_vip")
            ),
        ),
        "versionCode" to 1,
        "versionName" to "1.0.0",
        "systemApiLevel" to 19,
        "signingConfig" to { appExtension: com.android.build.gradle.AppExtension ->
            appExtension.signingConfigs.create("xxx") {
                keyAlias = "xxx"
                keyPassword = "xxx"
                storeFile = file("xxx.jks")
                storePassword = "xxx"
            }
        }
    ),
    "flavorC" to mapOf(
        "applicationId" to "cbfg.pin.flavorC",
        "screenOrientation" to "portrait",
        "supportFocus" to true,
        "modules" to mapOf(
            "m_core" to emptyMap<String, Any>(),
            "m_user" to mapOf(
                //user 模块使用 v3 版本:
                "pinsVersion" to "v3",
                //引入 pins-diff 中的 p_vip 页:
                "diffPins" to arrayOf("p_vip")
            ),
        ),
        "versionCode" to 1,
        "versionName" to "1.0.0",
        "systemApiLevel" to 19,
    )
)

结合注释,上面的配置应该不难理解,每个 flavor 的配置都是独立的,关键的一个配置是 modules 配置,可以根据 flavor 需要去引入所需的模块及版本,同时可以额外去引入差异化目录中的页面(比如 flavorB 和 flavorC 的 p_vip 页是一致的,但是 flavorA 的是专属的,那么就把 flavorB 和 flavorC 的 p_vip 页提取到了差异化目录 pins-diff 中,flavorB 和 flavorC 额外去引入即可)

接下来再看下项目中 user 模块代码目录截图:

其中:

1、pins 目录是放置公用页面代码的,放置在这个目录内的页面代码默认会被引入;

2、pins-diff 是放置差异化页面代码的,需要在 flavor -> modules ->对应模块 -> diffPins 配置中加入才会被引入;

3、pins-v1、pins-v2、pins-v3 对应的是这个模块的3个不同版本,模拟不同 flavor 需要的不同版本,需要在 flavor -> modules -> 对应模块 -> pinsVersion 配置中指定才会引入对应的版本(pinsVersion 的命名可以是随意的,可以根据自己的需要去起名称,比如可以是 pins-flavorA,只要在 flavor 的配置中指定 pinsVersion 为 flavorA 即可);

从截图中还可以看到 pins 目录下有个 p_user_core 目录,pins-v3 目录下有个 p_user_ui 目录(其实 pins-v1、pins-v2 下也有 p_user_ui 这个目录),这里模拟的是3个版本中,p_user 页 ui 部分可能不同,但是核心逻辑是相同的,所以抽取了核心逻辑到 pins 目录下(对应 pins/p_user_core),而 ui 部分则保留在各自版本中(对应 pins-v1、pins-v2、pins-v3 目录里的 p_user_ui),这里只是为了说明配置能够灵活的复用公共代码并支持差异化。

介绍完毕,详细配置见:build.gradle.kts

相关推荐
阿巴斯甜19 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker20 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952720 小时前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android