# 能力模块开发规范

## 📋 核心原则

**每个能力模块必须完全独立，自己管理自己的所有配置和依赖，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`**

#### ✅ 正确做法

```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>
```

```xml
<!-- 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>
```

#### ❌ 错误做法

```xml
<!-- 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/` 资源**

#### ✅ 正确做法

```xml
<!-- 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>
```

```xml
<!-- capability-share/src/main/AndroidManifest.xml -->
<activity>
    <!-- 使用本模块的 @string/ 资源 -->
    <data android:scheme="@string/qq_app_id_scheme" />
</activity>
```

#### ❌ 错误做法

```xml
<!-- app/src/main/AndroidManifest.xml -->
<application>
    <!-- ❌ 不要在这里配置能力模块的 meta-data！ -->
    <meta-data android:name="JPUSH_APPKEY" ... />
</application>
```

```groovy
// app/build.gradle
// ❌ 不要在这里配置能力模块的参数！
manifestPlaceholders = [
    XIAOMI_APPID: "..."
]
```

---

## 📦 依赖管理规范

### 1. 依赖声明

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

#### ✅ 正确做法

```groovy
// 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'))
}
```

```groovy
// app/build.gradle
dependencies {
    // 只需要依赖能力模块，不需要重复声明能力模块的依赖
    implementation project(':capability-share')
    // ❌ 不要在这里重复声明微信 SDK！
}
```

#### ❌ 错误做法

```groovy
// 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` 中配置能力模块的参数**
   ```xml
   <!-- ❌ 禁止 -->
   <string name="jpush_app_key">...</string>
   <string name="share_umeng_app_key">...</string>
   ```

2. **禁止在 app 模块的 `AndroidManifest.xml` 中配置能力模块的 meta-data**
   ```xml
   <!-- ❌ 禁止 -->
   <meta-data android:name="JPUSH_APPKEY" ... />
   ```

3. **禁止在 app 模块的 `build.gradle` 中重复声明能力模块的依赖**
   ```groovy
   // ❌ 禁止
   implementation("com.tencent.mm.opensdk:wechat-sdk-android:6.8.24")
   ```

4. **禁止在 app 模块的 `build.gradle` 中使用 XML 解析读取配置**
   ```groovy
   // ❌ 禁止
   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

```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 中使用

```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 中声明依赖

```groovy
// 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 模块中引用

```groovy
// 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：

```kotlin
// 在能力模块的代码中
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）

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

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

#### ✅ 实现类使用 internal 或 private

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

#### ❌ 禁止直接暴露实现类

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

### 2. Factory 模式规范

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

#### ✅ 标准 Factory 结构

```kotlin
// 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()"
        )
    }
}
```

#### ✅ 使用示例

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

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

### 3. 配置类设计规范

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

```kotlin
// 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 解析

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

### 4. 响应模型规范

#### ✅ 统一的响应模型

```kotlin
// 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. 错误处理规范

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

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

#### ❌ 禁止抛出未处理的异常

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

---

## 📝 日志规范

### 1. 使用统一的日志接口

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

#### ✅ 正确做法

```kotlin
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

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

### 2. 日志级别规范

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

### 3. Tag 命名规范

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

---

## 🔨 编译配置规范

### 1. build.gradle 配置

#### ✅ 标准配置结构

```groovy
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')
}
```

#### ❌ 禁止事项

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

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

### 2. AndroidManifest.xml 配置

#### ✅ 标准结构

```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` 中添加规则**

```proguard
# 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` 中管理**

```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. 模块版本号

**能力模块不需要版本号（因为是内部模块）**

```groovy
// ❌ 不需要
version = "1.0.0"
```

---

## 🔍 代码审查检查清单

### 创建新能力模块时检查

- [ ] **模块结构**
  - [ ] 有 `strings.xml` 配置文件
  - [ ] 有 `AndroidManifest.xml`
  - [ ] 有 `build.gradle`（使用 `library` 插件）

- [ ] **接口设计**
  - [ ] 定义了清晰的 API 接口
  - [ ] 实现类使用 `internal` 或 `private`
  - [ ] 使用 Factory 模式提供单例

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

- [ ] **依赖管理**
  - [ ] 依赖在模块自己的 `build.gradle` 中
  - [ ] 需要暴露的依赖使用 `api`
  - [ ] app 模块不重复声明依赖

- [ ] **日志规范**
  - [ ] 使用 `ILog` 接口
  - [ ] 不使用 `android.util.Log`
  - [ ] Tag 命名规范

- [ ] **错误处理**
  - [ ] 所有异常都被捕获
  - [ ] 返回统一的 Response 模型
  - [ ] 不抛出未处理的异常

---

## 🚨 常见错误和解决方案

### 错误 1：运行时找不到类

**原因**：依赖使用了 `implementation` 而不是 `api`

**解决**：
```groovy
// 改为 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

**解决**：
```kotlin
// ✅ 正确
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年**

