简化Cocos和Native交互利器详解

目录
  • 背景
  • ABCBinding 的结构设计
  • 具体实现
    • 通过 tag 找到 Native 方法
    • 约束 Native 方法
    • 优雅的回调
  • 其他 feature
    • 抹平系统差异
    • 无需关心线程切换
    • 支持超时
  • 彩蛋:在热更新当中的应用
    • 让热更包兼容所有版本的 App
    • 元素绑定

背景

我们在使用 Cocos 和 Native 进行交互的时候,发现体验并不是特别的友好。 如下所示,为我们项目当中的一段代码(代码已脱敏),当检测到发生了 js 异常,我们需要通知 Native 端去做一些处理。

jsException: function (scence, msg, stack) {
    if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
        jsb.reflection.callStaticMethod(
            "xxx/xxx/xxx/xxx/xxx",
            "xxx",
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
            scence, msg, stack
        );
    } else if (cc.sys.isNative && cc.sys.os === cc.sys.OS_IOS) {
        jsb.reflection.callStaticMethod(
            "xxx",
            "xxx:xxx:xxx:",
            scence, msg, stack
        );
    }
}

从代码当中我们可以看出有以下问题:

  • 方法调用非常麻烦,特别是 Android 端,需要明确的指出方法的路径,方法名,参数类型,特别容易出错;
  • 一旦 Native 端的方法发生变化,Cocos层必须要同步修改,否则就会出现异常,甚至有可能 crash;
  • 不方便回调;

我们尝试着用注解的思路来解决这些问题,从而设计并实现了 ABCBinding 。ABCBinding 能够极大的简化 Cocos和 Native 的交互,方便维护。

ABCBinding 的结构设计

ABCBinding 的结构如下图所示:

ABCBinding 包含 Native 和 TS 两个部分,Native 负责约束本地的方法,TS 负责对 Cocos 层提供调用 Native 方法的接口。 我们重新定义了一套 Cocos 和 Native 的交互模式:

  • 提供 Cocos 访问的 Native 方法必须为 public static,且参数必须为 Transfer(Transfer 为 SDK 提供的接口,能够在 Cocos 层和 Native 层传递数据);
  • 方法必须使用 ABCBinder 注解修饰,并在注解内传入约定好的 tag,此 tag 用于唯一标识这个方法;
  • 使用 SDK 提供的 callNativeMethod 方法,传入约定好的 tag 调用 Native 方法。

例子: 如下所示,为调用 Native 方法去下载,我们只需要传入约定好的 tag:downloadFile,并传入参数,便可以进行下载了。 TS 层:

Binding.callNativeMethod('downloadFile', { url: 'https://xxx.jpeg' }
).then((result) => {
    this.label.string = `下载成功:path=${result.path}`;
}).catch((error) => {
    this.label.string = error.msg;
});

Native 层:

@ABCBinder("downloadFile")
public static void download(Transfer transfer){
    new Thread(new Runnable() {
        @Override
        public void run() {
            String url = transfer.get("url","");
            try{
                //下载中
	        ...
                //下载成功
                TransferData data = new TransferData();
                data.put("path","/xxx/xxx.jpg");
                transfer.onSuccess(data);
            }catch (Exception e){
                //失败
                transfer.onFailure(e);
            }
        }
    }).start();
}

通过例子可以看到,使用 ABCBinding 能够让 Cocos 和 Native 的交互简单很多,我们再也不用再传入复杂的路径和参数了,而且回调也变得很优雅。接下来我们来看看 ABCBinding 是如何实现的。

具体实现

从上面的例子我们可以看出,ABCBinding 是通过 tag 来实现 Cocos 和 Native 进行交互的,那 SDK 是如何通过 tag 来找到对应的方法呢?

通过 tag 找到 Native 方法

我们定义了编译时注解 ABCBinder,

@Retention(RetentionPolicy.CLASS)//编译时生效
@Target({ElementType.METHOD})//描述方法
public @interface ABCBinder {
    String value();
}

在编译期间会生成一个类 ABCBindingProxy,成员变量 executors 包含了所有对应的 tag 和方法。其中真正可执行的方法被包装在了 ExecutableMethod 接口当中。

//以下为自动生成的代码
private Map executors = new java.util.HashMap();

private ABCBindingProxy() {
  executors.put("test2",new ExecutableMethod() {
    @Override
    public void execute(Transfer t) {
      com.example.abcbinding.MainActivity.test2(t);
    }
  });
  executors.put("test1",new ExecutableMethod() {
    @Override
    public void execute(Transfer t) {
      com.example.abcbinding.MainActivity.test1(t);
    }
  });
}
public interface ExecutableMethod {
    void execute(Transfer t);
}

因此我们只需要通过 executors 和 tag 就可以找到了对应的方法了,接着我们看看 TS 是如何与 Native 进行交互的, 以下是 SDK 里面 TS 层的部分代码,SDK 屏蔽了调用的具体细节,将请求的参数转变成为 json 字符串,并将相关的参数传递给 SDK 内部的方法 execute,由 execute 将请求转发给真正的方法。

public callNativeMethod(methodName: string, args ?: Record<string, string | number | boolean>): Promise < any > {
    ...
    if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
        let resultCode = jsb.reflection.callStaticMethod(
            'com/tencent/abckit/binding/ABCBinding',
            'execute',
            '(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I',
            methodName,
            typeof args === 'object' ? JSON.stringify(args) : '', cbName);
        ...
    } else if (cc.sys.isNative && cc.sys.os === cc.sys.OS_IOS) {
        let retId = jsb.reflection.callStaticMethod(
            'ABCBinding',
            'execute:methodName:args:callback:',
            methodName,
            typeof args === 'object' ? JSON.stringify(args) : '', cbName);
        ...
    } else {
        let msg = 'no implemented on this platform';
        reject(msg);
    }
}

这样,我们就解决了通过 tag 找到对应方法的问题。但还有两个问题需要解决: 如何约束 Native 方法?以及如何保证 tag 的唯一性?

约束 Native 方法

ABCBinding 规定,提供 Cocos 访问的 Native 方法必须为 public static,参数必须为 Transfer,且 tag 必须要保持唯一性,那怎么来约束呢? 在代码的编译期间,我们会去检查所有被 ABCBinder 修饰的方法,若发现这个方法并不符合我们的规范,则会直接报错。 如下所示: 1.参数错误

@ABCBinder("test1")
public static void test1(Transfer transfer, int a) {
    Log.i("测试", "test()");
}

2.方法非 static

@ABCBinder("test2")
public void test2(Transfer transfer) {
    Log.i("测试", "test()");
}

3.方法非 public

@ABCBinder("test3")
protected static void test3(Transfer transfer) {
    Log.i("测试", "test()");
}

4.tag 重复

@ABCBinder("helloworld")
public static void test1(Transfer transfer) {
    Log.i("测试", "test()");
}

@ABCBinder("helloworld")
public static void test2(Transfer transfer) {
    Log.i("测试", "test()");
}

优雅的回调

SDK 会在编译期间自动生成 callJs 方法,所有的回调都是通过 callJs 方法实现。Native 方法只需要调用 Transfer 所提供的回调接口,便可以轻松的将结果回调给 Cocos。由于 callJs 代码是自动生成,所以 SDK 不需要直接依赖 Cocos 库,只需要业务方依赖即可。

//以下为自动生成的代码
public void callJs(final String globalMethod, final TransferData params) {
  org.cocos2dx.lib.Cocos2dxHelper.runOnGLThread(new Runnable() {
    @Override
    public void run() {
      String command = String.format("window && window.%s && window.%s(%s);",
	  globalMethod,globalMethod, params.parseJson());;
      org.cocos2dx.lib.Cocos2dxJavascriptJavaBridge.evalString(command);;
    }
  });
}

ABCBinding 提供了onProcess,onSuccess 和 onFailed 回调方法, 以下为 downloadFile 接口的的回调示例: TS 层:

Binding.withOptions({
    //回调onProcess
    onProgress: (progress) => {
        let current = progress.current;
        this.label.string = `progress:${current}`;
    }
}).callNativeMethod(
    'downloadFile',
    { url: 'https://xxxx.jpg' }
).then((result) => {
    //回调onSuccess
    this.label.string = `下载成功:path=${result.path}`;
}).catch((error) => {
    //回调onFailed
    if (error) {
        this.label.string = error.msg;
    }
});

Native 层:

@ABCBinder("downloadFile")
public static void download(Transfer transfer){
    new Thread(new Runnable() {
        @Override
        public void run() {
            String url = transfer.get("url","");
            try{
              	//模拟下载过程
                for(int i =0;i<100;i++) {
                     transfer.onProgress("current", i);
                }
	       //下载成功
                TransferData data = new TransferData();
                data.put("path","/xxx/xxx.jpg");
                transfer.onSuccess(data);
            }catch (Exception e){
                //失败
                transfer.onFailure(e);
            }
        }
    }).start();
}

其他 feature

抹平系统差异

使用 ABCBinding 无须再判断当前系统是 Android 还是 IOS ,只需对应 Native 方法的 tag 保持一致即可。

Binding.callNativeMethod('isLowDevice').then(({isLowDevice}) => {
  console.log(isLowDevice);
})

无需关心线程切换

Native 方法使用 Transfer 回调时,ABCBinding 会自动切换到 Cocos 线程执行,无需业务方关心。

@ABCBinding("getHardwareInfo")
public static void getHardwareInfo(Transfer transfer) {
    TransferData data = new TransferData();
    data.put("brand", Build.BRAND);
    data.put("model", Build.MODEL);
    data.put("OsVersion", Build.VERSION.RELEASE);
    data.put("SdkVersion", Build.VERSION.SDK);
    transfer.onSuccess(data);
}

支持超时

ABCBinding 支持设置超时,其中超时的时间单位为秒,如下所示,超时会回调到 catch 方法当中。

Binding.withOptions({
    timeout: 120,
    onProgress: (progress) => {
        let current = progress.current;
        this.label.string = `progress:${current}`;
    }
}).callNativeMethod(
    'downloadFile',
    { url: 'https://xxxx.jpg' }
).then((result) => {
    this.label.string = `下载成功:path=${result.path}`;
}).catch((error) => {
    if (error.code == ERROR_CODE_TIMEOUT) {
         console.log("超时");
    }
});

彩蛋:在热更新当中的应用

我们在使用 Cocos 热更新服务的过程中发现,怎么确定 Cocos 热更新包能够发布到哪个 App 版本,是个难题。Cocos 热更新包能不能在这个 App 版本上正确运行,跟两个因素有关,Cocos 版本和 Native 接口。

Cocos 版本 如果 App 和热更包的 Cocos 版本不一致,那么很有可能这个热更包无法在 App 上运行,除非官方做了一些兼容处理。不过这个因素可控,Cocos 的版本不会频繁的升级,而且我们知道 Cocos 版本和 App 版本的对应关系。

Native 接口 如果热更包调用了某个 Native 的接口,但是这个接口在有些版本上不存在,那该热更包就无法在这个版本的 App 上运行。 在我们的业务场景当中,Cocos 版本不会频繁变更,但是每个版本的 Native 代码可能会相差较大,人工来核对每个版本的 Native 接口变更是一件极为费时费力的事情。 那么 ABCBinding 能帮助我们做什么呢?

让热更包兼容所有版本的 App

首先我们去除 Cocos 版本的因素,因为这个因素可控,且业务方无法解决。 ABCBinding 知道本地有哪些接口可用,所以当 Cocos 调用了一个不存在的接口时,我们会返回一个特殊的 code,这样热更包只需要在内部做兼容处理就可以了。 如下所示:

Binding.callNativeMethod('downloadFile', { url: 'https://xxx.jpeg' }
).then((result) => {
     //处理逻辑
}).catch((error) => {
    if(error.code == ERROR_CODE_METHOD_NOT_DEFINED){
        console.log("未找到该方法");
    }
});

元素绑定

既然热更新跟这两个元素有关,我们就可以通过这两个元素,让 App 和热更包进行匹配,如果能够匹配,那么这个热更包就可以下发到这个版本的 App 上。 Cocos 版本:我们在打包的时候就可以确定; Native 接口:在打包的过程中,ABCBinding 可以将当前所支持的接口,按照约定的方式生成一个元素。 例如:本地的接口有 test1,test2,test3 我们可以将接口按照指定的方式排序拼接取 md5 值,生成第二个元素。 这样当 App 和热更新包的两个元素能够匹配时,就能够下发了。

以上就是简化Cocos和Native交互利器详解的详细内容,更多关于Cocos Native交互简化的资料请关注我们其它相关文章!

(0)

相关推荐

  • React Native可定制底板组件Magic Sheet使用示例

    目录 正文 如何使用它 1.安装并导入 2.基本使用方法 预览 正文 一个React Native组件,通过提供一个强制性的API,可以从应用程序的任何地方(甚至在组件之外)调用,以显示一个完全可定制的底部表单,并能够等待它解决并得到一个响应. 这个库依赖于Gorhom的/bottom-sheet 的模态组件,并接受相同的道具和儿童. 如何使用它 1.安装并导入 # Yarn $ yarn add react-native-magic-sheet # NPM $ npm i react-nati

  • React Native提供自动完成的下拉菜单的方法示例

    目录 正文 如何使用它 1.安装 2.导入自动完成的下拉组件 3.基本使用方法 4.数据集应该是一个JS对象或数组 5.可用的道具 预览 正文 一个具有搜索和自动完成(typeahead)功能的React Native的下拉项目选择器. 如何使用它 1.安装 # Yarn $ yarn add react-native-autocomplete-dropdown # NPM $ npm i react-native-autocomplete-dropdown 2.导入自动完成的下拉组件 impo

  • 适用于React Native 旋转木马应用程序介绍

    目录 正文 如何使用它 1.安装并导入 react-native-intro-carousel 2.示例应用程序 预览 正文 一个带有分页功能的介绍页面旋转木马(onboarding)动画. 如何使用它 1.安装并导入 react-native-intro-carousel # Yarn $ yarn add react-native-intro-carousel # NPM $ npm i react-native-intro-carousel import * as React from '

  • React Native自定义Android的SSL证书链校验

    目录 前言 HTTPS请求 WebSocket 前言 虽然这次分享的内容解决了本人的实际开发需求,但由于不是专职的Android开发工程师,涉及到的Android相关内容可能会存在错误或者写法不合理,仅供参考,请多多指教. 本文示例基于: React Native - 0.67.3 Android - 10+ 不包括iOS 由于业务原因,需要在生产环境里面使用自签发证书,那自然这个证书是无法通过Android证书链验证的,为此需要自定义校验规则. 本文分为两部分,介绍了对HTTPS请求和WebS

  • Native Memory Tracking追踪区域示例分析

    目录 Compiler Internal Symbol Native Memory Tracking Arena Chunk Unknown Compiler Compiler 就是 JIT 编译器线程在编译 code 时本身所使用的内存. 查看 NMT 详情: [0x0000ffff93e3acc0] Thread::allocate(unsigned long, bool, MemoryType)+0x348 [0x0000ffff9377a498] CompileBroker::make_

  • ReactNative支付密码输入框实现详解

    目录 正文 hooks版本 正文 项目中需求如果涉及钱包,支付等功能,可以大概率会用到输入密码组件,也算是个常见组件吧. 之前写过一个纯js的开源组件,使用的类的形式,也比较老了,可直接添加npm库到项目中进行使用. hooks版本 最近项目需要,又重新写了一个hooks版本的,现在直接上源码,对于不想添加依赖库的伙伴,可直接复制源码到项目中,直接使用. 'use strict'; import React, {useRef, useState} from 'react'; import {St

  • 简化Cocos和Native交互利器详解

    目录 背景 ABCBinding 的结构设计 具体实现 通过 tag 找到 Native 方法 约束 Native 方法 优雅的回调 其他 feature 抹平系统差异 无需关心线程切换 支持超时 彩蛋:在热更新当中的应用 让热更包兼容所有版本的 App 元素绑定 背景 我们在使用 Cocos 和 Native 进行交互的时候,发现体验并不是特别的友好. 如下所示,为我们项目当中的一段代码(代码已脱敏),当检测到发生了 js 异常,我们需要通知 Native 端去做一些处理. jsExcepti

  • Spring boot admin 服务监控利器详解

    目录 一.简介 二.搭建 1.服务端 2.客户端 3.启动项目 4.客户端配置 3.微服务 3.1.服务端 3.2.客户端 4.我的微服务预警发送其他服务状态信息思路 一.简介 用于对 Spring Boot 应用的管理和监控.可以用来监控服务是否健康.是否在线.以及一些jvm数据等等.Spring Boot Admin 分为服务端(spring-boot-admin-server)和客户端(spring-boot-admin-client),服务端和客户端之间采用 http 通讯方式实现数据交

  • LangChain简化ChatGPT工程复杂度使用详解

    目录 什么是LangChain? LangChain中的模块,每个模块如何使用? 具体代码 什么是LangChain? 使用ChatGPT大家可能都是知道prompt, (1)想像一下,如果我需要快速读一本书,想通过本书作为prompt,使用ChatGPT根据书本中来回答问题,我们需要怎么做? (2)假设你需要一个问答任务用到prompt A,摘要任务要使用到prompt B,那如何管理这些prompt呢?因此需要用LangChain来管理这些prompt. LangChain的出现,简化了我们

  • Angular2 组件交互实例详解

    1. 组件通信 我们知道Angular2应用程序实际上是有很多父子组价组成的组件树,因此,了解组件之间如何通信,特别是父子组件之间,对编写Angular2应用程序具有十分重要的意义,通常来讲,组件之间的交互方式主要有如下几种: l 使用输入型绑定,把数据从父组件传到子组件 l 通过 setter 拦截输入属性值的变化 l 使用 ngOnChanges 拦截输入属性值的变化 l 父组件监听子组件的事件 l 父组件与子组件通过本地变量互动 l 父组件调用 ViewChild l 父组件和子组件通过服

  • PHP与MySQL交互使用详解

    PHP与MySQL交互使用详解 1.创建自动连接数据库的代码,并生成一些必要的代码.我们仔细研究一下数据库的连接函数,会发现是这样的一行代码. $link_id=@mysql_connect($hostname,$username,$password); 所以我们在include文件connect.inc中添加以下代码就可以了.connect.inc<?php$hostname='localhost'; $username='phpstar';$password='phpstar';$dbnam

  • 复选框和Struts2后台交互代码详解

    本文研究的主要是Struts框架中复选框的相关内容.复选框在web开发中用的非常广泛,具体介绍如下. 案例 如下图,当前为用户选中的水果为"香蕉",点击按钮,跳转到修改界面进行修改. 跳转到修改界面后要回显用户的选择(香蕉),然后由用户再次进行勾选,如图: 前台界面: <body> <form action="checBoxAction_test.action" method="post"> 请选择您喜欢的水果:<b

  • PHP与Web页面的交互示例详解二

    前言 在<PHP学习笔记-PHP与Web页面的交互1>笔记中讲解了form表单的一些属性,包括它的输入域标记.选择域标记和文字域标记的写法,接下来的内容就是讲如何获取表单数据以及PHP数据的传递,包括对各种控件值的获取. 插入表单 提交表单之前一定得有表单,当我们的表单创建完毕后可以将表单插入Web页中,代码如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://w

  • PHP与Web页面的交互示例详解一

    前言 这篇笔记记录的是Web表单的相关操作,Web表单主要用来在网页中发送数据到服务器.比如在日常开发中,提交注册时需要提交表单,表单从客户端传送到服务器,经过服务器处理后,再将用户所需要的信息传递回客户端,进而实现PHP与Web表单的交互. 表单 使用<form>元素,并在其中插入相关的表单元素,即可创建一个表单. 表单结构: <form name="form_name" method="method" action="url"

  • Struts2和Ajax数据交互示例详解

    前言 我们从Web 2.0的随波逐流,Ajax的大放异彩说起,Struts2框架自己整合了对Ajax的原生支持(struts 2.1.7+,之前的版本可以通过插件实现),框架的整合只是使得JSON的创建变得异常简单,并且可以简单的融入到Struts2框架中,当然这只是在我们需要JSON的时候才会显得流光溢彩. ajax请求在项目中常常使用,今天就平时掌握的总结一下,关于使用ajax请求到Struts2中的action时,前台页面与后台action之间的数据传递交互问题. 这里我主要记录下自己所掌

  • Android WebView与JS交互全面详解(小结)

    Android 和 H5 都是移动开发应用的非常广泛.市面上很多App都是使用Android开发的,但使用Android来开发一些比较复杂附属类,提示性的页面是得不偿失的.而H5具有开发速度快,更新不用依赖于App的更新,只需要服务端更新相应的页面即可,所以,App和H5页面相结合就显得尤为重要.而android和H5都不可能每次都是独立存在的,而是相互影响也相互的调用,获取信息等,例如,H5页面要获取App中的用户的基本信息,或者App端要操作H5页面等,下面来看看这两是怎么交互的 目录 1.

随机推荐