由于之前的jsJavaBridge的调用方式比较复杂,不太好复用,故提供一种更简便的使用方法。以下是说明文档和示例。
在脚本层的接口只有两个,分别是sendToNative和onNative,分别传输和接收原生层参数。需要注意的有几点:
- 由于现在这个功能还在实验阶段,所以只支持string的传输,如果需要传输包含多种参数的对象,请考虑将其转化为Json形式进行传输并在不同层级解析。
- onNative同一时间只会记录一个函数,当再次set该属性的时候会覆盖原先的onNative方法。
- sendToScript 方法是单向通信,其不会关心下层的返回情况,也不会告知js代码操作成功或者失败。
//JavaScript
export namespace bridge{
/**
* send to native with at least one argument.
*/
export function sendToNative(arg0: string, arg1?: string): void;
/**
* save your own callback controller with a js function,
* use jsb.bridge.onNative = (arg0: String, arg1: String | null)=>{...}
* @param args : received from native
*/
export function onNative(arg0: string, arg1?: string | null): void;
}
对应的暴露给原生层的同样为两个接口。功能对称。
//JAVA
public class JsbBridge {
public interface ICallback{
/**
* Applies this callback to the given argument.
*
* @param arg0 as input
* @param arg1 as input
*/
void onScript(String arg0, String arg1);
}
/**Add a callback which you would like to apply
* @param f ICallback, the method which will be actually applied. multiple calls will override
* */
public static void setCallback(ICallback f);
/**
* Java dispatch Js event, use native c++ code
* @param arg0 input values
*/
public static void sendToScript(String arg0, String arg1);
public static void sendToScript(String arg0);
}
//Objective-c
typedef void (^ICallback)(NSString*, NSString*);
//typedef int64_t strFunc;
@interface JsbBridge : NSObject
+(instancetype)sharedInstance;
-(bool)setCallback:(ICallback)cb;
-(bool)callByScript:(NSString*)arg0 arg1:(NSString*)arg1;
-(void)sendToScript:(NSString*)arg0 arg1:(NSString*)arg1;
-(void)sendToScript:(NSString*)arg0;
@end
假设我们的广告接口设置在原生层比如Java-Android广告,那么当玩家点击打开广告的按钮时,理应触发Java打开广告的操作。
我们会将打开广告的接口写成以下脚本
public void openAd(String adUrl){
//Code to open ad
}
这时候我们需要先将打开广告的事件注册起来。
JsbBridge.setCallback(new JsbBridge.ICallback() {
@Override
public void onScript(String usrName, String url) {
//Check usr
//Open Ad
openAd(url);
}
});
并且在js层脚本中对按钮的点击事件进行打开操作
public static onclick(){
//usrName and defaultAdUrl are both string
jsb.bridge.sendToNative(usrName, defaultAdUrl);
}
这样就可以通过Jsb.Bridge这个通道将需要的信息发送到Java层进行操作了
假设我们的动画播放操作记录在Js层,并且希望在Java层播放这个动画,我们也可以将它注册起来。我们首先定义该函数。
public void playAnimation(animationName: string, isLoop: boolean){
//Code to play Animation
}
然后在onNative中记录该方法
jsb.bridge.onNative = (animationName: string, isLoop: String | null):void=>{
if(isLoop && isLoop == "true") {
this.playAnimation(animationName, true);
return;
}
this.playAnimation(animationName, false);
return;
}
仍然以安卓项目为例,Java代码示范:
JsbBridge.sendToScript("SkeletonAnim001", "true");
就可以调用到JS的播放操作了。
即本工程。由于js中onNative操作和java/objc中setCallback
只能同一时间存在一份,不可避免的会有一些拘束,所以我们可以通过添加一个Map来存储我们需要的方法,来利用同一个callback做不同的事情。
在这里我们定义一个工具类,命名为方法管理器(Method Manager)来储存不同的方法。
export class MethodManager {
private methodMap: Map<String, Function>;
public static instance: MethodManager = new MethodManager;
public addMethod(methodName: String, f: Function): boolean {
if (!this.methodMap.get(methodName)) {
this.methodMap.set(methodName, f);
return true;
}
return false;
}
public applyMethod(methodName: String, arg?: String): boolean {
if (!this.methodMap.get(methodName)) {
console.log("Function not exist");
return false;
}
var f = this.methodMap.get(methodName);
try {
f?.call(null, arg);
return true;
} catch (e) {
console.log("Function trigger error: " + e);
return false;
}
}
public removeMethod(methodName: String):any{
return this.methodMap.delete(methodName);
}
constructor() {
this.methodMap = new Map<String, Function>();
MethodManager.instance = this;
}
}
接着我们将在ccclass
中创建多个标签并且给定不同的属性改变函数。利用刚刚的jsb.bridge.onNative
调用方法管理器的applyMethod
方法。
@ccclass('CallNative')
export class CallNative extends Component {
//static eventMap: Map<string, Function> = new Map<string, Function>();
@property(Label)
public labelForContent : Label|undefined;
@property(Label)
public labelForColor : Label|undefined;
@property(Label)
public labelForSize : Label|undefined;
start() {
new MethodManager;
jsb.bridge.onNative = (methodName: string, arg1?: string | null) => {
console.log("Trigger event for " + methodName + " is " + MethodManager.instance.applyMethod(methodName, arg1!));
};
this.registerAllScriptEvent();
}
public registerAllScriptEvent() {
MethodManager.instance.addMethod("changeLabelContent", (usr: string) => {
this.changeLabelContent(usr);
});
MethodManager.instance.addMethod("changeLabelColor", () => {
this.changeLabelColor();
});
MethodManager.instance.addMethod("changeLightColor", () => {
this.changeLightColor();
});
}
//Methods to apply
public changeLabelContent(user: string): void {
CC_LOG_DEBUG("Hello " + user + " I'm K");
this.labelForContent!.string = "Hello " + user + " ! I'm K";
}
public changeLabelColor(): void {
this.labelForColor!.color = new Color("#B8F768");
this.labelForContent!.color = new Color("#87E9B2");
}
public changeLightColor(): void {
this.lightToChange!.color = new Color("#90FF03");
}
}
我们在场景中添加3个按钮,用来触发对应的事件,但这个事件只有当Native层的方法被成功触发并返回时才能被触发,所以对应的btn event为以下写法。
//Button click event for SAY HELLO
public sayHelloBtn(){
jsb.bridge.sendToNative("requestLabelContent");
}
//Button click event for CHANGE LABEL COLOR
public changeLabelColorBtn(){
jsb.bridge.sendToNative("requestLabelColor");
}
public changeLightColorBtn(){
jsb.bridge.sendToNative("requestBtnColor", "50");
}
我们需要在原生层注册对应的callback函数,这应该是一个可以接收两个string参数的函数。并对第二个参数的有无进行判断。同理,我们声明一个HashMap来储存这些callback,并且存储一个统一的callback函数MyCallback
。在AppActivity
的创建时注册该函数。
首先需要在Java的应用入口初始化JsbBridgeTest。
public class AppActivity extends CocosActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508
...
JsbBridgeTest.start();
}
...
}
在这里JsbBridgeTest
设置为了Static变量保证全局可访问。
public class JsbBridgeTest {
public interface MyCallback{
void onTrigger(String arg);
}
public static void start(){
JsbBridgeTest.myCallbackHashMap.put("requestLabelContent", arg ->{
System.out.print("@JAVA: here is the argument transport in" + arg);
JsbBridge.sendToScript("changeLabelContent","Charlotte");
});
JsbBridgeTest.myCallbackHashMap.put("requestLabelColor", arg ->{
System.out.print("@JAVA: here is the argument transport in" + arg);
JsbBridge.sendToScript("changeLabelColor");
});
JsbBridgeTest.myCallbackHashMap.put("requestBtnColor", arg ->{
System.out.print("@JAVA: here is the argument transport in" + arg);
JsbBridge.sendToScript("changeLightColor");
});
JsbBridge.setCallback(new JsbBridge.ICallback() {
@Override
public void onScript(String arg0, String arg1) {
JsbBridgeTest.myCallbackHashMap.get(arg0).onTrigger(arg1);
}
});
}
public static HashMap<String, MyCallback> myCallbackHashMap = new HashMap<>();
private static JsbBridgeTest instance;
}
这样在Android应用初始化的时候就会将方法注册进去,并且该函数被触发时会通过JsbBridge反向执行JS的方法。
Objc中同样需要首先初始化一次JsbBridgeTest
。在这里示例工程将其设置为单例模式,来保证全局唯一性。然后用block的形式来注册事件。
//At AppDelegate.mm
jsbBridget = [JsbBridgeTest sharedInstance];
//At JsbBridgeTest.mm
@implementation JsbBridgeTest{
NSMutableDictionary<NSString*, eventCallback> *cbDictionnary;
}
static JsbBridgeTest* instance = nil;
static ICallback cb = ^void (NSString* _arg0, NSString* _arg1){
[[JsbBridgeTest sharedInstance] applyMethod:_arg0 arg1:_arg1];
};
+(instancetype)sharedInstance{
static dispatch_once_t pred = 0;
dispatch_once(&pred, ^{
instance = [[super allocWithZone:NULL]init];
});
return instance;
}
+(id)allocWithZone:(struct _NSZone *)zone{
return [JsbBridgeTest sharedInstance];
}
-(id)copyWithZone:(struct _NSZone *)zone{
return [JsbBridgeTest sharedInstance];
}
-(void)addMethod:(NSString*)arg0 callback:(eventCallback)callback {
[cbDictionnary setValue:callback forKey:arg0];
}
-(void)applyMethod:(NSString*)name arg1:(NSString*)arg1 {
[cbDictionnary objectForKey:name](arg1);
}
-(id)init{
self = [super init];
cbDictionnary = [NSMutableDictionary new];
eventCallback requestLabelContent = ^void(NSString* arg){
JsbBridge* m = [JsbBridge sharedInstance];
[m sendToScript:@"changeLabelContent" arg1:@"Charlotte"];
};
eventCallback requestLabelColor = ^void(NSString* arg){
JsbBridge* m = [JsbBridge sharedInstance];
[m sendToScript:@"changeLabelColor"];
};
eventCallback requestBtnColor = ^void(NSString* arg){
JsbBridge* m = [JsbBridge sharedInstance];
[m sendToScript:@"changeLightColor"];
};
[self addMethod:@"requestLabelContent" callback:requestLabelContent];
[self addMethod:@"requestLabelColor" callback:requestLabelColor];
[self addMethod:@"requestBtnColor" callback:requestBtnColor];
JsbBridge* m = [JsbBridge sharedInstance];
[m setCallback:cb];
return self;
}
@end
需要注意的是,JsbBridge
的setCallback
需要是一个被定义的block
,并且最好是全局变量,否则则会导致脱离作用域后指针为空。这也是为什么这里将JsbBridgeTest
设为单例的原因。
当初次进入场景时,界面如下。
点击SAY HELLO
按钮,第一行的内容会改变为打过招呼的信息,否则即为失败。
点击CHANGE LABEL COLOR
按钮,字体会变为如下的绿色。具体颜色并不一定完全相同(根据设备不同会有色彩差异)
点击CHANGE LIGHT COLOR
会改变立方体颜色。