能力模块开发规范.md 21 KB

能力模块开发规范

📋 核心原则

每个能力模块必须完全独立,自己管理自己的所有配置和依赖,app 模块只负责组装,不负责管理能力模块的内部事务。


🎯 原始诉求

问题背景

  • 之前的问题:所有配置都在 app 模块的 strings.xml
  • 之前的问题:所有依赖都在 app 模块的 build.gradle 中重复声明
  • 之前的问题:app 模块需要解析 XML 来读取配置
  • 之前的问题:能力模块的配置散落在各处,难以维护

解决方案

  • 现在:每个能力模块有自己的 strings.xml
  • 现在:每个能力模块管理自己的依赖(使用 api 传递)
  • 现在:不需要 XML 解析,直接使用 @string/ 引用
  • 现在:能力模块完全独立,app 模块只负责组装

📁 模块结构规范

标准目录结构

capability-xxx/
├── build.gradle                    # 模块依赖配置
├── src/main/
│   ├── AndroidManifest.xml        # 模块的 AndroidManifest(包含 meta-data、Activity 等)
│   ├── res/
│   │   └── values/
│   │       └── strings.xml         # 模块自己的配置(必须!)
│   └── java/...                    # 模块代码
└── libs/                           # 模块自己的 SDK 文件(如果有)

🔧 配置管理规范

1. strings.xml 配置

每个能力模块必须有自己的 strings.xml

✅ 正确做法

<!-- capability-push/src/main/res/values/strings.xml -->
<resources>
    <!-- 极光推送配置 -->
    <string name="jpush_app_key">your_jpush_appkey_here</string>
    <string name="push_jpush_channel">developer-default</string>
</resources>
<!-- capability-share/src/main/res/values/strings.xml -->
<resources>
    <!-- 友盟分享配置 -->
    <string name="share_umeng_app_key">your_umeng_appkey_here</string>
    <string name="share_wechat_app_id">your_wechat_appid_here</string>
    <string name="qq_app_id_scheme">tencentyour_qq_appid_here</string>
</resources>

❌ 错误做法

<!-- app/src/main/res/values/strings.xml -->
<resources>
    <!-- ❌ 不要在这里放能力模块的配置! -->
    <string name="jpush_app_key">...</string>
    <string name="share_umeng_app_key">...</string>
</resources>

2. AndroidManifest.xml 配置

每个能力模块在自己的 AndroidManifest.xml 中配置,使用自己的 @string/ 资源

✅ 正确做法

<!-- capability-push/src/main/AndroidManifest.xml -->
<application>
    <!-- 使用本模块的 @string/ 资源 -->
    <meta-data
        android:name="JPUSH_APPKEY"
        android:value="@string/jpush_app_key"
        tools:replace="android:value" />
    
    <!-- 厂商通道配置(覆盖厂商 SDK 的占位符) -->
    <meta-data
        android:name="XIAOMI_APPID"
        android:value="@string/mi_app_id"
        tools:replace="android:value" />
</application>
<!-- capability-share/src/main/AndroidManifest.xml -->
<activity>
    <!-- 使用本模块的 @string/ 资源 -->
    <data android:scheme="@string/qq_app_id_scheme" />
</activity>

❌ 错误做法

<!-- app/src/main/AndroidManifest.xml -->
<application>
    <!-- ❌ 不要在这里配置能力模块的 meta-data! -->
    <meta-data android:name="JPUSH_APPKEY" ... />
</application>
// app/build.gradle
// ❌ 不要在这里配置能力模块的参数!
manifestPlaceholders = [
    XIAOMI_APPID: "..."
]

📦 依赖管理规范

1. 依赖声明

每个能力模块在自己的 build.gradle 中声明依赖

✅ 正确做法

// capability-share/build.gradle
dependencies {
    // 使用 api 让依赖传递到 app 模块
    api("com.tencent.mm.opensdk:wechat-sdk-android:6.8.24")
    api(files('libs/umeng-share-wechat-full-7.3.7.jar'))
}
// app/build.gradle
dependencies {
    // 只需要依赖能力模块,不需要重复声明能力模块的依赖
    implementation project(':capability-share')
    // ❌ 不要在这里重复声明微信 SDK!
}

❌ 错误做法

// app/build.gradle
dependencies {
    implementation project(':capability-share')
    // ❌ 不要重复声明能力模块的依赖!
    implementation("com.tencent.mm.opensdk:wechat-sdk-android:6.8.24")
}

2. 依赖传递规则

依赖类型 是否传递到 app 模块 使用场景
api ✅ 是 需要暴露给 app 模块使用的依赖(如 SDK)
implementation ❌ 否 模块内部使用的依赖

规则:如果 app 模块需要使用某个依赖,能力模块必须使用 api 声明


🚫 禁止事项

❌ 禁止在 app 模块中做的事情

  1. 禁止在 app 模块的 strings.xml 中配置能力模块的参数

    <!-- ❌ 禁止 -->
    <string name="jpush_app_key">...</string>
    <string name="share_umeng_app_key">...</string>
    
  2. 禁止在 app 模块的 AndroidManifest.xml 中配置能力模块的 meta-data

    <!-- ❌ 禁止 -->
    <meta-data android:name="JPUSH_APPKEY" ... />
    
  3. 禁止在 app 模块的 build.gradle 中重复声明能力模块的依赖

    // ❌ 禁止
    implementation("com.tencent.mm.opensdk:wechat-sdk-android:6.8.24")
    
  4. 禁止在 app 模块的 build.gradle 中使用 XML 解析读取配置

    // ❌ 禁止
    def xml = new XmlParser().parse(resFile)
    manifestPlaceholders = [...]
    
  5. 禁止在 app 模块中管理能力模块的内部逻辑


✅ 正确做法检查清单

创建新能力模块时,请按以下清单检查:

配置管理

  • capability-xxx/src/main/res/values/strings.xml 中创建配置
  • capability-xxx/src/main/AndroidManifest.xml 中使用 @string/ 引用配置
  • 不在 app 模块的 strings.xml 中添加能力模块的配置
  • 不在 app 模块的 AndroidManifest.xml 中配置能力模块的 meta-data

依赖管理

  • capability-xxx/build.gradle 中声明所有依赖
  • 需要暴露给 app 的依赖使用 api
  • 模块内部依赖使用 implementation
  • 不在 app 模块的 build.gradle 中重复声明能力模块的依赖

代码管理

  • 能力模块的代码完全封装在模块内部
  • 通过 Factory 模式暴露接口给 app 模块
  • app 模块只调用能力模块的公开接口

📝 示例:创建新能力模块

步骤 1:创建模块结构

capability-new/
├── build.gradle
├── src/main/
│   ├── AndroidManifest.xml
│   ├── res/values/strings.xml
│   └── java/...
└── libs/(如果需要)

步骤 2:创建 strings.xml

<!-- capability-new/src/main/res/values/strings.xml -->
<resources>
    <string name="new_module_config_key">your_config_value_here</string>
</resources>

步骤 3:在 AndroidManifest.xml 中使用

<!-- capability-new/src/main/AndroidManifest.xml -->
<application>
    <meta-data
        android:name="NEW_MODULE_KEY"
        android:value="@string/new_module_config_key" />
</application>

步骤 4:在 build.gradle 中声明依赖

// capability-new/build.gradle
dependencies {
    // 需要暴露给 app 的依赖使用 api
    api("com.example:sdk:1.0.0")
    
    // 模块内部依赖使用 implementation
    implementation("com.example:internal-lib:1.0.0")
}

步骤 5:在 app 模块中引用

// app/build.gradle
dependencies {
    // 只需要依赖能力模块,不需要重复声明能力模块的依赖
    implementation project(':capability-new')
}

🔍 常见问题

Q1: 为什么能力模块的依赖要用 api 而不是 implementation

A: 因为 app 模块在运行时需要这些依赖(如微信 SDK),如果使用 implementation,依赖不会传递到 app 模块,会导致运行时找不到类。

Q2: 能力模块的 strings.xml 中的资源,app 模块能访问吗?

A: 可以。Android 构建系统会合并所有模块的资源,app 模块可以通过 context.resources.getIdentifier() 访问能力模块的资源。

Q3: 如果能力模块需要读取配置,应该怎么做?

A: 在能力模块的代码中使用 Android 标准的资源 API:

// 在能力模块的代码中
val resources = context.resources
val resId = resources.getIdentifier("config_key", "string", context.packageName)
val value = resources.getString(resId)

不要使用 XML 解析!

Q4: app 模块的 strings.xml 应该放什么?

A: 只放 app 模块自己的配置,如:

  • app_name
  • app 模块特有的配置

不要放能力模块的配置!


📚 参考

当前项目中的正确示例

  1. capability-push 模块

    • ✅ 配置:capability-push/src/main/res/values/strings.xml
    • ✅ AndroidManifest:capability-push/src/main/AndroidManifest.xml
    • ✅ 依赖:capability-push/build.gradle
  2. capability-share 模块

    • ✅ 配置:capability-share/src/main/res/values/strings.xml
    • ✅ AndroidManifest:capability-share/src/main/AndroidManifest.xml
    • ✅ 依赖:capability-share/build.gradle(使用 api 传递依赖)

🎯 总结

记住一句话:每个能力模块都是独立的,自己管理自己的所有事情,app 模块只负责组装,不负责管理!

  • ✅ 配置 → 能力模块的 strings.xml
  • ✅ AndroidManifest → 能力模块的 AndroidManifest.xml
  • ✅ 依赖 → 能力模块的 build.gradle(使用 api 传递)
  • ✅ 代码 → 能力模块的 java/ 目录

app 模块只做一件事:implementation project(':capability-xxx')



💻 代码开发规范

1. 接口设计规范

✅ 必须定义接口(API)

每个能力模块必须定义清晰的接口,实现类封装在内部:

// capability-xxx/src/main/java/.../api/XxxService.kt
interface XxxService {
    fun initialize(context: Context, config: XxxConfig)
    fun doSomething(): XxxResponse
    // ...
}

✅ 实现类使用 internal 或 private

// capability-xxx/src/main/java/.../impl/XxxServiceImpl.kt
internal class XxxServiceImpl : XxxService {
    // 实现细节
}

❌ 禁止直接暴露实现类

// ❌ 禁止
class XxxServiceImpl : XxxService { ... }  // 不要用 public

2. Factory 模式规范

每个能力模块必须使用 Factory 模式提供单例服务

✅ 标准 Factory 结构

// capability-xxx/src/main/java/.../factory/XxxServiceFactory.kt
object XxxServiceFactory {
    @Volatile
    private var instance: XxxService? = null
    
    /**
     * 初始化服务(全局单例)
     * @param context 应用上下文
     * @param config 配置(可选,默认从资源文件读取)
     */
    @JvmStatic
    fun init(context: Context, config: XxxConfig? = null) {
        if (instance == null) {
            synchronized(this) {
                if (instance == null) {
                    val finalConfig = config ?: XxxConfig.fromResources(context)
                    instance = XxxServiceImpl().apply {
                        initialize(context, finalConfig)
                    }
                }
            }
        }
    }
    
    /**
     * 获取服务实例(必须在 init 之后调用)
     */
    @JvmStatic
    fun getInstance(): XxxService {
        return instance ?: throw IllegalStateException(
            "XxxService 未初始化,请先调用 XxxServiceFactory.init()"
        )
    }
}

✅ 使用示例

// 在 Application 中初始化
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        XxxServiceFactory.init(applicationContext)
    }
}

// 在业务代码中使用
val service = XxxServiceFactory.getInstance()
service.doSomething()

3. 配置类设计规范

✅ 配置类必须支持从资源文件读取

// capability-xxx/src/main/java/.../model/XxxConfig.kt
data class XxxConfig(
    val appKey: String,
    val channel: String = "developer-default"
) {
    companion object {
        /**
         * 从资源文件读取配置
         * 资源文件:capability-xxx/src/main/res/values/strings.xml
         */
        @JvmStatic
        fun fromResources(context: Context): XxxConfig {
            val resources = context.resources
            val packageName = context.packageName
            
            fun getString(name: String): String? {
                val resId = resources.getIdentifier(name, "string", packageName)
                return if (resId != 0) resources.getString(resId) else null
            }
            
            val appKey = getString("xxx_app_key") 
                ?: throw IllegalStateException("未配置 xxx_app_key")
            val channel = getString("xxx_channel") ?: "developer-default"
            
            return XxxConfig(appKey = appKey, channel = channel)
        }
    }
}

❌ 禁止使用 XML 解析

// ❌ 禁止在代码中解析 XML
val xml = XmlParser().parse(file)

4. 响应模型规范

✅ 统一的响应模型

// capability-xxx/src/main/java/.../model/XxxResponse.kt
data class XxxResponse(
    val success: Boolean,
    val data: Any? = null,
    val errorMessage: String? = null,
    val timestamp: Long = System.currentTimeMillis()
)

5. 错误处理规范

✅ 必须处理异常,返回统一的响应

override fun doSomething(): XxxResponse {
    return try {
        // 业务逻辑
        XxxResponse(success = true, data = result)
    } catch (e: Exception) {
        ILog.e(tag, "操作失败", e)
        XxxResponse(
            success = false,
            errorMessage = e.message ?: "未知错误"
        )
    }
}

❌ 禁止抛出未处理的异常

// ❌ 禁止
override fun doSomething() {
    throw Exception("错误")  // 必须捕获并返回 Response
}

📝 日志规范

1. 使用统一的日志接口

所有能力模块必须使用 base-core 模块的 ILog 接口

✅ 正确做法

import com.narutohuo.xindazhou.core.log.ILog

class XxxServiceImpl {
    private val tag = "XxxService"
    
    fun doSomething() {
        ILog.d(tag, "开始执行操作")
        try {
            // ...
            ILog.d(tag, "操作成功")
        } catch (e: Exception) {
            ILog.e(tag, "操作失败", e)
        }
    }
}

❌ 禁止直接使用 Android Log

// ❌ 禁止
import android.util.Log
Log.d("Tag", "message")

2. 日志级别规范

级别 使用场景 示例
ILog.d() 调试信息、流程跟踪 "开始初始化"、"收到回调"
ILog.i() 重要信息 "服务初始化成功"
ILog.w() 警告信息 "配置缺失,使用默认值"
ILog.e() 错误信息(必须带异常) "初始化失败", exception

3. Tag 命名规范

// ✅ 使用类名或模块名
private val tag = "XxxService"
private val tag = "XxxServiceImpl"
private val tag = "XxxReceiver"

🔨 编译配置规范

1. build.gradle 配置

✅ 标准配置结构

plugins {
    alias(libs.plugins.kotlin.android)
    id("com.android.library")  // 能力模块必须是 library
}

android {
    namespace = "com.narutohuo.xindazhou.xxx"  // 模块自己的 namespace
    compileSdk = 36

    defaultConfig {
        minSdk = 26
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    
    kotlinOptions {
        jvmTarget = "17"
    }
}

repositories {
    flatDir {
        dirs 'libs'  // 本地 SDK 文件
    }
    mavenCentral()
    google()
}

dependencies {
    // 1. 依赖 base-core(所有能力模块都依赖)
    implementation(project(":base-core"))
    
    // 2. AndroidX 基础依赖
    implementation(libs.androidx.core.ktx)
    
    // 3. 模块特定的依赖
    // 需要暴露给 app 的依赖使用 api
    api("com.example:sdk:1.0.0")
    // 模块内部依赖使用 implementation
    implementation("com.example:internal-lib:1.0.0")
    
    // 4. 本地 SDK 文件
    implementation(files('libs/xxx-sdk.jar'))
    implementation(name: 'xxx-sdk', ext: 'aar')
}

❌ 禁止事项

// ❌ 禁止使用 application 插件
plugins {
    id("com.android.application")  // 能力模块必须是 library
}

// ❌ 禁止在能力模块中配置 applicationId
defaultConfig {
    applicationId "..."  // 只有 app 模块才有
}

2. AndroidManifest.xml 配置

✅ 标准结构

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    
    <!-- 模块需要的权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    
    <application>
        <!-- 模块的 Service、Receiver、Activity 等 -->
        <!-- 使用本模块的 @string/ 资源 -->
        <meta-data
            android:name="XXX_KEY"
            android:value="@string/xxx_config_key" />
    </application>
</manifest>

3. ProGuard 规则

如果模块使用了第三方 SDK,必须在模块的 proguard-rules.pro 中添加规则

# capability-xxx/proguard-rules.pro
-keep class com.example.sdk.** { *; }
-dontwarn com.example.sdk.**

🧪 测试规范

1. 单元测试

每个能力模块应该有自己的单元测试

capability-xxx/
└── src/test/
    └── java/.../XxxServiceTest.kt

2. 集成测试

在 app 模块中进行集成测试

app/
└── src/androidTest/
    └── java/.../XxxServiceIntegrationTest.kt

📦 版本管理规范

1. SDK 版本管理

所有 SDK 版本统一在 gradle/libs.versions.toml 中管理

[versions]
androidx-core = "1.12.0"
retrofit = "2.9.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" }

2. 模块版本号

能力模块不需要版本号(因为是内部模块)

// ❌ 不需要
version = "1.0.0"

🔍 代码审查检查清单

创建新能力模块时检查

  • [ ] 模块结构

    • strings.xml 配置文件
    • AndroidManifest.xml
    • build.gradle(使用 library 插件)
  • [ ] 接口设计

    • 定义了清晰的 API 接口
    • 实现类使用 internalprivate
    • 使用 Factory 模式提供单例
  • [ ] 配置管理

    • 配置在模块自己的 strings.xml
    • AndroidManifest 使用 @string/ 引用
    • 配置类支持从资源文件读取
  • [ ] 依赖管理

    • 依赖在模块自己的 build.gradle
    • 需要暴露的依赖使用 api
    • app 模块不重复声明依赖
  • [ ] 日志规范

    • 使用 ILog 接口
    • 不使用 android.util.Log
    • Tag 命名规范
  • [ ] 错误处理

    • 所有异常都被捕获
    • 返回统一的 Response 模型
    • 不抛出未处理的异常

🚨 常见错误和解决方案

错误 1:运行时找不到类

原因:依赖使用了 implementation 而不是 api

解决

// 改为 api
api("com.example:sdk:1.0.0")

错误 2:找不到资源

原因:在 app 模块的 strings.xml 中配置了能力模块的资源

解决:移到能力模块自己的 strings.xml

错误 3:编译错误:unable to resolve class

原因:依赖没有正确传递

解决

  1. 检查能力模块的依赖是否使用 api
  2. 检查 app 模块是否正确依赖了能力模块

错误 4:配置读取失败

原因:使用了 XML 解析而不是 Android 资源 API

解决

// ✅ 正确
val resId = resources.getIdentifier("key", "string", packageName)
val value = resources.getString(resId)

// ❌ 错误
val xml = XmlParser().parse(file)

📚 参考示例

当前项目中的标准实现

  1. capability-push 模块

    • ✅ Factory: PushServiceFactory
    • ✅ API: PushService
    • ✅ Config: PushConfig.fromResources()
    • ✅ 配置: capability-push/src/main/res/values/strings.xml
  2. capability-share 模块

    • ✅ Factory: ShareServiceFactory
    • ✅ API: ShareService
    • ✅ Config: ShareConfig.fromResources()
    • ✅ 配置: capability-share/src/main/res/values/strings.xml

🎯 总结

核心原则(再次强调)

  1. 独立性:每个能力模块完全独立,不依赖 app 模块
  2. 封装性:实现细节封装在模块内部,只暴露接口
  3. 统一性:使用统一的 Factory 模式、日志接口、响应模型
  4. 规范性:遵循统一的代码规范和目录结构

开发流程

  1. 创建模块目录结构
  2. 定义 API 接口
  3. 实现服务类(internal)
  4. 创建 Factory(单例)
  5. 创建配置类(支持从资源读取)
  6. 创建 strings.xml 配置文件
  7. 配置 AndroidManifest.xml
  8. 配置 build.gradle(依赖使用 api
  9. 在 app 模块中依赖:implementation project(':capability-xxx')
  10. 在 Application 中初始化:XxxServiceFactory.init(context)

最后更新:2024年