# H5+ App接入指南

[H5+ App](https://ask.dcloud.net.cn/article/89)，HTML5 Plus移动App，简称5+App，是一种基于HTML、JS、CSS编写的运行于手机端的App

本文档概述如何在H5+ App中接入版署审核 SDK

## 一、接入准备

### 1、开发环境搭建
- nodejs下载、安装、配置，本教程使用的版本是`v16.16.0`
- Android Studio 与 Android SDK 等
- HBuilderX下载安装，配置游戏项目
- 初始化 Android App项目，配置包名、应用名、签名等

<span style="color:red">**本文档使用的HBuilderX版本为 3.7.3，非此版本注意兼容性问题**</font>  
<span style="color:red">**本文档中使用的Android 编译工具及依赖都是最新，游戏不必如此**</font>

### 2、应用注册
#### H5+App注册
在 [DCloud开发者中心](https://dev.dcloud.net.cn/) 注册自己的应用，提交
- 开发者信息
- 应用信息：应用名、包名、apk的签名sha1等

应用注册后将得到`app id`, `app key`这在接下来的打包中需要

#### 4399 开发者平台注册
在 [4399 开发者平台](https://open.4399.cn/console/) 注册游戏，成功后将得到 `game key` ——游戏在4399平台的标识

## 二、HBuilderX 与 Android 配置打包

### Android 原生工程配置
建议大致了解官方教程： [Android 原生工程配置](https://nativesupport.dcloud.net.cn/AppDocs/usesdk/android)  ，再对比[H5+ App 4399版署审核 SDK 示例](https://sdkftp.4399doc.com/external/operateBZ/2.4.4/operateBZ-h5plus-2.4.4+40.zip)（以下称“示例”） 实际操作  
*注意: 示例的`app/libs`需要单独下载（见下文），`app/src/main/assets`无游戏资源*

下载好示例解压后，看到的主要内容有
```bash
app  # Android 应用模块，最终打包成APK
operateBZ-phx # 插件模块，接入SDK并将按H5+协议封装为插件，为app模块依赖
h5plusSample # H5+ app 示例，游戏项目
```

按以下步骤尝试初次打包：
- 配置游戏项目的`h5plusSample/manifest.json`，主要是`app id`
- 通过 HBuilderX 菜单栏，"发行" -> "原始app-本地打"包 -> "生成本地打包 app 资源"，打包后将生成`unpackage`目录
- 创建 Android Sutdio项目，假设主模块为`app`，将`h5plusSample/unpackage`拷贝到`app/src/main/assets`目录
- 在`app/src/main/assets/data/dcloud_control.xml`配置`app id`
- 在`app/src/mainAndroidManifest.xml`中添加`app key`

完成以上步骤后，可以直接运行项目，通过真机调试，若游戏正常游戏即可进行后续步骤

## 三、封装与使用 SDK

### 1、4399 版署审核 SDK 封装
H5+App 在 Android 平台接入第三方SDK，需要先按照协议将其封装为js接口，示例中为`operateBZ-phx`模块
- 引入 H5+ app 插件需要的依赖，下载[App离线SDK下载](https://nativesupport.dcloud.net.cn/AppDocs/download/android)并按[指引](https://nativesupport.dcloud.net.cn/AppDocs/usesdk/android.html#%E6%96%B9%E5%BC%8F%E4%BA%8C-%E5%AF%BC%E5%85%A5%E5%B7%A5%E7%A8%8B)拷贝文件到`app/libs`目录
- 创建与配置`operateBZ-phx`插件模块，此模块对插件的依赖方式为*编译期*依赖
- 在插件模块，按照《Java接入指南》引入SDK
- 参考`cn.m4399.operateBZ.phx.OperateCenterPHX`类的封装方式，注意其中的参数传递方式

<span style='color:red'>本示例工程没有打包`app/libs`下的依赖，这部分内容单独上传到了[h5+ app 3.7.3目录](http://sdkftp.4399doc.com/external/operateBZ/phx/3.7.3/)</span>

### 2、注册与使用原生插件
- 在应用模块中注册插件，参考`app/src/main/assets/dcloud_properties.xml`中内容

`properties.features`节点下新增
```xml
<!-- 4399 OperateBZ PHX plugin -->
<feature name="OperatePHX" value="cn.m4399.operateBZ.phx.OperateCenterPHX" />
```

- 在游戏项目`manifest.json`注册插件

`permissions`节点下新镇
```json
"OperatePHX" : {
     "description" : "4399 版署审核 SDK Phaser HBuilderX封装"
 }
```

- 在HTML的`window`对象中注入插件实例，参考`src/sdk/operateBZPHX.js`中内容

### 3、js接口说明
在游戏中调用js接口前，先简要说明 android 接口到 js 接口的封装方式，以`iniSDK`接口为例

- 插件类继承`io.dcloud.common.DHInterface.StandardFeature`
- 方法原型符合规范，`initSDK`从游戏或js中接收的参数值在`optsions`中，且第一个参数为回调对象
```java
public void initSDK(IWebview iWebview, JSONArray options);
```

- `initSDK`方法实现中，先提取 js 参数，然后正常调用 android 接口
- android 接口调用后，其结果封装成 json 字符串，通过 callback 传递给 js
- js 的 callback 中，先将字符串格式化为对象，再进行后续处理，如`operateBZPHX.js`中的`wrapJsCallback`所示


### 4、调用插件方法
*提示：`mockOpeBZPHX.js`是模拟对象，可以在浏览器中模拟数据，直接测试游戏调用*  
*提示：示例游戏中的调用，只是示意在此特定游戏环境下的调用时机，部分逻辑并不完善*

初始化/读取存档, `src/App.vue`
```js
let localUser; // 读取游戏自身登录状态，可选操作
// 初始化
createOperatePHX().then(opePHX => {
   opePHX.initSDK('40027', 0, true,
       ({event, isLogin, user}) => {
         switch (event) {
           case 'init_finished': // 初始化完成
             if (!localUser.username) {
               if (isLogin) {
                 let newUser = {id: user.uid, username: user.name,};
                 this.addUser(newUser);
                 opePHX.readArchive(({data: role}) => {  // 读取存档
                   if (role?.length)
                     newUser.role = role;
                   // todo: 读取存档，更新角色信息
                 })
               }
             }
             break;
           case 'account_logout': // 账号登出
             this.addUser({})
             break;
           case 'account_switch': // 账号切换
             break;
           default:
         }
         this.$router.push({name: "userInfo"})
       });
 });
```

登录/读取存档、登出、充值, `src/UserInfo.vue`
```js
// 登录
login() {
  this.invokeOpePHX(opePHX => {
    opePHX.isLogin(({isLogin}) => {
      if (isLogin) {
        // SDK 初始化后已经有登录状态，不需要在登录，可以直接获取用户信息
      } else {
        opePHX.login(({success, resultCode, user}) => {
          if (success) {
            this.user = {id: user.uid, username: user.name};
            opePHX.readArchive(({data: role}) => { // 读取存档
              // todo: 处理登录结果
              // todo: 读取存档，更新角色信息
              this.addUser(this.user)
            })
          } else {
            // 登录失败
          }
        })
      }
    })
  });
},

// 登出
logout() {
  this.user = {id: '', username: ''}
  this.invokeOpePHX(opePHX => {
    opePHX.logout();
    // 清除游戏内登录状态
  });
},

// 充值
recharge() {
  this.invokeOpePHX(opePHX => {
    let money = 10;
    let commodity = `${money * 10} 金币`
    let mark = `cid_xx|uid_xx|${Date.now()}`;
    opePHX.recharge(money, mark, commodity, false,
        ({success, resultCode, message}) => {
          // todo: 游戏充值，处理充值结果
          if (success) {
            // 充值成功，游戏根据服务端回调发放物品
          } else
            // 充值失败
        });
  })
},

invokeOpePHX(callback) {
  createOperatePHX()
      .then(opePHX => {
        callback(opePHX);
      })
      .catch(e => {
        this.$message({content: `错误: ${e.toString()}`})
      })
}
```
