# 极光推送集成说明

## 目录结构

```
capability-push/src/
└── main/
    ├── AndroidManifest.xml          # Android 清单文件（权限声明）
    └── java/
        └── com/narutohuo/xindazhou/push/
            ├── api/                  # API 接口层
            │   └── PushService.kt    # 推送服务接口定义
            ├── factory/              # 工厂类层
            │   └── PushServiceFactory.kt  # 服务工厂（统一入口，单例获取）
            ├── impl/                 # 实现层
            │   └── PushServiceImpl.kt     # 推送服务具体实现（封装极光推送SDK）
            ├── receiver/             # 接收器层
            │   └── JPushReceiver.kt       # 推送消息接收器（完全封装在模块内部）
            ├── mapping/              # 页面映射层
            │   └── ActivityMapping.kt     # 静态页面映射配置（支持应用未启动时跳转）
            └── model/                # 数据模型层
                ├── PushConfig.kt          # 推送服务配置模型
                └── PushResponse.kt         # 推送相关的数据模型
                    ├── PushMessage        # 推送消息模型
                    └── PushResponse       # 推送响应模型
```

## 功能说明

### 1. api/ 层 - 接口定义
- **职责**：定义推送服务的公共接口，业务层只依赖接口
- **内容**：`PushService` 接口，包含初始化、别名/标签设置、消息监听、注册/注销等方法

### 2. factory/ 层 - 工厂类
- **职责**：提供统一的服务实例获取和初始化方式
- **内容**：`PushServiceFactory` 单例工厂
- **作用**：隐藏实现细节，统一入口，便于替换实现

### 3. impl/ 层 - 具体实现
- **职责**：实现 `PushService` 接口的具体逻辑
- **内容**：`PushServiceImpl` 单例实现类
- **功能**：封装极光推送 SDK 调用，处理推送消息接收，错误处理和日志记录

### 3.1 receiver/ 层 - 消息接收器
- **职责**：封装推送消息接收逻辑
- **内容**：`JPushReceiver` BroadcastReceiver
- **功能**：
  - 自动接收极光推送的消息（自定义消息、通知消息、通知点击）
  - 自动解析消息内容
  - **完全封装**：在模块的 `AndroidManifest.xml` 中自动注册，业务层无需处理
  - **自动跳转**：优先使用静态配置（`ActivityMapping`）自动处理通知点击跳转，支持应用未启动场景

### 3.2 mapping/ 层 - 页面映射配置
- **职责**：提供静态页面映射配置，支持应用未启动时的跳转
- **内容**：`ActivityMapping` 静态配置类
- **功能**：
  - **静态配置**：页面映射关系在编译时确定，不依赖运行时初始化
  - **应用未启动支持**：即使应用从未启动过，点击通知也能正常跳转
  - **向后兼容**：支持运行时动态配置（通过 `pageMappings` 参数）
  - **优先级**：静态配置优先，运行时配置作为补充

### 4. model/ 层 - 数据模型
- **职责**：定义推送相关的数据模型
- **内容**：
  - `PushConfig` - 推送服务配置模型
  - `PushMessage` - 推送消息模型（标题、内容、扩展数据等）
  - `PushResponse` - 推送响应模型（成功/失败、错误信息等）

## 架构设计

- **接口隔离**：业务层只依赖接口，不依赖实现
- **ARouter 依赖注入**：通过 ARouter 实现模块间解耦，base-core 定义接口，能力层实现接口
- **工厂模式**：统一入口，隐藏创建逻辑（内部使用 ARouter 获取服务）
- **单例模式**：全局共享一个服务实例
- **分层架构**：base-core (接口) → capability-push (实现) → ARouter (注册) → Factory (获取)
- **完全封装**：跳转逻辑封装在模块内部，外部只需配置页面映射规则
- **静态配置优先**：使用静态配置（`ActivityMapping`）确保应用未启动时也能正常跳转

### ARouter 架构说明

- **base-core** 模块定义 `IPushService` 接口（位于 `com.narutohuo.xindazhou.core.push`）
- **capability-push** 模块实现 `IPushService` 接口，并通过 `@Route(path = "/push/service")` 注册到 ARouter
- **Factory** 内部使用 `ARouter.getInstance().navigation(IPushService::class.java)` 获取服务实例
- **业务层** 通过 Factory 获取服务，无需直接依赖实现模块

## 已完成的工作

✅ **SDK 依赖已添加**
- `jpush-5.9.0.aar` - 极光推送SDK（本地 AAR）
- `jcore-5.2.0.aar` - 极光推送核心库（本地 AAR）
- `cn.jiguang.sdk.plugin:xiaomi:5.9.0` - 小米通道
- `cn.jiguang.sdk.plugin:huawei:5.9.0` - 华为通道
- `cn.jiguang.sdk.plugin:oppo:5.9.0` - OPPO通道
- `cn.jiguang.sdk.plugin:vivo:5.9.0` - vivo通道
- `cn.jiguang.sdk.plugin:meizu:5.9.0` - 魅族通道

✅ **代码实现已完成**
- `PushServiceImpl` 已实现极光推送 SDK 调用，并实现 `IPushService` 接口
- `PushServiceImpl` 已通过 ARouter 注册（`@Route(path = "/push/service")`）
- `PushServiceFactory` 已创建，内部使用 ARouter 获取服务
- `IPushService` 接口已在 `base-core` 模块定义
- `PushConfig` 配置模型已创建
- `JPushReceiver` 消息接收器已封装
- **`ActivityMapping` 静态页面映射已实现**：支持应用未启动时的跳转
- **跳转逻辑已封装**：自动跳转功能已实现，优先使用静态配置
- **ARouter 集成完成**：模块间解耦，通过 ARouter 进行依赖注入

✅ **配置已完成**
- 所有配置都在 `capability-push` 模块中管理
- `AndroidManifest.xml` 已在 `capability-push/src/main/AndroidManifest.xml` 中配置
- `strings.xml` 已在 `capability-push/src/main/res/values/strings.xml` 中配置

## 需要您配置的内容

### 1. 在极光推送官网注册应用

1. 访问 [极光推送官网](https://www.jiguang.cn/)
2. 注册账号并创建应用
3. 获取 **AppKey** 和 **AppSecret**

### 2. 配置厂商通道参数（可选，但推荐）

在各厂商开放平台申请参数，并在 `capability-push/src/main/res/values/strings.xml` 中配置。

**详细步骤请参考**：[厂商通道接入说明.md](./厂商通道接入说明.md)

### 3. 配置资源文件

**所有配置都在 `capability-push/src/main/res/values/strings.xml` 中管理**

编辑 `capability-push/src/main/res/values/strings.xml`：

```xml
<resources>
    <!-- 极光推送基础配置（必填） -->
    <string name="jpush_app_key">您的极光推送 AppKey</string>
    <string name="push_jpush_channel">developer-default</string>
    
    <!-- 厂商通道配置（可选，但推荐配置以提升推送到达率） -->
    <!-- 详细配置请参考 厂商通道接入说明.md -->
</resources>
```

### 3.5 配置静态页面映射（推荐，支持应用未启动场景）

**方式1：使用静态配置（推荐）**

在 `capability-push/src/main/java/com/narutohuo/xindazhou/push/mapping/ActivityMapping.kt` 中配置：

```kotlin
package com.narutohuo.xindazhou.push.mapping

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

/**
 * 静态页面映射配置
 * 
 * 优势：
 * - 支持应用未启动时的跳转
 * - 不依赖 Application.onCreate() 的执行顺序
 * - 更可靠，避免初始化时序问题
 */
object ActivityMapping {
    /**
     * 静态页面映射配置
     * 页面名称 -> Activity 完整类名
     */
    private val pageToActivityMap = mapOf(
        "detail" to "com.narutohuo.xindazhou.ui.DetailActivity",
        "home" to "com.narutohuo.xindazhou.ui.MainActivity",
        "order" to "com.narutohuo.xindazhou.ui.OrderActivity",
        // 添加更多页面映射...
    )
    
    /**
     * 根据页面名称获取 Activity Class
     * 
     * @param page 页面名称（从推送的 extras["page"] 获取）
     * @return Activity Class，如果未找到返回 null
     */
    fun getActivityClass(page: String): Class<*>? {
        val className = pageToActivityMap[page] ?: return null
        return try {
            Class.forName(className)
        } catch (e: ClassNotFoundException) {
            ILog.e("ActivityMapping", "找不到 Activity: $className", e)
            null
        }
    }
}
```

**方式2：使用运行时配置（向后兼容）**

在 `Application.onCreate()` 中通过 `pageMappings` 参数配置（见下方"4. 在 Application 中初始化"）。

**推荐使用方式1（静态配置）**，因为：
- ✅ 支持应用未启动时的跳转
- ✅ 不依赖初始化顺序
- ✅ 更可靠

### 4. 在 Application 中初始化（统一入口）

在您的 `XinDaZhouApplication` 类中初始化推送服务：

```kotlin
// app/src/main/java/com/narutohuo/xindazhou/XinDaZhouApplication.kt
package com.narutohuo.xindazhou

import android.app.Application
import com.narutohuo.xindazhou.push.factory.PushServiceFactory

class XinDaZhouApplication : Application() {
    
    override fun onCreate() {
        super.onCreate()
        
        // ========== 初始化推送服务（从资源文件读取配置）==========
        // 方式1：只使用静态配置（推荐，已在 ActivityMapping 中配置）
        PushServiceFactory.init(
            context = this
        )
        
        // 方式2：同时配置运行时映射（可选，作为静态配置的补充）
        PushServiceFactory.init(
            context = this,
            // 页面映射规则（用于自动跳转，作为静态配置的补充）
            // 注意：如果已在 ActivityMapping 中配置，这里可以不传
            pageMappings = mapOf(
                "detail" to DetailActivity::class.java,  // 详情页
                "home" to MainActivity::class.java,       // 首页
            )
        )
    }
}
```

**配置说明**：
- ✅ **静态配置优先**：`ActivityMapping` 中的配置优先级最高，即使应用未启动也能使用
- ✅ **运行时配置补充**：`pageMappings` 参数作为补充，用于动态配置或覆盖静态配置
- ✅ **向后兼容**：如果只使用 `pageMappings`，也能正常工作（但应用未启动时可能失败）

**优势**：
- ✅ **最简单**：只需一行代码，配置都在资源文件和静态配置类中
- ✅ **统一入口**：通过 `PushServiceFactory.init()` 统一初始化
- ✅ **跳转逻辑封装**：跳转逻辑完全封装在模块内部，外部只需要配置页面映射规则
- ✅ **应用未启动支持**：静态配置确保即使应用从未启动过，点击通知也能正常跳转
- ✅ **容错处理**：初始化失败不会影响应用启动，只记录日志
- ✅ **自动识别设备**：极光推送 SDK 自动识别设备类型并选择对应厂商通道
- ✅ 业务层不直接依赖极光推送API
- ✅ 如果更换SDK，只需修改实现层，业务层无需改动

## 核心功能说明

### 1. 通知展示（系统通知栏）

✅ **不需要我们处理，系统自动显示**

- 极光推送 SDK 会自动在系统通知栏显示通知
- 系统会自动处理通知的展示、图标、声音等

### 2. 通知点击跳转（已封装）

✅ **跳转逻辑已完全封装在模块内部！**

**工作原理**：
1. 用户点击通知栏通知
2. 模块内部自动解析 `extras["page"]`
3. **优先使用静态配置**：从 `ActivityMapping` 获取 Activity Class（支持应用未启动场景）
4. **备选方案**：如果静态配置未找到，使用运行时配置的 `pageMappings`
5. 根据映射关系自动跳转到对应 Activity
6. 所有 `extras` 参数自动传递给目标 Activity

**优势**：
- ✅ **应用未启动支持**：即使应用从未启动过，点击通知也能正常跳转
- ✅ **不依赖初始化顺序**：静态配置在类加载时即可用，不依赖 `Application.onCreate()`
- ✅ **更可靠**：避免因初始化时序问题导致的跳转失败
- ✅ **向后兼容**：仍支持运行时动态配置 `pageMappings`

**服务器推送格式**：
```json
{
  "notification": {
    "title": "新订单",
    "alert": "您有一个新订单待处理"
  },
  "extras": {
    "page": "detail",  // 必须匹配 ActivityMapping 或 pageMappings 中的 key
    "id": "12345"      // 会自动传递给目标 Activity
  }
}
```

### 3. 厂商通道（自动识别）

✅ **极光推送 SDK 会自动识别设备类型，无需手动处理！**

- ✅ **自动识别**：SDK 会自动检测设备品牌（小米、华为、OPPO、vivo、魅族等）
- ✅ **自动选择通道**：根据设备类型自动选择对应的厂商通道
- ✅ **自动降级**：如果设备不是厂商手机，自动降级到极光通道
- ✅ **无需手动处理**：不需要判断手机品牌，不需要手动选择通道

## 使用示例

### 初始化（在 Application 中）

```kotlin
// 方式1：只使用静态配置（推荐，支持应用未启动场景）
// 页面映射已在 ActivityMapping 中配置
PushServiceFactory.init(
    context = this
)

// 方式2：同时配置运行时映射（可选，作为静态配置的补充）
PushServiceFactory.init(
    context = this,
    messageListener = { message ->
        // 处理自定义消息（透传消息）
        Timber.d("收到推送: ${message.title} - ${message.content}")
    },
    notificationClickListener = { message ->
        // 处理通知点击事件（可选，用于统计、埋点等）
        Timber.d("用户点击通知，已自动跳转")
    },
    pageMappings = mapOf(
        "detail" to DetailActivity::class.java,
        // 注意：如果已在 ActivityMapping 中配置，这里可以不传
    )
)
```

### 配置静态页面映射（在 ActivityMapping.kt 中）

```kotlin
// capability-push/src/main/java/com/narutohuo/xindazhou/push/mapping/ActivityMapping.kt
package com.narutohuo.xindazhou.push.mapping

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

object ActivityMapping {
    private val pageToActivityMap = mapOf(
        "detail" to "com.narutohuo.xindazhou.ui.DetailActivity",
        "home" to "com.narutohuo.xindazhou.ui.MainActivity",
        "order" to "com.narutohuo.xindazhou.ui.OrderActivity",
        // 添加更多页面映射...
    )
    
    fun getActivityClass(page: String): Class<*>? {
        val className = pageToActivityMap[page] ?: return null
        return try {
            Class.forName(className)
        } catch (e: ClassNotFoundException) {
            ILog.e("ActivityMapping", "找不到 Activity: $className", e)
            null
        }
    }
}
```

### 后续使用（可选，按需调用）

```kotlin
val pushService = PushServiceFactory.getInstance()
pushService.setAlias("user123")  // 设置别名（可选）
pushService.setTags(listOf("vip", "premium"))  // 设置标签（可选）
val registrationId = pushService.getRegistrationId()  // 获取注册ID（可选）
val isEnabled = pushService.isPushEnabled()  // 检查推送状态（可选）
```

## 推送消息类型说明

1. **自定义消息（透传消息）**：`messageListener`
   - 不会显示在系统通知栏
   - 需要在应用内自己处理并显示
   - 适用于需要应用内自定义显示的推送

2. **通知消息（系统通知栏）**：
   - **展示**：由极光推送 SDK 和系统自动处理，自动显示在通知栏
   - **点击**：优先使用静态配置（`ActivityMapping`）自动跳转，支持应用未启动场景（已封装）
   - 适用于需要系统通知栏显示的推送

## 配置位置说明

**⚠️ 重要：所有配置都在 `capability-push` 模块中管理，`app` 模块不需要任何配置！**

- ✅ **配置位置**：`capability-push/src/main/res/values/strings.xml`
- ✅ **清单文件**：`capability-push/src/main/AndroidManifest.xml`
- ✅ **app 模块**：只需要在 `Application` 中调用 `PushServiceFactory.init()` 即可

**❌ 禁止在 app 模块中配置：**
- ❌ **禁止**在 `app/src/main/AndroidManifest.xml` 中配置极光推送的 `meta-data`
- ❌ **禁止**在 `app/src/main/res/values/strings.xml` 中配置极光推送参数
- ❌ **禁止**在 `app/build.gradle` 中配置极光推送相关参数
- ✅ **所有配置都在 `capability-push` 模块中！**

## 常见问题

### Q1: 应用从未启动过，点击通知能正常跳转吗？

**A: 可以！** 使用静态配置（`ActivityMapping`）后，即使应用从未启动过，点击通知也能正常跳转。

**原理**：
- `BroadcastReceiver` 是独立组件，系统会在应用未启动时启动应用进程
- 静态配置在类加载时即可用，不依赖 `Application.onCreate()` 的执行
- 跳转逻辑优先使用静态配置，确保可靠性

### Q2: 静态配置和运行时配置（pageMappings）有什么区别？

**A:**
- **静态配置**（`ActivityMapping`）：
  - ✅ 支持应用未启动时的跳转
  - ✅ 不依赖初始化顺序
  - ✅ 更可靠
  - ❌ 需要修改代码并重新编译
  
- **运行时配置**（`pageMappings`）：
  - ✅ 可以动态配置
  - ✅ 不需要重新编译
  - ❌ 依赖 `Application.onCreate()` 的执行
  - ❌ 应用未启动时可能失败

**推荐**：优先使用静态配置，运行时配置作为补充。

### Q3: 如何添加新的页面映射？

**A:** 在 `ActivityMapping.kt` 的 `pageToActivityMap` 中添加新的映射关系：

```kotlin
private val pageToActivityMap = mapOf(
    "detail" to "com.narutohuo.xindazhou.ui.DetailActivity",
    "newPage" to "com.narutohuo.xindazhou.ui.NewPageActivity",  // 新增
)
```

注意：Activity 类名必须是完整的包名路径。

### Q4: 如果静态配置和运行时配置都配置了同一个页面，会使用哪个？

**A:** 静态配置（`ActivityMapping`）优先级更高。如果静态配置中找到了对应的 Activity，会优先使用静态配置；如果静态配置中未找到，才会使用运行时配置（`pageMappings`）。

## 参考文档

- [极光推送官方文档](https://docs.jiguang.cn/jpush/client/android/android_guide/)
- [厂商通道 SDK 集成指南](https://docs.jiguang.cn/jpush/client/Android/android_3rd_guide)
- [厂商通道接入说明.md](./厂商通道接入说明.md) - 本项目厂商通道配置说明
