如何实现 javascript “同步”调用 app 代码
在 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 卡顿的风险。但是在这里因为我们并不会真的去远端请求内容,所以不妨一用。
至此实现方式已经比较清楚了,梳理一下思路:
- 使用同步 XMLHttpRequest 配合特殊构造的 URL 通知 app层。
- app 层拦截请求执行功能,将结果作为 Response 返回。
- 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 代码的更多相关文章
- JAVAFX 2.0 javascript中调用java代码
现在你已经知道如何在JavaFX中调用JavaScript.在本章中,你将了解到相反的功能——在web页面中调用JavaFX. 大体上的理念是在JavaFX程序中创建一个接口对象,并通过调用JSObj ...
- ios中javascript直接调用oc代码而非通过改变url回调方式(转)
之前一个ios项目中,需要通过UIWebview来打开一个静态页面,并在静态页面中 调用相关object-c代码. 一.以前使用js调用object-c的方法 关于如何使用javascript调用ob ...
- [CefSharp] 如何在JavaScript中调用C#代码
本例在WinForms下实现,具体流程与WPF一致. 本例仅供调用示例,不代表正常业务书写流程. 1. 创建WinForms项目,并将项目属性设置为x86平台 此处预先设置,避免引用时报错,再花更多的 ...
- Hybrid App开发模式中, IOS/Android 和 JavaScript相互调用方式
IOS:Objective-C 和 JavaScript 的相互调用 iOS7以前,iOS SDK 并没有原生提供 js 调用 native 代码的 API.但是 UIWebView 的一个 dele ...
- JavaScript调用App原生代码(iOS、Android)通用解决方案
实际场景 场景:现在有一个H5活动页面,上面有一个登陆按钮,要求点击登陆按钮以后,唤出App内部的登录界面,当登录成功以后将用户的手机号返回给H5页面,显示出来.这个场景应该算是比较完整的一次H5中的 ...
- ASP.NET 前台Javascript调用后台代码 / 后台调用前台Javascript
1:ASP.NET 前台Javascript调用后台代码 1.1:前台Javascript <script> function CallCs() { var str = "< ...
- 一步一步学Silverlight 2系列(22):在Silverlight中如何用JavaScript调用.NET代码
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- Android和JavaScript相互调用的方法
转载地址:http://www.jb51.net/article/77206.htm 这篇文章主要介绍了Android和JavaScript相互调用的方法,实例分析了Android的WebView执行 ...
- android与javascript相互调用
下面这一节来介绍android和javascript是怎么相互调用的,这样我们的UI界面设计起来就简单多了,而且UI设计起来也可以跨平台.现在有好多web app前台框架了,比如sencha和jque ...
随机推荐
- MYSQL 导入Excel数据到数据库中
1,先把excel的数据整理整齐,如每列都要保持同样的格式:就一列一列的数据: 2,导出excel的数据为CSV格式,即把excel的数据另存为xxxx.csv;: 3,用EditPlus工具将xxx ...
- Ubuntu15.04安装不完全指南
0x00. 烧盘 使用UltraISO(破解版)烧录到U盘里,设置电脑从U盘启动,即可安装. 安装时可能出现not COM32R image的命令行,“boot:” 后面直接输入live即可解决问题. ...
- [django]django 在apache2上部署静态文件如何加载
首先找到apache2的conf文件下的httpd.conf,添加如下信息: Alias /static/ E:/wamp/Apache24/www/static/ <Directory E:/ ...
- HTML 学习笔记 (drag & drop)
拖放(Drag & Drop)是一种常见的特性,即抓取对象以后拖到另一个位置.在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放.过去,我们用监听鼠标的Mousedown.Mouseo ...
- 转 jQuery 中bind(),live(),delegate(),on() 区别
当我们试图绑定一些事件到DOM元素上的时候,我相信上面这4个方法是最常用的.而它们之间到底有什么不同呢?在什么场合下用什么方法是最有效的呢? 准备知识: 当我们在开始的时候,有些知识是必须具备的: D ...
- DeveloperExceptionPageMiddleware中间件如何呈现“开发者异常页面”
DeveloperExceptionPageMiddleware中间件如何呈现"开发者异常页面" 在<ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式&g ...
- [LeetCode] Construct Binary Tree from Inorder and Postorder Traversal 由中序和后序遍历建立二叉树
Given inorder and postorder traversal of a tree, construct the binary tree. Note: You may assume tha ...
- ASP.NET Core MVC 在linux上的创建及发布
前言 ASP.NET core转眼都发布半月多了,社区最近也是非常活跃,虽然最近从事python工作,但也一直对.NET念念不忘,看过了园区大神们搭建的Asp.net core项目之后,自己也是跃跃欲 ...
- Nginx配置Https
1.申请证书: https://console.qcloud.com/ssl?utm_source=yingyongbao&utm_medium=ssl&utm_campaign=qc ...
- express不是内部或外部命令
网上找了一下原因,是因为express在4.0以后把命令工具单独分出来了,所以安装完express后,还得再安装express-generator: 如果不是采用的全局安装,则需要把 "安装 ...