|
|
@@ -0,0 +1,868 @@
|
|
|
+# 能力模块开发规范
|
|
|
+
|
|
|
+## 📋 核心原则
|
|
|
+
|
|
|
+**每个能力模块必须完全独立,自己管理自己的所有配置和依赖,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年**
|
|
|
+
|