淘先锋技术网

首页 1 2 3 4 5 6 7

1.介绍

业务需求需要提供较多native接口(Android、IOS)给Unity、Unreal来进行使用。为了方便开发,提供类似于阉割版的JSBridge的框架来实现通信。

native需要给engine提供大量接口,即主要讨论Engine如果调用Native接口为主。

2.原理

Unity调用Android、IOS接口。Unity提供AndroidJavaClass工具类来进行通信,IOS则是直接通过C#调用C++方法。

//Android

var javaClass = new AndroidJavaClass("className");

javaClass.CallStatic("methodName", "params");

//IOS

[DllImport("__Internal")]

private static extern void ObjCMethod();

CSharpObj.ObjCMethod();

Unreal4调用Android、IOS接口。Unreal也类似,提供FAndroidApplication来进行JNI调用,IOS则是可以直接通过Objc和C++相互调用进行实现。

//Android

JNIEnv *env = FAndroidApplication::GetJavaEnv();

auto jUnrealClass = FAndroidApplication::FindJavaClass(ClassName);

env->CallStaticObjectMethod();

//IOS 省略

3.调用流程

除了业务需求外,需要保证以下几点的实现,Unity、Unreal、Android、IOS的兼容,接入,方法调用以及回调。

3.1 方法调用

框架的核心点就在于Engine如何调用到native的方法。要保证各个平台的兼容性,就得自定义一套通信协议。这里使用Json来当做消息通信的载体。

{

"service":"className",

"method":"methodName",

"callback":true,

"callbackId":"callbackId",

"args":"{

"args1":"value1",

"args:2":"value2"

}"

}

Engine通过发送以上消息体(Command)给native层。

service:类名称

method:方法名称

callback:是否需要回调

callbackId:回调UUID,用于区分回调

args:参数

3.2 方法回调

我们可以通过Command来进行发送消息,要支持回调,也就需要订一个Result来接受Native的信息。

{

"code":0,

"message":"Success",

"callbackId":"callbackId",

"content":"{

"result1":"value1",

"result2":"value2"

}"

}

Engine通过以上消息题来接受Native的消息。

code:0 是否调用成功

message:调用消息

callbackId:同Command的CallbackId相同,用于Engine方法区分回调是由哪一个命令来执行的。

content:回调内容

native在接收到Command消息体之后执行异步操作,在执行完之后,构造Result消息体回调给Engine。这样就形成了Engine调用Native的一个闭环。

3.3 native层接入方式

3.3.1 Android接口定义以及注册、实现

@BridgeService("CommandService")

public interface CommandService{

@BridgeMethod("execute")

void execute(Activity activity, @BridgeParam("param") string param, @BridgeParam("param2") string param2, BridgeCallback callback);

}

public class CommandServiceImpl implements CommandService{

@Override

public void execute(Activity activity,string param,string param2,BridgeCallback callback){

//TODO Engine真正调用到的方法

}

}

Android端通过三个注解@BridgeService,@BridgeMethod,@BridgeParam来对Command中的service,method,args三个字段进行绑定。下文也是通过改规则来反射获取到指定的方法进行执行。

tips:Activity 以及 BridgeCallback 不需要通过BridgeParam来标注。Activity通过Bridge框架初始化后缓存,而BridgeCallback则可以通过Unity的AndroidJavaProxy来进行构建(Unreal通过jni方法进行回调)。

3.3.2 IOS接口定义、实现

+ (void) param:(NSString *) param param2:(NSString*) param2 bridgeCallback:(void (^)(NSString *result))callback

因为IOS方法名为 args1:args2:args3: ,所以IOS的匹配规则和Android不同。当参数不止一个,IOS则去匹配Command.args而不是Command.method。所以定义JSON时,需要考虑到IOS的方法名特点。

3.4 Engine层接入

3.4.1 Unity 使用

EngineBridge.GetInstance().Register(serviceClz,serviceImpl);

EngineBridge.GetInstance().Reigster(command,(result)=>{

//TODO 回调

});

3.4.2 Unreal 使用

GetBridge()->CallHandler(commandJson);

4.如何实现

以Android端为主,IOS仅列举Command解析流程。

4.1 native层实现

4.1.1 调用入口

Engine所有的Command消息体都用该方法入口

public void registerHandler(String json,BridgeCallback callback){

Command command = new Command(json);

//缓存BridgeCallback

mBridgeCallbackMaps.put(command.callbackId,callback);

//创建执行Handler

createEngineHandler();

//切换到主线程执行方法

getActivity().runOnUIThread(()=>{

new CommandTaskImpl().execute(command,result=>{

//通过Handler切换到 EngineThread 进行回调

sendMessage(result);

});

});

}

4.1.2 Command 执行方法

native接收到Json字符串后,通过反序列化转换成Command类。这里通过CommandTask来对该命令进行处理。会经过一下几步:

获取Command.service

获取执行方法 Command.method

解析Command.args参数,填入method方法中

执行method方法

以上每步操作都需要TryCatch,及时抛出异常给Engine。

//执行方法

public void execute(Command command,BridgeCallback callback){

//获取Service、方法

Method method = BridgeReflect.getServiceMethodFromCommand(BridgeHolder.INSTANCE.getBridgeService(command.service), command);

//获取执行方法的Object

IBridgeService serviceImpl = BridgeHolder.INSTANCE.getBridgeService(command.service).getValue();

//执行method

Object result = this.execute(serviceImpl, method, command, callback);

//判断方法是否有返回值和Callback是否为false

if (result != null && command.callback) {

callback.onResult(BridgeJsonHelper.object2JsonString(result));

}

}

4.1.3 Engine回调给native处理

如果Command中Callback字段为true且方法中定义中包含了BridgeCallback,那么则会构建一个BridgeCallback进行传递下去。

tips:BridgeCallback接口就是用于回调给Engine

以下代码举例:

public void createEngineHandler()

{

//回调给engine时需要切换到引擎的主线程(eg:UnityMain),所以需要构建一个Engine Thread的Handler

Looper.prepare();

mEngineHandler = new Handler(Looper.myLooper(),new Handler.Callback(){

@Override

public boolean handleMessage(Message message){

...

//balabal处理之后,回调给游戏

callback.onResult(message.obj);

...

//如果是Unreal的情况下,通过JNI方法回调

nativeOnResult(message.obj);

}

});

Looper.looper();

}

这里需要注意的点是,Engine的执行线程通常不是Android的主线程。通常Engine调用原生方法,不能保证不进行UI操作,所以我们需要确认的是调用到Android方法时的线程是在Android的主线程,回调的时是在 Engine的执行线程。

4.1.4 反射具体执行方法

根据 4.1.2中介绍,native层接收到Json之后会进行反序列化成Command命令,需要注意的关键点在在于匹配Command.args和@BridgeParam注解。

public static Object[] constructorCommandArgs(Method method,Command command,BridgeCallback callback){

Class>[] parameterTypes = method.getParameterTypes();

Object[] args = new Object[parameterTypes.length];

try {

//讲Command.args 转换为JsonObject

JSONObject jsonObject;

if (TextUtils.isEmpty(command.args)) {

jsonObject = new JSONObject();

} else {

jsonObject = new JSONObject(command.args);

}

for (int i = 0; i < parameterTypes.length; i++) {

Class> paramsClz = parameterTypes[i];

if (paramsClz == Activity.class) {

//如果参数为Activity,设置当前Bridge缓存的Activity

args[i] = Bridge.getInstance().getActivity();

} else if (paramsClz == BridgeCallback.class) {

args[i] = callback;

} else {

//匹配BridgeParam和Command.args参数

args[i] = BridgeReflect.findParams(jsonObject, method);

}

}

} catch (IllegalArgumentException | JSONException e) {

e.printStackTrace();

throw new EngineBridgeException(EngineBridgeExceptionStatus.COMMAND_ARGS_ERROR.getExpandMessage(e.getMessage()));

}

return args;

}

IOS的解析过程略有不同。

根据Command.service获取当前metaClass(元类,用于获取Method)以及targetClass(执行方法的Class类)

Class targetClz = objc_getClass([command.service UTF8String]);

Class metaClz = objc_getMetaClass([command.service UTF8String]);

获取当前类的所有方法,然后遍历所有方法以及属性,匹配Command.args。

关键代码:

Method *methodArr = class_copyMethodList(metaClz,&methodIndex);

for (NSInteger i = 0; i < methodIndex; i++) {

Method method = methodArr[i];

//获取方法指针SEL

SEL sel = method_getName(method);

//根据SEL获取方法名

NSString* methodName = NSStringFromSelector(sel);

//因为IOS的方法名为 args:args2:arge3凭借而成,所以通过字符串“:”来分割。

NSArray *methodNameSpreate = [methodName componentsSeparatedByString:@":"];

//根据Command.args、方法名构造出方法参数

NSArray *methodArgs = [self class:metaClass

currentMethod:command.method

methodSeparated:methodNameSpreate

commandArgs:commandArgs

bridgeCallback:result];

//根据元类以及方法SEL获取方法签名信息

NSMethodSignature *signature = [metaClass instanceMethodSignatureForSelector:sel];

if (signature == nil) {

continue;

}

//IOS 的签名信息默认是2个参数,以此累加

//匹配规则:当方法的参数仅为1,且方法名匹配Command.service、构造BridgeCallback 作为参数

// example: -(void) registerCallback:(void (^)(NSString *result)) callback;

if (signature.numberOfArguments == 3

&& [command.method isEqualToString:methodNameSpreate[0]]

&& (methodArgs == nil || [methodArgs count] == 0)) {

NSLog(@"method:%@ add BridgeCallback",methodName);

methodArgs = [NSArray arrayWithObject:result];

}

if (methodArgs == nil) {

NSLog(@"method:%@ args is NULL",methodName);

continue;

}

if ([methodArgs count] != signature.numberOfArguments - 2) {

NSLog(@"method:%@ args 个数与签名个数不同",methodName);

continue;

}

//执行方法

id methodResult = [self invoke:metaClass signature:signature targetClass:targetClass method:sel args:methodArgs];

if (command.callback && methodResult != nil) {

result([TDSBridgeTool jsonStringWithObject:methodResult]);

}

5.成果

目前该框架已经稳定运行3个月以上。