如何? javascript “同步”調用 app 代碼

來源:互聯網
上載者:User

標籤:

在 App 混合開發中,app 層向 js 層提供介面有兩種方式,一種是同步介面,一種一非同步介面(不清楚什麼是同步的請看這裡的討論)。為了保證 web 流暢,大部分時候,我們應該使用非同步介面,但是某些情況下,我們可能更需要同步介面。同步介面的好處在於,首先 js 可以通過傳回值得到執行結果;其次,在混合式開發中,app 層匯出的某些 api 按照語義就應該是同步的,否則會很奇怪——一個可能在 for 迴圈中使用的,執行非常快的介面,比如讀寫某個配置項,設計成非同步會很奇怪。

那麼如何向 js 層匯出同步介面呢?

我們知道,在 Android 架構中,通過 WebView.addJavascriptInterface() 這個函數,可以將 java 介面匯出到 js 層,並且這樣匯出的介面是同步介面。但是在 iOS 的 Cocoa 架構中,想匯出同步介面卻不容易,究其原因,是因為 UIWebView 和 WKWebView 沒有 addJavascriptInterface 這樣的功能。同時,Android 這個功能爆出過安全性漏洞,那麼,我們有沒有別的方式實現同步調用呢?我們以 iOS UIWebView 為例提供一種實現,WKWebView 和 Android 也可以參考。

為了找到問題的關鍵,我們看一下 iOS 中實現 js 調用 app 的通行方法:

首先,自訂 UIWebViewDelegate,在函數 shouldStartLoadWithRequest:navigationType: 中攔截請求。

- (BOOL) webView:(UIWebView* _Nonnull)webView    shouldStartLoadWithRequest:(NSURLRequest* _Nonnull)request                navigationType:(UIWebViewNavigationType)navigationType {    if ([request.HTTPMethod compare:@"GET" options:NSCaseInsensitiveSearch] != NSOrderedSame) {        // 不處理非 get 請求        return YES;    }        NSURL* url = request.URL;    if ([url.scheme isEqualToString:@‘YourCustomProtocol‘]) {        return [self onMyRequest:request];    }    return YES;}

這種做法實質上就是將函數調用命令轉化為 url,通過請求的方式通知 app 層,其中 onMyRequest: 是自訂的 request 響應函數。為了發送請求,js 層要建立一個隱藏的 iframe 元素,每次發送請求時修改 iframe 元素的 src 屬性,app 即可攔截到相應請求。

/** * js 向 native 傳遞訊息 * @method js_sendMessageToNativeAsync * @memberof JSToNativeIOSPolyfill * @public * @param str {String} 訊息字串,由 HybridMessage 轉換而來 */JSToNativeIOSPolyfill.prototype.js_sendMessageToNativeAsync = function (str) {if (!this.ifr_) {this._prepareIfr();}this.ifr_.src = ‘YourCustomProtocol://__message_send__?msg=‘ + encodeURIComponent(str); }

當 app 執行完 js 調用的功能,執行結果無法直接返回,為了返回結果,普遍採用回呼函數方式——js 層記錄一個 callback,app 通過 UIWebView 的 stringByEvaluatingJavaScriptFromString 函數調用這個 callback(類似 jsonp 的機制)。

注意,這樣封裝的介面,天然是非同步介面。因為 js_sendMessageToNativeAsync 這個函數會立即返回,不會等到執行結果發回來。

所以,我們要想辦法把 js 代碼“阻塞”住。

請回憶一下,js 中是用什麼方法能把 UI 線程代碼“阻塞”住,同時又不跑滿 CPU?

var async = false;var url = ‘http://baidu.com‘;var method = ‘GET‘;
var req = new XMLHttpRequest();
req.open(method, url, async);
req.send(null);

“同步”ajax(其實沒這個詞,ajax 內涵非同步意思)可以!在 baidu 的響應沒返回之前,這段代碼會一直阻塞。一般來說同步請求是不允許使用的,有導致 UI 卡頓的風險。但是在這裡因為我們並不會真的去遠端請求內容,所以不妨一用。

至此實現方式已經比較清楚了,梳理一下思路:

  1. 使用同步 XMLHttpRequest 配合特殊構造的 URL 通知 app層。
  2. app 層攔截請求執行功能,將結果作為 Response 返回。
  3. XMLHttpRequest.send() 返回,通過 status 和 responseText 得到結果。

那麼,如何攔截請求呢?大家知道,UIWebViewDelegate 是不會攔截 XMLHttpRequest 請求的,但是 iOS 至少給了我們兩個位置攔截這類請求——NSURLCache 和 NSURLProtocol。

一、NSURLCache 是 iOS 中用來實現自訂緩衝的類,當你建立了自訂的 NSURLCache 子類對象,並將其設定為全域緩衝管理器,所有的請求都會先到這裡檢查有無緩衝(如果你沒禁掉緩衝的話)。我們可以藉助這個性質攔截到介面調用請求,執行並返回資料。

- (NSCachedURLResponse*) cachedResponseForRequest:(NSURLRequest *)request {    if ([request.HTTPMethod compare:@"GET" options:NSCaseInsensitiveSearch] != NSOrderedSame) {        // 只對 get 請求做自訂處理        return [super cachedResponseForRequest:request];    }    NSURL* url = request.URL;    NSString* path = url.path;    NSString* query = url.query;    if (path == nil || query == nil) {        return [super cachedResponseForRequest:request];    }        LOGF(@"url = %@, path = %@, query = %@", url, path, query);    if ([path isEqualToString:@"__env_get__"]) {        // 讀環境變數        return [self getEnvValueByURL:url]; //*    } else if ([path isEqualToString:@"__env_set__"]) {        // 寫環境變數        return [self setEnvValueByURL:url];    }    return [super cachedResponseForRequest:request];}

注意注釋有 * 號的一行,即是執行 app 介面,返回結果。這裡的結果是一個 NSCachedResponse 對象,就不贅述了。

二、NSURLProtocol 是 Cocoa 中處理自訂 scheme 的類。這個類的使用更複雜一些,但它相比 NSURLCache 的好處是,可以使用自訂協議 scheme,防止 URL 和真實 URL 混淆,並且自訂 scheme 在非同步介面機制中也有使用,當你的 app 中同時存在兩種機制時,可以使用 scheme 使得代碼更清晰。

+ (BOOL) canInitWithRequest:(NSURLRequest* _Nonnull)request {    //只處理特定 scheme    NSString* scheme = [[request URL] scheme];    if ([scheme compare:@"YourCustomProtocol"] == NSOrderedSame) {        return YES;    }    return NO;}+ (NSURLRequest* _Nonnull) canonicalRequestForRequest:(NSURLRequest* _Nonnull)request {    return request;}- (BirdyURLProtocol* _Nonnull) initWithRequest:(NSURLRequest* _Nonnull)request                                cachedResponse:(NSCachedURLResponse* _Nullable)cachedResponse                                        client:(id<NSURLProtocolClient> _Nullable)client {    self = [super initWithRequest:request cachedResponse:cachedResponse client:client];    return self;}- (void) startLoading {    NSURLRequest* connectionRequest = [self.request copy];    NSCachedURLResponse* cachedResponse = [[YourURLCache sharedURLCache] cachedResponseForRequest:connectionRequest];        if (cachedResponse != nil) {        NSURLResponse* response = cachedResponse.response;        NSData* data = cachedResponse.data;        [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];        [[self client] URLProtocol:self didLoadData:data];        [[self client] URLProtocolDidFinishLoading:self];    } else {        NSError* error = [NSError errorWithDomain:@"Bad Hybrid Request"                                             code:400                                         userInfo:nil];        [[self client] URLProtocol:self didFailWithError:error];    }}

注意,以上代碼我借用了 YourURLCache 的實現,實際這是沒必要的。只是為了方便示範。

以上便是實現 javascript “同步”調用 app 代碼的方法,其核心就是使用同步 XMLHttpRequest 阻塞代碼,以及 app 層攔截請求。事實上,這個方法和作業系統以及開發架構無關,在 Android 系統中,也可以實現這樣的機制。

如何? javascript “同步”調用 app 代碼

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.