zoukankan      html  css  js  c++  java
  • WebViewJavascriptBridge源码探究--看OC和JS交互过程

          今天把实现OC代码和JS代码交互的第三方库WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了stringByEvaluatingJavaScriptFromString函数

    。现在主要是了解js是如何调用oc方法的,分享下探究过程。

       源码不多,就一个头文件WebViewJavascriptBridge.h和实现文件WebViewJavascriptBridge.m, 和一个js文件,实现在js那边可以调用oc方法,也可以在oc里面调用js方法。

    先上图,实现简单的oc和js互相调用的demo, 另外附加一个模拟项目中用到的oc和js互相调用场景:

      

    一、然后说说js调用oc方法的原理,它们是如何实现的?库文件三个

    我们跟踪下oc控制器加载UIWebView的过程和js调用oc方法过程

    1、程序启动,在自定义控制器里,创建一个WebViewJavascriptBridge对象时,会加载WebViewJavascriptBridge.js.txt文件,里面是初始js代码

         在这个js里面,创建了一个WebViewJavascriptBridge脚本对象,另外创建一个隐藏的iframe标签:每次js调用oc方法,都是修改iframe标签的src来触发UIWebView的代理监听方法

    ;(function() {
        if (window.WebViewJavascriptBridge) { return }
        var messagingIframe
        var sendMessageQueue = []
        var receiveMessageQueue = []
        var messageHandlers = {}
        
        var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'
        var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'
        
        var responseCallbacks = {}
        var uniqueId = 1
        
        function _createQueueReadyIframe(doc) {
            messagingIframe = doc.createElement('iframe')
            messagingIframe.style.display = 'none'
            messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
            doc.documentElement.appendChild(messagingIframe)
        }
    
        function init(messageHandler) {
            if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice') }
            WebViewJavascriptBridge._messageHandler = messageHandler
            var receivedMessages = receiveMessageQueue
            receiveMessageQueue = null
            for (var i=0; i<receivedMessages.length; i++) {
                _dispatchMessageFromObjC(receivedMessages[i])
            }
        }
    
        function send(data, responseCallback) {
            _doSend({ data:data }, responseCallback)
        }
        
        function registerHandler(handlerName, handler) {
            messageHandlers[handlerName] = handler
        }
        
        function callHandler(handlerName, data, responseCallback) {
            _doSend({ handlerName:handlerName, data:data }, responseCallback)
        }
        
        function _doSend(message, responseCallback) {
            if (responseCallback) {
                var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
                responseCallbacks[callbackId] = responseCallback
                message['callbackId'] = callbackId
            }
            sendMessageQueue.push(message); //将字典放入数组
            //修改iframe标签的src属性,UIWebView监听执行代理方法
            messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
        }
    
        function _fetchQueue() {  //json数组转成json字符串
            var messageQueueString = JSON.stringify(sendMessageQueue)
            sendMessageQueue = []
            return messageQueueString
        }
    
        function _dispatchMessageFromObjC(messageJSON) {
            setTimeout(function _timeoutDispatchMessageFromObjC() {
                var message = JSON.parse(messageJSON)
                var messageHandler
                
                if (message.responseId) {
                    var responseCallback = responseCallbacks[message.responseId]
                    if (!responseCallback) { return; }
                    responseCallback(message.responseData)
                    delete responseCallbacks[message.responseId]
                } else {
                    var responseCallback
                    if (message.callbackId) {
                        var callbackResponseId = message.callbackId
                        responseCallback = function(responseData) {
                            _doSend({ responseId:callbackResponseId, responseData:responseData })
                        }
                    }
                    
                    var handler = WebViewJavascriptBridge._messageHandler
                    if (message.handlerName) {
                        handler = messageHandlers[message.handlerName]
                    }
                    
                    try {
                        handler(message.data, responseCallback)
                    } catch(exception) {
                        if (typeof console != 'undefined') {
                            console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
                        }
                    }
                }
            })
        }
        
        function _handleMessageFromObjC(messageJSON) {
            if (receiveMessageQueue) {
                receiveMessageQueue.push(messageJSON)
            } else {
                _dispatchMessageFromObjC(messageJSON)
            }
        }
    
        window.WebViewJavascriptBridge = {
            init: init,
            send: send,
            registerHandler: registerHandler,
            callHandler: callHandler,
            _fetchQueue: _fetchQueue,
            _handleMessageFromObjC: _handleMessageFromObjC
        }
    
        var doc = document
        _createQueueReadyIframe(doc)
        var readyEvent = doc.createEvent('Events')
        readyEvent.initEvent('WebViewJavascriptBridgeReady')
        readyEvent.bridge = WebViewJavascriptBridge
        doc.dispatchEvent(readyEvent)
    })();
    View Code

    2、UIWebView加载我们自定义的html页面TestJSBridge.html, 里面有脚本注册js调用oc方法标识,和oc调用js标识

    <html>
        <head>
            <meta charset="utf-8"/>
            <style type="text/css">
                html { font-family:Helvetica; color:#222; background:#D5FFFD; border: 5px dashed blue;}
                .rowH3{margin: 0px; text-align: center;}
                .jsBtn{font-size: 18px;}
            </style>
        </head>
        <body>
            <h3 class="rowH3">测试OC和JS互相调用</h3>
            <button class="jsBtn" id="jsBtn">JS调用OC方法</button>
            <div id="logDiv"><!-- 打印日志 --></div>
        </body>
    </html>
    <script type="text/javascript">
        window.onerror = function(err) {
            printLog(err);
        }
        
        function connectWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) {
                callback(WebViewJavascriptBridge);
            } else {
                document.addEventListener('WebViewJavascriptBridgeReady', function() {
                    callback(WebViewJavascriptBridge);
                }, false);
            }
        }
        
        var uniqueId = 1;
        //日志打印方法
        function printLog(data) {
            var logObj = document.getElementById('logDiv');
            var el = document.createElement('div');
            el.className = 'logLine';
            el.innerHTML = uniqueId++ + ': ' + JSON.stringify(data); //json转字符串
            if (logObj.children.length) { logObj.insertBefore(el, logObj.children[0]) }
            else { logObj.appendChild(el) }
        }
        
        //初始化调用函数connectWebViewJavascriptBridge
        connectWebViewJavascriptBridge(function(bridge) {
            
            bridge.init(function(message, responseCallback) {});  
                                       
            //注册js响应方法,响应OC调用,标识objc_Call_JS_Func
            bridge.registerHandler('objc_Call_JS_Func', function(data, responseCallback) {
                printLog(data);  //打印oc传过来的参数
            });
    
            //给标签按钮设置点击事件
            var callbackButton = document.getElementById('jsBtn');
            callbackButton.onclick = function(e) {
                e.preventDefault();
                //注册标识js_Call_Objc_Func,便于js给IOS发送消息
                bridge.callHandler('js_Call_Objc_Func', {id: 1, info: 'hello, iOS, 我从js那边过来!'},  function(response) { });
            }
        });
        
    </script>
    View Code

    3、点击html标签按钮,触发js事件

    //给标签按钮设置点击事件
    		var callbackButton = document.getElementById('jsBtn');
    		callbackButton.onclick = function(e) {
    			e.preventDefault();
                //注册标识js_Call_Objc_Func,便于js给IOS发送消息
    			bridge.callHandler('js_Call_Objc_Func', {id: 1, info: 'hello, iOS, 我从js那边过来!'},  function(response) { });
    		}
    

     我们跟踪bridge.callHandler方法,进入WebViewJavascriptBridge.js

      var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'

      var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'

        messagingIframe是个iframe标签,点击我们自定义html按钮标签,触发js事件,最后进入callHandler -->  _doSend ,

    当messagingIframe标签src重新赋值时,会触发UIWebView的代理方法(src的值一直是:wvjbscheme://__WVJB_QUEUE_MESSAGE__ ,也可自定义,这个在进入oc UIWebView代理方法时会用来作为判断标识)。

    跟踪后面执行的过程:

      

    至此,js调用oc成功

    总结js调用oc过程:

    -->   触发js事件

    -->   把要传入参数和自定义注册标识“js_Call_Objc_Func”存入js数组sendMessageQueue

     -->  重新赋值iframe标签的src属性,触发UIWebView代理方法, 根据src的值进入相应处理方法中

    -->   在oc方法里面调用js方法_fetchQueue, 获取js数组里面所有的参数  

    -->   根据传入的自定义注册标识 js_Call_Objc_Func  从oc字典_messageHandlers找出匹配block, 最后执行block,里面有我们自定义处理的后续代码

    二、oc调用js过程

    从oc内部发起

    -- > 调用bridge的callHandler方法,传入需要的参数和自定义注册标识

    --> 最后使用UIWebView系统方法stringByEvaluatingJavaScriptFromString调用js脚本WebViewJavascriptBridge._handleMessageFromObjC 完成参数的传递

      

    ---------------------------------------------  end --------------------------------------

    DEMO下载

    github地址:https://github.com/xiaotanit/Tan_WebViewJavaScriptBridge

    另外记录一个UIWebView不能加载带中文参数的url问题:

    假设加载url为:http://baidu.com/?search=博客园

    这样UIWebView加载这个带中文参数的url, 是不能显示的,需要把中文进行转义,才能显示。

    使用字符串方法stringByAddingPercentEncodingWithAllowedCharacters对中文进行转义

    NSString *str = @"http://baidu.com/?search=博客园";
      //  str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
      str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}"[]|\<> "].invertedSet];
        
    NSURL
    *url = [NSURL URLWithString:str];

      NSURLRequest *request = [NSURLRequest requestWithURL:url];

    另外记录一下:UIWebView不能监听加载的html页面a标签进行相对链接跳转。

    举例说明,比如加载的html页面有个a标签链接:

    <a href="/index.html">去首页</a>

    这种跳转UIWebViewDelegate的代理方法监听不到

    原文链接:http://www.cnblogs.com/tandaxia/p/5699886.html

  • 相关阅读:
    多测师讲解接口测试 _linux中搭建环境cms_高级讲师肖sir
    多测师讲解自动化测试 _RF数据库操作(上)_高级讲师肖sir
    多测师讲解自动化测试 _RF自定义关键字_高级讲师肖sir
    多测师讲解自动化--rf断言(下)--_高级讲师肖sir
    多测师讲解自动化测试 _RFalert弹框._高级讲师肖sir
    Tomcat 自定义默认网站目录
    .tomcat 管理 给tomcat设置用户名和密码登录
    tomcat访问管理页面出现:403 Access Denied 解决方法
    tomcat 日志目录 介绍
    部署 jdk
  • 原文地址:https://www.cnblogs.com/tandaxia/p/5699886.html
Copyright © 2011-2022 走看看