普通视图

发现新文章,点击刷新页面。
今天 — 2026年2月8日首页

APP原生与H5互调Bridge技术原理及基础使用

作者 黄诂多
2026年2月8日 11:57

API使用

js调用原生插件功能

调用命名为'11'的插件里的一个定时器api:jsCallTimer

带回调结果带参数的调用方式:

YN.callNative('11',"jsCallTimer",'我是传到原生端的参数',function (value) {
      if (a == 1){
        document.getElementById("progress1").innerText = value
      }else{
        document.getElementById("progress2").innerText = value
      }
    },function (error) {
      alert(error)
    })

不带回调结果带参数的调用方式:

YN.callNative('11',"jsCallTimer",'我是传到原生端的参数')

不带回调结果不带参数的调用方式:

YN.callNative('11',"jsCallTimer")

原生调用js插件功能

调用命名为'asynObj'的插件里的一个定时器api:startTimer

带回调结果带参数的调用方式:

[dwebview callHandler:@"asynObj" action:@"startTimer" arguments:@"我是传到js端的参数" completionHandler:^(CallbackStatus status, id  _Nonnull value, NSString * _Nonnull callId, BOOL complete) {
        [sender setTitle:[NSString stringWithFormat:@"%@-%@",value,callId] forState:0];
    }];

不带回调结果的调用方式:

[dwebview callHandler:@"asynObj" action:@"startTimer" arguments:@"我是传到js端的参数" completionHandler:nil];

一些全局约定

  • js调原生和原生调js的参数传递必须是json字符串格式。

  • api调用,底层逻辑必须使用命名空间方式即:namespace.apixxx的形式。

  • 还有很多规范和约定,后续补充。

js call native

关键技术点

原生Android端向浏览器注入供js调用的对象‘_anbridge’,对象里实现‘call()’方法,并且方法需要加上@JavascriptInterface注解,代码示例:

WebSettings webSettings = wv.getSettings();
webSettings.setJavaScriptEnabled(true);
wv.addJavascriptInterface(new JsApp(),"_anbridge");
class JsApp{
  public JsApp(){}
  @JavascriptInterface
  public void call(Object obj){

  }
}

原生iOS端

向浏览器配置对象里注入‘window._ynwk=true;’这段js代码,并且设置注入时机为开始加载时即:injectionTime=WKUserScriptInjectionTimeAtDocumentStart,代码实现:

///初始化注入js标记
    WKUserScript *script = [[WKUserScript alloc] initWithSource:@"window._ynwk=true;"
                                                  injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                               forMainFrameOnly:YES];
    [configuration.userContentController addUserScript:script];

实现js端换起原生通信的关键是实现wk的h5输入框拦截回调方法- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler
当js端执行代码‘prompt()’时原生端就会自动调起该方法

在上面实现的基础上,js端判断window._anbridge为true则为与Android通信,执行代码:_anbridge.call(api, arg),如果判断window._ynwk为true则为与iOS端通信,执行代码:prompt('_ynbridge=' + api, arg),js端代码实现:

var natiValue = '';
if (window._anbridge)
   natiValue = _anbridge.call(api, arg);//调用android对象的call()
else if (window._ynwk)
   natiValue = prompt('_ynbridge=' + api, arg);

原生端、js端提供的api都要通过命名空间的方式管理,如:api_1在‘namespace1’这个命名空间下的类里面,则js端调用api_1书写形式为‘namespace1.api_1’。

原生端和js端提供的功能都以插件的方式提供,插件(除基础插件)都继承自一个基础插件类,插件结果回调都是走异步回传值方式,同步方式也可以但暂没实现。

iOS端逻辑步骤

基础插件对象是处理js通讯和插件扩展的必要条件,wk浏览器初始化好后将基础插件类注册进插件集合,然后读取配置文件里可用的其他插件,将每个插件类注册进插件集合,代码实现:

//注册基础插件
    [self addJavascriptObject:self.ynPlugin namespace:baseNameSpace];
    //注册已有插件
    NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"applyPlugPlist" ofType:@"plist"];
    NSArray *modules = [NSArray arrayWithContentsOfFile:plistPath];
    for (NSDictionary *obj in modules) {
        Class class = NSClassFromString(obj[@"plug"]);
        if (class != nil && ![class isKindOfClass:[NSNull class]]) {
            [self addJavascriptObject:[[class alloc] init] namespace:obj[@"namespace"]];
        }
    }
  1. js端的第一个信号来自wk的h5输入框拦截回调方法,参数prompt里携带js端要调用的api名字,参数值为字符串:_ynbridge=namespace1.api_1,_ynbridge=为YNBridge框架调用的标记,如果不是以这个标记开头则不做任何处理,只弹出正常的系统弹框。

  2. 通过api名,去插件集合里找有没有注册对应的插件对象,如果没有找到或找到了但插件下没有对应api则将错误结果返回js端

  3. js调起的api,参数由defaultText携带。defaultText是json字符串,需要转换为json对象来解析出数据,参数值示例:{"data":null,"callId":"callId0"} data:真实参数值。 callId:api调用事件id或叫回传值队列id,当次api调用js需要回传值时此参数不为空,如果为空则表示当次api调用js端不需要结果回调

  4. -(BOOL)exec:(YNJsCallInfo*)arg 此方法是插件接收数据的入口,这是个工厂方法子类必须实现,解析和组装好js过来的api和参数后用反射的方式执行对应插件的exec:方法,该方法同步方式返回个bool值,表示调用成功或失败,如果失败则将失败结果返回给js,代码实现:

BOOL(*action)(id,SEL,id) = (BOOL(*)(id,SEL,id))objc_msgSend;
    BOOL ret=action(JavascriptInterfaceObject,sel,info);
    if (ret) {
        return YES;
    }
    return [self nativeCallBackWithCode:ret ? OK : ERROR value:ret ? @"OK" : error complete:YES callId:info.callId];
  1. exec:方法的形参是YNJsCallInfo对象,该对象携带的参数:
    action:api名,或叫动作标识字符串,各业务通过该字段判断该执行什么功能,如果插件内没有处理该api则返回调用失败的错误值false反之返回true。
    callId:api调用事件id或叫回传值队列id,当给js回传值时需要带上该值返回去。
    data:js给过来的参数值。
    callBack:block变量,结果回调入口,回传值时需要指定四个参数status、value、callId、complete,参数用处后面讲解。

  2. 功能实现完成后需要调用YNJsCallInfo对象的callBack回调方法,方法参数:
    status:结果状态值,此值为一个枚举类型,OK表示成功ERROR表示失败。
    value:结果值,该值最后在调用js回传值api时会转换为json字符串格式。
    callId:api调用事件id或叫回传值队列id。
    complete:bool值,当次api任务是否全部执行完毕,处理需要保活服务的长连接状态,false执行完毕,true服务需要继续保持。

  3. api调用完毕,需要给js回传值时,调用wk的- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler方法 执行这段js代码:window.nativeCallBack('%@',%ld,%@,%d),nativeCallBack()是js端接收原生端回传值的方法,接收四个参数,即为YNJsCallInfo对象的callBack回调参数。

  4. 原生功能通过插件的形式实现,要新增一个插件只需要: 第一步新建一个继承'YNPlugin'基础插件类的对象,然后在对象里实现方法-(BOOL)exec:(YNJsCallInfo*)arg; 第二步在YNBridgePlugPlist.plist文件里添加以下形式的代码

<dict>
        <key>namespace</key>
        <string>命名空间</string>
        <key>plug</key>nativeCallBack
        <string>插件类名</string>
</dict>

然后将命名空间名和相应的api名告诉js端即可

js端

调起一个原生插件时,执行YN对象里面的callNative: function (service,action,actionArgs,successCallback,failCallback)方法,方法参数:
service:原生api对应的命名空间名。
action:api名。
actionArgs:需要给原生端的参数。
successCallback:成功的回调。
failCallback:失败的回调。 比如我要调起原生端11命名空间下的jsCallTimer这个api,让原生端执行一个定时器功能,代码实现:

YN.callNative('11',"jsCallTimer",undefined,function (value) {
      if (a == 1){
        document.getElementById("progress1").innerText = value
      }else{
        document.getElementById("progress2").innerText = value
      }
    },function (error) {
      alert(error)
    })
  1. 执行YN.call()方法,实现调起原生和结果回调队列的维护,如果注入过安卓js对象‘window._anbridge’则执行_anbridge.call(api, arg)调起安卓端,如果注入过‘window._ynwk’值为true则执行prompt('_ynbridge=' + api, arg)调起iOS端,如果需要有回传值,则arg对象将给callId字段赋一个唯一值,并且在window.nativeCallBackIds缓存集合里新增callId值,值即为回调函数。

  2. 所有插件调用的前提基础是js端和原生端都已正常初始化,并且通讯已建立,即deviceReady已为ture,deviceReady的询问会在js入口函数里执行,即通过YN.call()方法,执行一个原生YNBase.init的api,如果结果返回为OK则为deviceReady成功

  3. 原生端插件执行结果回调通过‘nativeCallBack = function (callId,status,args,complete)’方法接收值,方法内部通过callId在window.nativeCallBackIds对象里找到回调方法然后执行,将args值由json字符串转json对象后传入,判断complete字段,为true则执行:delete window.nativeCallBackIds[callId]代码,将该服务回调移除队列。

native call js

js端

  1. 实现思路和设计方式同js call native,即只是其一个反向过程,实现基础依然是需要实现和注册基础插件类,各子插件继承基础插件,结果回调都是通过异步回传值,所以细节不做重复阐述。
  2. 在入口函数执行基础插件和各插件对象的注册,注册完成后可以调用原生YNBase.jsinit这个api告诉原生端,代码实现:
YN.register('asynObj',new YNPlugin());
   YN.register('YNPlugin1',new YNPlugin1());
  //告诉原生js初始化了,调原生初始化api(在js初始化前原生就要求执行的js方法可在jsinit方法里开始执行了)
  if (deviceReady){
    YN.call('YNBase.jsinit');
  }

register()方法内部实现同原生注册插件的形式,将插件和对应的命名空间添加进window.nativeNamespaceInterfaces集合。

  1. 接收原生端第一个信号由nativeCallJs = function(callId,service,action,actionArgs)方法接收,参数:
    callId:api调用事件id或叫回传值队列id。
    service:js api对应的命名空间名。
    action:api名。
    actionArgs:原生端的参数。 方法内部实现同原生插件调用,也是找到插件并执行插件方法exec(action,args,responseCallback)。

  2. 插件回传值结果和api调用结果通过调用原生的YNBase.returnValue这个api实现,即执行YN.call('YNBase.returnValue', value); value是参数对象,包含data、callId、complete、status四个字段,含义和用途同原生回调那里。

iOS端

  1. 调起一个js端的插件功能,执行wk对象的方法-(void)callHandler:(NSString*)server action:(NSString *)action arguments:(id)args completionHandler:(JSCallback)completionHandler;该方法逻辑同js call native时调用的YN.call()方法,通过维护一个callid服务队列来处理结果回传。

  2. 组装好参数后浏览器执行window.nativeCallJs('%@','%@','%@',%@)这个js代码即可调起js,代码示例:

[self evaluateJavaScript:[NSString stringWithFormat:@"window.nativeCallJs('%@','%@','%@',%@)",info.callId,info.service,info.action,[JSBUtil objToJsonString:info.args]]];

接收插件结果回传值在基础插件里监听returnValue这个api的执行,逻辑处理同js端nativeCallBack()方法。也是如果complete字段值为true时将该服务对象从队列里移除

❌
❌