# 小程序与移动应用通信

WMPF 提供了小程序与 WMPF Client 之间进行数据通信的通道（Invoke Channel）。帮助开发者在小程序与移动应用之间，以事件和接口调用的形式进行数据传递。

## 1. 小程序端

### 1.1 服务指令

我们可以通过触发第三方应用服务指令的形式，让 WMPF Client 端为我们实现一些特定的功能逻辑，调用完成之后，再将最后的结果返回到小程序端。

- [wmpf.Channel.invoke](api/mini_program/invoke)
- [wmpf.Channel.invokeSync](api/mini_program/invokeSync)

**示例代码（异步调用）**

```js
wmpf.Channel.invoke({
  command: 'test', // 指令为 'test' ，WMPF Client 端收到后执行特定的功能逻辑
  success: function(res) {
    // WMPF Client 端执行任务完成后，返回最终的结果到小程序端
    console.log(res.data)
  }
})
```

**示例代码（同步调用）**

```js
try {
  const res = wmpf.Channel.invokeSync({
    command: 'test'
  })
  console.log(res.data)
} catch (e) {}
```

### 1.2 监听事件

`wmpf` 还可以通过注册事件的方式，被动接收从 WMPF Client 端发送过来的消息。

- [wmpf.Channel.registerEvent](api/mini_program/registerEvent)
- [wmpf.Channel.unregisterEvent](api/mini_program/unregisterEvent)
- [wmpf.Channel.on](api/mini_program/on)
- [wmpf.Channel.off](api/mini_program/off)

**示例代码**

```js
// 首先注册一个 event 名为 'test' 的事件
wmpf.Channel.registerEvent({
  event: 'test',
  success(res) {
    console.log(res)
  }
})

const callback = (res) => {
  // WMPF Client 端在特定的时机触发 event 'test' 事件，向小程序端发送数据
  console.log(res.data)
}

// 监听已注册过的 'test' 事件
wmpf.Channel.on('test', callback)

// 取消监听已注册过的 'test' 事件
wmpf.Channel.off('test', callback)

// 取消注册 event 名为 'test' 的事件
wmpf.Channel.unregisterEvent({
  event: 'test',
  success(res) {
    console.log(res)
  }
})
```

## 2. WMPF Client 端

WMPF 在整个流程中的角色是透传小程序的入参和客户端的返回参数给小程序，故各字段名及含义需由小程序开发者和 wmpf-cli 开发者协定。WMPF 与 wmpf-cli 可以选择下列方式之一进行数据交互。

### 2.1 继承 WMPFClientProvider 类（推荐）

从 WMPF 2.2 版本开始，我们封装了 [WMPFClientProvider](api/client/WMPFClientProvider)，方便开发者更简单的响应小程序发送的服务指令和向小程序发送事件。

### 2.2 使用安卓 ContentProvider

#### 2.2.1 服务指令

WMPF 每次调用都会生成唯一的 \_\_invoke_id\_\_ 并插入到传给 wmpf-cli 的 ContentValues 中，作为调用唯一标识。

- [invoke](api/client/invoke)
- [invokeSync](api/client/invokeSync)

##### 异步调用

当小程序中调用 `wmpf.Channel.invoke` 时，Client 端需要使用 insert 操作完成异步调用。

**调用流程**

![流程图](https://res8.wxqcloud.qq.com.cn/wxdoc/c9575355-9373-4bd4-b0cb-42beb215ecb3.png)

- 通过 `insert(content://com.tencent.wmpf.cli.provider/invokeChannel)` 触达 wmpf-cli 。
- 通过 `insert(content://com.tencent.wmpf.comm.provider/callbackInvokeChannel)` 通知回调 。

**示例代码**

```kotlin
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
  when(sURIMatcher.match(p0)) {
    // 异步方法调用
    CODE_CALLBACK_INVOKE_CHANNEL -> {
      val invokeId = p1?.getAsString(InvokeChannelConstants.Key.INVOKE_ID)
      val command = p1?.getAsString(InvokeChannelConstants.Key.COMMAND)
      val sourceData = p1?.getAsString(InvokeChannelConstants.Key.DATA)

      Log.i(TAG, "invokeId: $invokeId, command: $command, sourceData: $sourceData")

      if (command.equals("test")) {
        // 实现特定的功能逻辑
        mHandler.post({
          val cv = ContentValues()
          cv.apply {
            put(InvokeChannelConstants.Key.INVOKE_ID, invokeId)
            put(InvokeChannelConstants.Key.COMMAND, command)
            put(InvokeChannelConstants.Key.DATA, "your data") // 返回执行结果给小程序端
          }
          try {
            context?.contentResolver?.insert(InvokeChannelConstants.ContentProvider.Cli2WMPF.URI_CALLBACK_INVOKE_CHANNEL, cv)
          } catch (e: Exception) {
            Log.e(TAG, "callback invoke channel error")
          }
        })
      }
    }
  }
  return null
}
```

##### 同步调用

当小程序中调用 `wmpf.Channel.invokeSync` 时，Client 端需要使用 call 操作完成同步调用。

**调用流程**

![流程图](https://res8.wxqcloud.qq.com.cn/wxdoc/b93ec4a6-2fb9-4528-935f-140a58bf5c88.png)

- 通过 `call(content://com.tencent.wmpf.cli.provider/invokeChannel)` 触达 wmpf-cli 。
- 通过 Bundle 直接返回结果。

**示例代码**

```kotlin
// 同步调用
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
  Log.i(TAG, "method: $method, arg: $arg, extras: $extras")
  // 实现特定的功能逻辑
  val bundle = Bundle()
  bundle.putString("data", "call success, method: $method, arg: $arg, extras: $extras")
  // 返回执行结果给小程序端
  return bundle
}
```

#### 2.2.2 事件监听

- [registerEvent](api/client/registerEvent)
- [unregisterEvent](api/client/unregisterEvent)

##### 注册事件与触发事件

当小程序中调用 `wmpf.Channel.registerEvent` 时，会通知到 Client 端。当小程序注册的事件触发时，客户端需要向小程序发送对应事件，小程序可以通过 `wmpf.Channel.on` 监听。

**调用流程**

![流程图](https://res8.wxqcloud.qq.com.cn/wxdoc/376a49e2-0015-4e90-9164-aac4a8f57304.png)

- 通过 `insert(content://com.tencent.wmpf.comm.provider/invokeChannelEvent)` 注册监听回调。
- 通过 `insert(content://com.tencent.wmpf.comm.provider/notifyInvokeChannelEvent)` 触发事件。

**示例代码**

```kotlin
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
  when(sURIMatcher.match(p0)) {
    // 回调事件注册
    CODE_NOTIFY_INVOKE_CHANNEL_EVENT -> {
      val eventId = p1?.getAsString(InvokeChannelConstants.Key.EVENT_ID)
      val event = p1?.getAsString(InvokeChannelConstants.Key.EVENT)
      Log.i(TAG, "register, eventId: $eventId, event: $event")
      mEventIdList.put(eventId, event)
    }
  }
  return null
}

// 在特定时机通知某个已经注册的 event ，并传入相关 data 到小程序端
private fun notifyEvent(counter: Int, eventId: String?, event: String?) {
  if (!mEventIdList.containsKey(eventId)) {
    return 
  }
  val cv = ContentValues()
  cv.apply {
    put(InvokeChannelConstants.Key.EVENT_ID, eventId)
    put(InvokeChannelConstants.Key.EVENT, event)
    put(InvokeChannelConstants.Key.DATA, "event$counter notify event success")
  }
  try {
    context.contentResolver.insert(InvokeChannelConstants.ContentProvider.Cli2WMPF.URI_NOTIFY_INVOKE_CHANNEL_EVENT, cv)
    Log.i(TAG, "send message success, content: event$counter success")
  } catch (e: Exception) {
    Log.e(TAG, "callback invoke channel error")
  }
}
```

##### 解除注册事件

当小程序中调用 `wmpf.Channel.unregisterEvent` 时，会通知到 Client 端，Client 应该停止发送对应事件。

**调用流程**

![流程图](https://res8.wxqcloud.qq.com.cn/wxdoc/84ef5360-adf2-4ffa-be24-516d6cff4a0b.png)

与 `registerEvent` 为反逻辑，通过 delete 操作通知 wmpf-cli 客户端，意义在于通知 wmpf-cli 客户端释放逻辑。

**示例代码**

```kotlin
// 取消注册回调事件
override fun delete(p0: Uri, p1: String?, p2: Array?): Int {
  if (p2?.size == 2) {
    val eventId = p2[0]
    val event = p2[1]
    mEventIdList.remove(eventId)
    Log.i(TAG, "unregister success, eventId: $eventId, event: $event")
  }
  return 0
}
```
