1. 程式人生 > >H5與Native交互之JSBridge技術

H5與Native交互之JSBridge技術

ren 編程 打開 信息 ray The 告訴 one link

一、原理篇

下面分別介紹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獲取某些數據的時候,可以調用這個方法。

參數類型是否必須示例值說明
datatype String userInfo 數據類型
callback Function 回調函數
extra Object 傳遞給Native APP的數據對象

示例代碼:

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

2.putData(datatype, data) H5告訴Native APP一些數據

使用場景:H5告訴Native APP一些數據,可以調用這個方法。

參數類型是否必須示例值說明
datatype String userInfo 數據類型
data Object { username: ‘zhangsan‘, age: 20 } 傳遞給Native APP的數據對象

示例代碼:

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

3.gotoWebview(url, page, data) Native APP新開一個Webview窗口,並打開相應網頁

參數類型是否必須示例值說明
url String http://www.youzan.com 網頁鏈接地址,一般都只要傳遞URL參數就可以了
page String web 網頁page類型,默認為web
data Object 額外參數對象

示例代碼:

// 示例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的某個原生界面

參數類型是否必須示例值說明
page String loginPage Native頁面標示符,例如loginPage
data Object { username: ‘zhangsan‘, age: 20 } 額外參數對象

示例代碼:

// 示例1:打開Native APP登錄頁面
JSBridge.gotoNative(‘loginPage‘);

// 示例2:打開Native APP登錄頁面,並且傳遞用戶名給Native APP
JSBridge.gotoNative(‘loginPage‘, {
    username: ‘張三‘
});

5.doAction(action, data) 功能上的一些操作

參數類型是否必須示例值說明
action String copy 操作功能類型,例如分享、復制
data Object { 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”選項,如下圖所示。
技術分享圖片
(2)打開真機或iPhone模擬器的調試模式,在真機或iPhone模擬器中打開設置界面,選擇“Safari”→“高級”→“Web檢查器”,選擇開啟即可,如下圖所示。
技術分享圖片
(3)將真機通過USB連上電腦,或者開啟模擬器,Safari的“Develop”菜單下便會多出相應的菜單項,如圖所示。

技術分享圖片

(4)Safari連接上UIWebView之後,我們就可以直接在Safari中直接修改HTML、CSS,以及調試Javascript。

技術分享圖片

四、參考鏈接

  • UIWebView Class Reference
  • WKWebView Class Reference
  • https://github.com/marcuswestin/WebViewJavascriptBridge

H5與Native交互之JSBridge技術