前言

在前端进行开发H5页面的时候,常常会有一些需要内嵌到APP的H5页面。在APP中使用H5页面主要是由于它是运行在浏览器上,所以只需要开发一次便可以在不同的操作系统上显示,并且迭代版本很方便。开发成本比较低。

在开发的过程中,经常需要和APP进行交互。本文已H5的角度整理了双方交互,如有不全面欢迎补充指正,大致将交互分为这两种情况:

  • 单向通信的交互
  • 不满足于单项传参需要return和callback的交互。

单向通信的交互

单向通信又分为不需要传参的通信以及需要传参的通信。

  1. 不需要传参的通信

    只是需要在原生和H5页面之间完成跳转页面的动作,不需要互相进行数据的传递:

    • 原生跳H5:将H5对应的URL给移动端同学即可
    • H5跳原生:location.herf = 要跳转的原生页面对应的协议名
    示例:
    // H5
    window.location = 'app://login';
    
    // 原生(以IOS端为例)
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {  
      
      // 一般用作交互的链接都会有一个固定的协议头,这里我们一“app”作为协议头为了,实际项目中可以修改
      if ([scheme isEqualToString:@"app"]) { // scheme为“app”说明是做交互的链接
          if ([host isEqualToString:@"login"]) { // host为“login”对应的就是登录操作
              NSDictionary *paramsDict = [request.URL getURLParams];
              UIAlertView *alert = [[UIAlertView alloc] 
              [alert show];
          }
      return YES;
    }
    复制代码
  2. 需要传参的通信 H5与APP需要进行单向的数据传递,这里介绍两种方式:

    • url传参:

    将参数携带在H5url后面或者app协议后面,对需要的参数进行拦截过滤便可在各自的开发环境中使用了

    H5向原生传参示例:
    // H5
    window.location = 'app://login?account=13011112222&password=123456'; //将参数拼接到协议后面
    
    // 原生(以IOS端为例)
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {  
      NSString *scheme = request.URL.scheme;
      NSString *host = request.URL.host;
      
      // 一般用作交互的链接都会有一个固定的协议头,这里我们一“app”作为协议头为了,实际项目中可以修改
      if ([scheme isEqualToString:@"app"]) { // scheme为“app”说明是做交互的链接
          if ([host isEqualToString:@"login"]) { // host为“login”对应的就是登录操作
              NSDictionary *paramsDict = [request.URL getURLParams];
              NSString *account = paramsDict[@"account"];
              NSString *password = paramsDict[@"password"];
              NSString *msg = [NSString stringWithFormat:@"执行登录操作,账号为:%@,密码为:%@", account, password];
              UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"原生弹窗" message:msg delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
              [alert show];
          }
      return YES;
    }
    复制代码
    • window传参:

    因为 app 是宿主,可以直接访问 h5,所以这种调用比较简单,就是在 h5 中曝露一些全局对象(包括方法),然后在原生 app 中调用这些对象。

    原生调用H5示例:
    // H5
    window.sdk= {
      double = value => value * 2
      trible = value => value * 3 
    } 
    
    // 原生(以IOS端为例)
    NSString *func = @"window.sdk.double(10)";
    NSString *str = [webview stringByEvaluatingJavaScriptFromString:func];// 20
    复制代码
    H5调用原生示例:
    // 原生(以IOS端为例)
    @interface AppSdk : NSObject
    {}
    - (int) double:(int)value;
    - (int) triple:(int)value;
    @end
    @implementation AppSdk
    - (int) double:(int)value {
      return value * 2;
    }
    - (int) triple:(int)value {
      return value * 3;
    }
    @end
    JSContext *context=[webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    AppSdk *appSdk = [AppSdk new];
    context[@"appSdk"] = appSdk;
    
    H5:
    
    window.appSdk.double(10); // 20
    复制代码

双向通信的交互

上面说的单向通信的交互通常会使一个简单的方法变得非常割裂,在一些稍微复杂的场景之下,双端的维护成本很高,因此通常我们使用双向通信的时候更多一些。我们可以在回调函数中做很多事情。这里就要提到大家耳熟能详的WebViewJavaScriptBridge:

  • WebViewJavaScriptBridge基本原理
  • WebViewJavaScriptBridge准备工作
  • WebViewJavaScriptBridge原生调用H5
  • WebViewJavaScriptBridgeH5调用原生

基本原理

WebViewJavaScriptBridge的基本原理简单来说就是,建立一个桥梁,然后注册自己,调用他人。

把 OC 的方法注册到桥梁中,让 JS 去调用。

把 JS 的方法注册在桥梁中,让 OC 去调用。

准备工作

  • js初始化:

    将下面这两段函数贴进js中

    function setupWebViewJavascriptBridge(callback) {
       if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
       if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
       window.WVJBCallbacks = [callback]; // 创建一个 WVJBCallbacks 全局属性数组,并将 callback 插入到数组中。
       var WVJBIframe = document.createElement('iframe'); // 创建一个 iframe 元素
       WVJBIframe.style.display = 'none'; // 不显示
       WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; // 设置 iframe 的 src 属性
       document.documentElement.appendChild(WVJBIframe); // 把 iframe 添加到当前文导航上。
       setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
    }
    
    // 这里主要是注册 OC 将要调用的 JS 方法。下面具体的交互操作会提到
    setupWebViewJavascriptBridge(function(bridge){
      
    });
    复制代码

原生调用H5

原生要调用H5的js需要两个步骤。首先js要注入一个方法a到桥梁中去。第二步当然就是在原生那边去调用桥梁中的方法a了。

  1. 往桥梁中注入js的a方法

    在上面准备工作的时候,我们提到了,js中的第二段函数是要注册OC将要调用的JS方法的,具体的操作如下:

    // 往桥梁中注入js方法a
    setupWebViewJavascriptBridge(function(bridge){
       // 声明 OC 需要调用的 JS 方法。
       bridge.registerHanlder('a',function(data,responseCallback){
           // data 是 OC 传递过来的数据.
           // responseCallback 是调用完毕之后传递给 OC 的数据
           alert("JS 被 OC 调用了.");
           responseCallback({jsdata: "js 的数据",from : "JS"});
       })
    }); 
    });
    复制代码
  2. OC调用桥梁中的a方法

    [_jsBridge callHandler:@"a" data:@"传递给 JS 的参数" responseCallback:^(id responseData) {
       NSLog(@"JS 的返回值: %@",responseData);
    }];
    复制代码

H5调用原生

H5调用原生同样是两个步骤。注册自己,调用他人。

  1. 往桥梁中注入OC的b方法

    [_jsBridge registerHandler:@"b" handler:^(id data, WVJBResponseCallback responseCallback) {
       // data 是 JS 传递过来的数据.
       // responseCallback 是调用完毕之后传递给 js 的数据
       responseCallback(@"传给js的值");
    }];
    复制代码
  2. JS调用桥梁中的b方法

    WebViewJavascriptBridge.callHandler('b',{data : "传给 OC 的入参"},function(dataFromOC){
           alert("JS 调用了 OC 的方法");
           alert('调用结束后OC返回给JS的数据:', dataFromOC);
    });
    复制代码

调用方式

  1. OC调JS的三种方式

        // 单纯的调用 JSFunction,不往 JS 传递参数,也不需要 JSFunction 的返回值。
        [_jsBridge callHandler:@"a"];
        // 调用 JSFunction,并向 JS 传递参数,但不需要 JSFunciton 的返回值。
        [_jsBridge callHandler:@"a" data:@"传给js的入参"];
        // 调用 JSFunction ,并向 JS 传递参数,也需要 JSFunction 的返回值。
        [_jsBridge callHandler:@"a" data:@"传递给 JS 的参数" responseCallback:^(id responseData) {
            NSLog(@"JS 的返回值: %@",responseData);
        }];
    复制代码
  2. JS调OC的三种方式

         // JS 单纯的调用 OC 的 block
        WebViewJavascriptBridge.callHandler('b');
    
        // JS 调用 OC 的 block,并传递 JS 参数
        WebViewJavascriptBridge.callHandler('b',"JS 参数");
    
        // JS 调用 OC 的 block,传递 JS 参数,并接受 OC 的返回值。
        WebViewJavascriptBridge.callHandler('b',{data : "这是 JS 传递到 OC 的数据"},function(dataFromOC){
                alert("JS 调用了 OC 的方法!");
                document.getElementById("returnValue").value = dataFromOC;
        });
    复制代码

以上。