H5与Native交互之JSBridge手艺

做过夹杂开辟的很多人都晓得Ionic和PhoneGap之类的框架,这些框架在web基本上包了一层Native,然后经由过程Bridge手艺使得js能够挪用视频、位置、音频等功用。本文就是引见这层Bridge的交互道理,经由过程阅读本文你能够相识到js与ios及android底层的通信道理及JSBridge的封装手艺及调试要领。

一、道理篇

下面离别引见IOS和Android与Javascript的底层交互道理

IOS

在解说道理之前,起首来相识下iOS的UIWebView组件,先来看一下苹果官方的引见:

You can use the UIWebView class to embed web content in your application. To do so, you simply create a UIWebView object, attach it to a window, and send it a request to load web content. You can also use this class to move back and forward in the history of webpages, and you can even set some web content properties programmatically.

上面的意义是说UIWebView是一个可加载网页的对象,它有阅读纪录功用,且对加载的网页内容是可编程的。说白了UIWebView有相似阅读器的功用,我们运用能够它来翻开页面,并做一些定制化的功用,如能够让js调某个要领能够取到手机的GPS信息。

但须要注重的是,Safari阅读器运用的阅读器控件和UIwebView组件并非同一个,二者在机能上有很大的差别。荣幸的是,苹果宣布iOS8的时刻,新增了一个WKWebView组件,假如你的APP只斟酌支撑iOS8及以上版本,那末你就能够运用这个新的阅读器控件了。

原生的UIWebView类供应了下面一些属性和要领

属性:

  • loading:是不是处于加载中
  • canGoBack:A Boolean value indicating whether the receiver can move backward. (只读)
  • canGoForward:A Boolean value indicating whether the receiver can move forward. (只读)
  • request:The URL request identifying the location of the content to load. (read-only)

要领:

  • loadData:Sets the main page contents, MIME type, content encoding, and base URL.
  • loadRequest:加载收集内容
  • loadHTMLString:加载当地HTML文件
  • stopLoading:住手加载
  • goBack:退却
  • goForward:行进
  • reload:从新加载
  • stringByEvaluatingJavaScriptFromString:实行一段js剧本,而且返回实行效果

Native(Objective-C或Swift)挪用Javascript要领

Native挪用Javascript言语,是经由过程UIWebView组件的stringByEvaluatingJavaScriptFromString要领来完成的,该要领返回js剧本的实行效果。

// Swift
webview.stringByEvaluatingJavaScriptFromString("Math.random()")
// OC
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];

从上面代码能够看出它实在就是挪用了window下的一个对象,假如我们要让native来挪用我们js写的要领,那这个要领就要在window下能访问到。但从全局斟酌,我们只需暴露一个对象如JSBridge对native挪用就好了,所以在这里能够对native的代码做一个简朴的封装:

//下面为伪代码
webview.setDataToJs(somedata);
webview.setDataToJs = function(data) {
 webview.stringByEvaluatingJavaScriptFromString("JSBridge.trigger(event, data)")
}

Javascript挪用Native(Objective-C或Swift)要领

反过来,Javascript挪用Native,并没有现成的API能够直接拿来用,而是须要间接地经由过程一些要领来完成。UIWebView有个特征:在UIWebView内提议的一切收集要求,都能够经由过程delegate函数在Native层获得关照。如许,我们就能够在UIWebView内提议一个自定义的收集要求,通常是如许的花样:jsbridge://methodName?param1=value1&param2=value2

因而在UIWebView的delegate函数中,我们只需发现是jsbridge://开首的地点,就不举行内容的加载,转而实行响应的挪用逻辑。

提议如许一个收集要求有两种体式格局:1. 经由过程localtion.href;2. 经由过程iframe体式格局;
经由过程location.href有个题目,就是假如我们一连屡次修正window.location.href的值,在Native层只能吸收到最后一次要求,前面的要求都会被疏忽掉。

运用iframe体式格局,以唤起Native APP的分享组件为例,简朴的关闭以下:

var url = 'jsbridge://doAction?title=分享题目&desc=分享形貌&link=http%3A%2F%2Fwww.baidu.com';
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
    iframe.remove();
}, 100);

然后Webview就能够阻拦这个要求,而且剖析出响应的要领和参数。以下代码所示:

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        print("shouldStartLoadWithRequest")
        let url = request.URL
        let scheme = url?.scheme
        let method = url?.host
        let query = url?.query
        
        if url != nil && scheme == "jsbridge" {
            print("scheme == \(scheme)")
            print("method == \(method)")
            print("query == \(query)")

            switch method! {
                case "getData":
                    self.getData()
                case "putData":
                    self.putData()
                case "gotoWebview":
                    self.gotoWebview()
                case "gotoNative":
                    self.gotoNative()
                case "doAction":
                    self.doAction()
                case "configNative":
                    self.configNative()
                default:
                    print("default")
            }
    
            return false
        } else {
            return true
        }
    }

Android

在android中,native与js的通信体式格局与ios相似,ios中的经由过程schema体式格局在android中也是支撑的。

javascript挪用native体式格局

现在在android中有三种挪用native的体式格局:

1.经由过程schema体式格局,运用shouldOverrideUrlLoading要领对url协定举行剖析。这类js的挪用体式格局与ios的一样,运用iframe来挪用native代码。
2.经由过程在webview页面里直接注入原生js代码体式格局,运用addJavascriptInterface要领来完成。
在android里完成以下:

class JSInterface {
    @JavascriptInterface //注重这个代码一定要加上
    public String getUserData() {
        return "UserData";
    }
}
webView.addJavascriptInterface(new JSInterface(), "AndroidJS");

上面的代码就是在页面的window对象里注入了AndroidJS对象。在js里能够直接挪用

alert(AndroidJS.getUserData()) //UserDate

3.运用prompt,console.log,alert体式格局,这三个要领对js里是属性原生的,在android webview这一层是能够重写这三个要领的。平常我们运用prompt,由于这个在js里运用的不多,用来和native通信副作用比较少。

class YouzanWebChromeClient extends WebChromeClient {
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        // 这里就能够对js的prompt举行处置惩罚,经由过程result返回效果
    }
    @Override
    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {

    }
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

    }

}

Native挪用javascript体式格局

在android里是运用webview的loadUrl举行挪用的,如:

// 挪用js中的JSBridge.trigger要领
webView.loadUrl("javascript:JSBridge.trigger('webviewReady')");

二、库的封装

js挪用native的封装

上面我们相识了js与native通信的底层道理,所以我们能够封装一个基本的通信要领doCall来屏障android与ios的差别。

YouzanJsBridge = {
    doCall: function(functionName, data, callback) {
        var _this = this;
        // 处理一连挪用题目
        if (this.lastCallTime && (Date.now() - this.lastCallTime) < 100) {
            setTimeout(function() {
                _this.doCall(functionName, data, callback);
            }, 100);
            return;
        }
        this.lastCallTime = Date.now();
    
        data = data || {};
        if (callback) {
            $.extend(data, { callback: callback });
        }
    
        if (UA.isIOS()) {
            $.each(data, function(key, value) {
                if ($.isPlainObject(value) || $.isArray(value)) {
                    data[key] = JSON.stringify(value);
                }
            });
            var url = Args.addParameter('youzanjs://' + functionName, data);
            var iframe = document.createElement('iframe');
            iframe.style.width = '1px';
            iframe.style.height = '1px';
            iframe.style.display = 'none';
            iframe.src = url;
            document.body.appendChild(iframe);
            setTimeout(function() {
                iframe.remove();
            }, 100);
        } else if (UA.isAndroid()) {
            window.androidJS && window.androidJS[functionName] && window.androidJS[functionName](JSON.stringify(data));
        } else {
            console.error('未猎取platform信息,调取api失利');
        }
    }
}

上面android端我们运用了addJavascriptInterface要领来注入一个AndroidJS对象。

项目通用要领笼统

在项目的实践中,我们逐步笼统出一些通用的要领,这些要领基本上都是能够满足项目的需求。以下所示:

1.getData(datatype, callback, extra) H5从Native APP猎取数据

运用场景:H5须要从Native APP猎取某些数据的时刻,能够挪用这个要领。

参数范例是不是必需示例值申明
datatypeStringuserInfo数据范例
callbackFunction回调函数
extraObject通报给Native APP的数据对象

示例代码:

JSBridge.getData('userInfo',function(data) {
    console.log(data);
});

2.putData(datatype, data) H5通知Native APP一些数据

运用场景:H5通知Native APP一些数据,能够挪用这个要领。

参数范例是不是必需示例值申明
datatypeStringuserInfo数据范例
dataObject{ username: ‘zhangsan’, age: 20 }通报给Native APP的数据对象

示例代码:

JSBridge.putData('userInfo', {
    username: 'zhangsan',
    age: 20
});

3.gotoWebview(url, page, data) Native APP新开一个Webview窗口,并翻开响应网页

参数范例是不是必需示例值申明
urlString http://www.youzan.com网页链接地点,平常都只需通报URL参数就能够了
pageStringweb网页page范例,默以为web
dataObject分外参数对象

示例代码:

// 示例1:翻开一个网页
JSBridge.gotoWebview('http://www.youzan.com');

// 示例2:翻开一个网页,而且通报分外的参数给Native APP
JSBridge.gotoWebview('http://www.youzan.com', 'goodsDetail', {
    goods_id: 10000,
    title: '这是商品的题目',
    desc: '这是商品的形貌'
});

4.gotoNative(page, data) 从H5页面跳转到Native APP的某个原生界面

参数范例是不是必需示例值申明
pageStringloginPageNative页面标示符,比方loginPage
dataObject{ username: ‘zhangsan’, age: 20 }分外参数对象

示例代码:

// 示例1:翻开Native APP登录页面
JSBridge.gotoNative('loginPage');

// 示例2:翻开Native APP登录页面,而且通报用户名给Native APP
JSBridge.gotoNative('loginPage', {
    username: '张三'
});

5.doAction(action, data) 功用上的一些操纵

参数范例是不是必需示例值申明
actionStringcopy操纵功用范例,比方分享、复制
dataObject{ content: ‘这是要复制的内容’ }分外参数

示例代码:

// 示例1:挪用Native APP复制一段文本到剪切板
JSBridge.doAction('copy', {
    content: '这是要复制的内容'
});

// 示例2:挪用Native APP的分享组件,分享当前网页到微信
JSBridge.doAction('share', {
    title: '分享题目',
    desc: '分享形貌',
    link: 'http://www.youzan.com',
    imgs_url: 'http://wap.koudaitong.com/v2/common/url/create?type=homepage&index%2Findex=&kdt_id=63077&alias=63077'
});

三、调试篇

运用Safari举行UIWebView的调试

(1)起首须要翻开Safari的调试形式,在Safari的菜单中,挑选“Safari”→“Preference”→“Advanced”,勾选上“Show Develop menu in menu bar”选项,以下图所示。
《H5与Native交互之JSBridge手艺》
(2)翻开真机或iPhone模拟器的调试形式,在真机或iPhone模拟器中翻开设置界面,挑选“Safari”→“高等”→“Web搜检器”,挑选开启即可,以下图所示。
《H5与Native交互之JSBridge手艺》
(3)将真机经由过程USB连上电脑,或许开启模拟器,Safari的“Develop”菜单下便会多出响应的菜单项,如图所示。

《H5与Native交互之JSBridge手艺》

(4)Safari连接上UIWebView以后,我们就能够直接在Safari中直接修正HTML、CSS,以及调试Javascript。

《H5与Native交互之JSBridge手艺》

四、参考链接

本文由 @kk @劲风 配合创作,首发于有赞手艺博客: http://tech.youzan.com/jsbridge/

    原文作者:有赞前端
    原文地址: https://segmentfault.com/a/1190000010356403
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞