美文网首页
Android、IOS与Unity、UE4桥接详解

Android、IOS与Unity、UE4桥接详解

作者: CactusXe | 来源:发表于2021-02-22 14:25 被阅读0次

1.介绍

框架思路按照 Qingtech的浅谈Unity与Android之间的通信

IOS Runtime相关知识参考

业务需求需要提供较多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<string>("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消息体都用该方法入口

/**
 *
 * 引擎调用方法入口
 * @param json Command 消息体
 * @param callback 回调给engine
 */
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注解。

/**
 * 构建执行参数
 * @param method 执行参数
 * @param command 命令
 * @param callback 回调接口
 */
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个月以上。

相关文章

网友评论

      本文标题:Android、IOS与Unity、UE4桥接详解

      本文链接:https://www.haomeiwen.com/subject/tiahfltx.html