摘要:同時,這個功能爆出過安全漏洞,那么,我們有沒有別的方式實(shí)現(xiàn)同步調(diào)用呢我們以為例提供一種實(shí)現(xiàn),和也可以參考。一般來說同步請求是不允許使用的,有導(dǎo)致卡頓的風(fēng)險。總結(jié)以上便是實(shí)現(xiàn)同步調(diào)用代碼的方法,其核心就是使用同步阻塞代碼,以及層攔截請求。
緣起
在 App 混合開發(fā)中,app 層向 js 層提供接口有兩種方式,一種是同步接口,一種是異步接口(不清楚什么是同步的請看這里的討論)。為了保證 web 流暢,大部分時候,我們應(yīng)該使用異步接口,但是某些情況下,我們可能更需要同步接口。同步接口的好處在于,首先 js 可以通過返回值得到執(zhí)行結(jié)果;其次,在混合式開發(fā)中,app 層導(dǎo)出的某些 api 按照語義就應(yīng)該是同步的,否則會很奇怪——一個可能在 for 循環(huán)中使用的,執(zhí)行非常快的接口,比如讀寫某個配置項(xiàng),設(shè)計(jì)成異步會很奇怪。
那么如何向 js 層導(dǎo)出同步接口呢?
問題的核心我們知道,在 Android 框架中,通過 WebView.addJavascriptInterface() 這個函數(shù),可以將 java 接口導(dǎo)出到 js 層,并且這樣導(dǎo)出的接口是同步接口。但是在 iOS 的 Cocoa 框架中,想導(dǎo)出同步接口卻不容易,究其原因,是因?yàn)?UIWebView 和 WKWebView 沒有 addJavascriptInterface 這樣的功能。同時,Android 這個功能爆出過安全漏洞,那么,我們有沒有別的方式實(shí)現(xiàn)同步調(diào)用呢?我們以 iOS UIWebView 為例提供一種實(shí)現(xiàn),WKWebView 和 Android 也可以參考。
為了找到問題的關(guān)鍵,我們看一下 iOS 中實(shí)現(xiàn) js 調(diào)用 app 的通行方法:
首先,自定義 UIWebViewDelegate,在函數(shù) 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; }
這種做法實(shí)質(zhì)上就是將函數(shù)調(diào)用命令轉(zhuǎn)化為 url,通過請求的方式通知 app 層,其中 onMyRequest: 是自定義的 request 響應(yīng)函數(shù)。為了發(fā)送請求,js 層要建立一個隱藏的 iframe 元素,每次發(fā)送請求時修改 iframe 元素的 src 屬性,app 即可攔截到相應(yīng)請求。
/** ?* js 向 native 傳遞消息 ?* @method js_sendMessageToNativeAsync ?* @memberof JSToNativeIOSPolyfill ?* @public ?* @param str {String} 消息字符串,由 HybridMessage 轉(zhuǎn)換而來 ?*/ JSToNativeIOSPolyfill.prototype.js_sendMessageToNativeAsync = function (str) { ????if (!this.ifr_) { ????????this._prepareIfr(); ????} ? ????this.ifr_.src = "YourCustomProtocol://__message_send__?msg=" + encodeURIComponent(str); }
當(dāng) app 執(zhí)行完 js 調(diào)用的功能,執(zhí)行結(jié)果無法直接返回,為了返回結(jié)果,普遍采用回調(diào)函數(shù)方式——js 層記錄一個 callback,app 通過 UIWebView 的 stringByEvaluatingJavaScriptFromString 函數(shù)調(diào)用這個 callback(類似 jsonp 的機(jī)制)。
注意,這樣封裝的接口,天然是異步接口。因?yàn)?js_sendMessageToNativeAsync 這個函數(shù)會立即返回,不會等到執(zhí)行結(jié)果發(fā)回來。
所以,我們要想辦法把 js 代碼“阻塞”住。
同步 XMLHttpRequest請回憶一下,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(其實(shí)沒這個詞,ajax 內(nèi)涵異步的意思)可以!在 baidu 的響應(yīng)沒返回之前,這段代碼會一直阻塞。一般來說同步請求是不允許使用的,有導(dǎo)致 UI 卡頓的風(fēng)險。但是在這里因?yàn)槲覀儾⒉粫娴娜ミh(yuǎn)端請求內(nèi)容,所以不妨一用。
至此實(shí)現(xiàn)方式已經(jīng)比較清楚了,梳理一下思路:
使用同步 XMLHttpRequest 配合特殊構(gòu)造的 URL 通知 app層。
app 層攔截請求執(zhí)行功能,將結(jié)果作為 Response 返回。
XMLHttpRequest.send() 返回,通過 status 和 responseText 得到結(jié)果。
請求攔截那么,如何攔截請求呢?大家知道,UIWebViewDelegate 是不會攔截 XMLHttpRequest 請求的,但是 iOS 至少給了我們兩個位置攔截這類請求——NSURLCache 和 NSURLProtocol。
NSURLCache 是 iOS 中用來實(shí)現(xiàn)自定義緩存的類,當(dāng)你創(chuàng)建了自定義的 NSURLCache 子類對象,并將其設(shè)置為全局緩存管理器,所有的請求都會先到這里檢查有無緩存(如果你沒禁掉緩存的話)。我們可以借助這個性質(zhì)攔截到接口調(diào)用請求,執(zhí)行并返回?cái)?shù)據(jù)。
- (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__"]) { ????????// 讀環(huán)境變量 ????????return [self getEnvValueByURL:url]; //* ????} else if ([path isEqualToString:@"__env_set__"]) { ????????// 寫環(huán)境變量 ????????return [self setEnvValueByURL:url]; ????} ? ????return [super cachedResponseForRequest:request]; }
注意注釋有 * 號的一行,即是執(zhí)行 app 接口,返回結(jié)果。這里的結(jié)果是一個 NSCachedResponse 對象,就不贅述了。
NSURLProtocol 是 Cocoa 中處理自定義 scheme 的類。這個類的使用更復(fù)雜一些,但它相比 NSURLCache 的好處是,可以使用自定義協(xié)議 scheme,防止 URL 和真實(shí) URL 混淆,并且自定義 scheme 在異步接口機(jī)制中也有使用,當(dāng)你的 app 中同時存在兩種機(jī)制時,可以使用 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_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 的實(shí)現(xiàn),實(shí)際這是沒必要的。只是為了方便演示。
總結(jié)以上便是實(shí)現(xiàn) javascript “同步”調(diào)用 app 代碼的方法,其核心就是使用同步 XMLHttpRequest 阻塞代碼,以及 app 層攔截請求。事實(shí)上,這個方法和操作系統(tǒng)以及開發(fā)框架無關(guān),在 Android 系統(tǒng)中,也可以實(shí)現(xiàn)這樣的機(jī)制。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79688.html
摘要:小程序的視圖層目前使用作為渲染載體,而邏輯層是由獨(dú)立的作為運(yùn)行環(huán)境。比如小程序的,通信一次就像是寫情書所以,嚴(yán)格來說,小程序是微信定制的混合開發(fā)模式。出棧入棧解決小程序接口不支持的問題小程序的所有接口,都是通過傳統(tǒng)的回調(diào)函數(shù)形式來調(diào)用的。 作者:張利濤,視頻課程《微信小程序教學(xué)》、《基于Koa2搭建Node.js實(shí)戰(zhàn)項(xiàng)目教學(xué)》主編,滬江前端架構(gòu)師本文原創(chuàng),轉(zhuǎn)載請注明作者及出處 小程...
平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個網(wǎng)址:http://www.kancloud.cn/jsfron... 03月份前端資源分享 1. Javascript 175453545 Redux compose and middleware 源碼分析 深入 Promise(二)——進(jìn)擊的 Promise Effective JavaScript leeheys blog -...
平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個網(wǎng)址:http://www.kancloud.cn/jsfron... 03月份前端資源分享 1. Javascript 175453545 Redux compose and middleware 源碼分析 深入 Promise(二)——進(jìn)擊的 Promise Effective JavaScript leeheys blog -...
閱讀 2125·2019-08-29 16:53
閱讀 2700·2019-08-29 16:07
閱讀 2043·2019-08-29 13:13
閱讀 3268·2019-08-26 13:57
閱讀 1332·2019-08-26 13:31
閱讀 2434·2019-08-26 13:22
閱讀 1222·2019-08-26 11:43
閱讀 2085·2019-08-23 17:14