Hybrid App: 看看第三方WebViewJavascriptBridge是如何来实现Native和JavaScript交互

一、简介

在前面两篇文章中已经介绍了Native与JavaScript交互的几种方式,依次是JavaScriptCore框架、UI组件UIWebView、WebKit框架,这几种方式都是苹果公司提供的,除了UIWebView在IOS8之后被苹果淘汰了外,其他基本都能很好地满足开发者的使用。作为一个技术人员,每个人心里都有造轮子的想法,可能有时觉得原生的API使用起来感觉还是不够方便,就对苹果原生的API再进行一层封装,这不WebViewJavascriptBridge这个轮子出来了。WebViewJavascriptBridge的star和fork量还是比较高的,仔细看看WebViewJavascriptBridge类文件相当简单,使用起来也很方便,很受开发者欢迎,它的原理还是利用WKWebView或者UIWebView的相关API,通过bridge桥梁来实现OC与JS互相注册和调用。大致结构图如下:

可以看出:OC调用JS,JS需要注册函数; JS调用OC,OC需要注册函数。


 

二、分析

了解基本原理结构图后,再来看看框架中的类以及它们的作用定义,如下:

WebViewJavascriptBridge_JS:Javascript环境的Bridge初始化和处理。负责接收OC发给Javascript的消息,并且把Javascript环境的消息发送给OC。

WKWebViewJavascriptBridge/WebViewJavascriptBridge:主要负责OC环境的消息处理,并且把OC环境的消息发送给Javascript环境。

WebViewJavascriptBridgeBase:主要实现了OC环境的Bridge初始化和处理。

//初始化桥接器
+ (instancetype)bridgeForWebView:(id)webView;
+ (instancetype)bridge:(id)webView;

//设置日志相关
+ (void)enableLogging;
+ (void)setLogMaxLength:(int)length;

//注册函数, handlerName: 函数名称  handler:传递数据的block
- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
- (void)removeHandler:(NSString*)handlerName;

//调用函数, handlerName:函数名称 data:参数 responseCallback:接收数据的block 
- (void)callHandler:(NSString*)handlerName;
- (void)callHandler:(NSString*)handlerName data:(id)data;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;

//设置网页代理
- (void)setWebViewDelegate:(id)webViewDelegate;

//禁用超时安全弹框
- (void)disableJavscriptAlertBoxSafetyTimeout;

调用相关知识点 :

// OC 调用 JS
// 1、单纯的调用 JSFunction。 [self.jsBridge callHandler:@"JSFunction"]; // 2、调用 JSFunction,并传递给js需要的参数,但不需要 JSFunciton 的返回值。 [self.jsBridge callHandler:@"JSFunction" data:"arg of js"]; // 3、调用 JSFunction ,并传递给js需要的参数,也需要 JSFunction 的返回值。 [self.jsBridge callHandler:@"JSFunction" data:"arg of js" responseCallback:^(id responseData) { NSLog(@"%@",responseData); }]; // ------------------------------------------------------------------------------- // //JS 调用 OC // 1、JS 单纯的调用 OC 的 OCMethod WebViewJavascriptBridge.callHandler('OCMethod'); // 2、JS 调用 OC 的 OCMethod,并传递给OC需要的参数 WebViewJavascriptBridge.callHandler('OCMethod',"arg of oc"); // 3、JS 调用 OC 的 OCMethod,传递给OC需要的参数,并接受OC的返回值。 WebViewJavascriptBridge.callHandler('OCMethod', "arg of oc", function(responseValue){ alert(responseValue);
});

 

三、核心

使用该框架,还需要完成某些初始化的工作,也即在HTML或者JavaScript文件中,拷贝进官方指定的函数,在函数内进行初始化操作:

//固定格式的函数
function setupWebViewJavascriptBridge(callback) {
    if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    window.WVJBCallbacks = [callback];
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'https://__bridge_loaded__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

//官方说这里主要进行初始化,有两个功能:1、进行JS函数的注册提供OC调用(必须在此实现)   2、JS调用OC在端上注册的函数(不用在此处实现)
//这里需要说明一下: 至于第2点如果放在这里会立即执行且执行一次。可以挪到某一个js事件中执行,例如按钮事件等,可以频繁触发,后面的使用会有demo演示
setupWebViewJavascriptBridge(function(bridge) {
    
    /* Initialize your app here */

    bridge.registerHandler('JavaScript_functionName', function(data, responseCallback) {
        responseCallback(data); //传递数据给OC
    })


    bridge.callHandler('OC_methodName', function responseCallback(responseData) {
        alert(responseData); //接收OC的数据
    })
})    

 

四、使用

Example introduce:  页面有一个原生按钮和H5按钮,点击原生按钮调用JS,切换H5的div背景色;点击H5按钮,调用OC方法,切换原生按钮背景颜色。

完整代码:

//
//  ViewController.m
//  WebViewJavaScriptBridge
//
//  Created by 夏远全 on 2019/11/17.
//  Copyright © 2019 Beijing Huayue Education Technology Co., Ltd. All rights reserved.
//

#import "ViewController.h"
#import "WebViewJavascriptBridge/WebViewJavascriptBridge.h"

@interface ViewController ()
@property (nonatomic, strong) WebViewJavascriptBridge *bridge;
@property (nonatomic, strong) UIButton  *topButton;
@property (nonatomic, strong) WKWebView *wkWebView;
@end

@implementation ViewController

#pragma mark - init

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建topView
    CGFloat width = [UIScreen mainScreen].bounds.size.width;
    self.topButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, width, 200)];
    self.topButton.titleLabel.numberOfLines = 0;
    self.topButton.backgroundColor = [UIColor blueColor];
    self.topButton.titleLabel.font = [UIFont systemFontOfSize:17];
    [self.topButton setTitle:@"我是TopButton\n点击我调用JS方法,切换div盒子的背景色" forState:UIControlStateNormal];
    [self.topButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [self.topButton addTarget:self action:@selector(topButtonAction) forControlEvents:UIControlEventTouchUpInside];
    

    //创建wkWebView
    CGFloat height = [UIScreen mainScreen].bounds.size.height-CGRectGetHeight(self.topButton.frame);
    self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.topButton.frame), width, height)];
    
    //添加wkWebView视图
    [self.view addSubview:self.topButton];
    [self.view addSubview:self.wkWebView];
    
    //为wkWebView创建桥接器
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.wkWebView];
    
    //OC注册方法,提供给JavaScript调用,并给JavaScript传递数据
    [self.bridge registerHandler:@"updateTopButtonBgColor" handler:^(id data, WVJBResponseCallback responseCallback) {
        UIColor *randomColor = [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1.0];
        self.topButton.backgroundColor = randomColor;
        [self showAlertView:@"JavaScript调用OC ----- success! "];
        responseCallback(@"JavaScript调用OC ----- success! "); //可以回调给JavaScript一个结果
    }];
    
    //加载资源
    NSString *file = [[NSBundle mainBundle] pathForResource:@"example" ofType:@"html"];
    NSString *html = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    [self.wkWebView loadHTMLString:html baseURL:nil];
}

#pragma mark - remove
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.bridge removeHandler:@"updateTopButtonBgColor"];
}

#pragma mark -action
-(void)topButtonAction {
    
    //调用JavaScript函数,传递颜色参数,并接收JavaScript回传的数据
    NSArray *colors = [NSArray arrayWithObjects:@"orange",@"green",@"red",@"blue",nil];
    [self.bridge callHandler:@"updateDivBgColor" data:colors[arc4random_uniform(4)] responseCallback:^(id responseData) {
        [self showAlertView:responseData];
    }];
}

#pragma mark - method
-(void)showAlertView:(NSString *)message {
    UIAlertController *aletVc = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleDefault handler:nil];
    [aletVc addAction:action];
    [self presentViewController:aletVc animated:YES completion:nil];
}


@end
View Code

细分步骤:

1、创建HTML,完成初始工作

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  
  <style>
      .divcss{ background:#F00; color:#FFF; width:300px; height:200px}
  </style>
  
  <script type="text/javascript">
      
    function button_onclick(){
        //JavaScript调用OC注册的方法,并接收OC的数据
        WebViewJavascriptBridge.callHandler('updateTopButtonBgColor',function(responseValue) {
            alert(responseValue);
        });
    }

      // 固定函数 
    function setupWebViewJavascriptBridge(callback) {
        if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
        if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
        window.WVJBCallbacks = [callback];
        var WVJBIframe = document.createElement('iframe');
        WVJBIframe.style.display = 'none';
        WVJBIframe.src = 'https://__bridge_loaded__';
        document.documentElement.appendChild(WVJBIframe);
        setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
    }
    
    // 初始化
    setupWebViewJavascriptBridge(function(bridge){
       
         //JavaScript调用OC注册的方法,并接收OC的数据,页面启动后会立即调用且调用一次,我把它放到了上面的👆button_onclick()事件中可以频繁执行
         //WebViewJavascriptBridge.callHandler('changeTopButtonBgColor',function(responseValue) {
             //alert(responseValue);
         //});
          
         //JavaScript注册函数,提供给OC调用,并传递数据给OC
         bridge.registerHandler('updateDivBgColor',function(colorData, responseCallback) {
             alert(colorData);
             document.getElementById("div").style.backgroundColor = colorData;
             responseCallback("OC调用JavaScript ----- success! "); 
         });
    });

  </script>
  
</head>

<body>
    <div class="divcss" id="div">
        <br>
        <h2 style="text-align:center"> div盒子 </h2>
    </div>
    
    <br>
    <button onclick="button_onclick()">点击调用OC方法,切换TopButton的背景色</button>
    
</body>

</html>

2、导入头文件,定义控件属性

//头文件
#import "ViewController.h"
#import "WebViewJavascriptBridge/WebViewJavascriptBridge.h"

//定义属性
@interface ViewController ()
@property (nonatomic, strong) WebViewJavascriptBridge *bridge;
@property (nonatomic, strong) UIButton  *topButton;
@property (nonatomic, strong) WKWebView *wkWebView;
@end 

3、创建控件,并添加到父视图

//创建topView
CGFloat width = [UIScreen mainScreen].bounds.size.width;
self.topButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, width, 200)];
self.topButton.titleLabel.numberOfLines = 0;
self.topButton.backgroundColor = [UIColor blueColor];
self.topButton.titleLabel.font = [UIFont systemFontOfSize:17];
[self.topButton setTitle:@"我是TopButton\n点击我调用JS方法,切换div盒子的背景色" forState:UIControlStateNormal];
[self.topButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[self.topButton addTarget:self action:@selector(topButton) forControlEvents:UIControlEventTouchUpInside];
    
//创建wkWebView
CGFloat height = [UIScreen mainScreen].bounds.size.height-CGRectGetHeight(self.topButton.frame);
self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.topButton.frame), width, height)];

//添加wkWebView视图
[self.view addSubview:self.topButton];
[self.view addSubview:self.wkWebView];

4、创建桥接器

//为wkWebView创建桥接器
self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.wkWebView]; 

5、JavaScript调用OC: 需要OC注册方法

//OC注册方法,提供给JavaScript调用,并给JavaScript传递数据
[self.bridge registerHandler:@"changeTopButtonBgColor" handler:^(id data, WVJBResponseCallback responseCallback) {
    UIColor *randomColor = [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1.0];
    self.topButton.backgroundColor = randomColor;
    [self showAlertView:@"JavaScript调用OC ----- success! "];
    responseCallback(@"JavaScript调用OC ----- success! "); //可以回调给JavaScript一个结果
}];

6、OC调用JavaScript:需要在HTML中注册JS函数

//原生按钮事件
-(void)topButtonAction {
    //调用JavaScript函数,传递颜色参数,并接收JavaScript回传的数据
    NSArray *colors = [NSArray arrayWithObjects:@"orange",@"green",@"red",@"blue",nil];
    [self.bridge callHandler:@"updateDivBgColor" data:colors[arc4random_uniform(4)] responseCallback:^(id responseData) {
        [self showAlertView:responseData];
    }];
}

7、加载HTML资源

//加载资源
NSString *file = [[NSBundle mainBundle] pathForResource:@"example" ofType:@"html"];
NSString *html = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
[self.wkWebView loadHTMLString:html baseURL:nil];

8、清除工作

//需要清除注册到桥接器中的函数,否则导致vc无法释放
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.bridge removeHandler:@"updateTopButtonBgColor"];
}

9、显示结果如下:可以发现TopButton和div的背景色都能频繁改变

 

posted @ 2019-11-18 00:47  XYQ全哥  阅读(706)  评论(0编辑  收藏  举报