IOS:Objective-C 和 JavaScript 的相互调用

iOS7以前,iOS SDK 并没有原生提供 js 调用 native 代码的 API。但是 UIWebView 的一个 delegate 方法使我们可以做到让 js 需要调用时,通知 native。在 native 执行完相应调用后,可以用stringByEvaluatingJavaScriptFromString 方法,将执行结果返回给 js。这样,就实现了 js 与 native 代码的相互调用。具体让 js 通知 native 的方法是让 js 发起一次特殊的网络请求。使用加载一个隐藏的 iframe 来实现的,通过将 iframe 的 src 指定为一个特殊的 URL,在Objective-C中通过UIWebView的webView:shouldStartLoadWithRequest:navigationType:方法拦截这个跳转,然后通过解析跳转的url获取js需要调用的方法名和参数。

Objective-C调用JavaScript

UIWebView有个方法是: - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script 可以直接调用js。例如你想获取页面document的clientHeight属性,这样写: NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.documentElement.clientHeight"]];

如果想调用页面的一个叫xxx的函数,则只需要 [webview stringByEvaluatingJavaScriptFromString:@"xxx()"]

这种调用有一个限制条件: JS代码占用的内存 < 10M。

Javascript调用Objective-C

iOS里面加载一个网页用的是UIWebView,页面加载是通过UIWebView的一个Delegate:UIWebViewDelegate来通知对应的webview的。而每次点击页面上的链接(或者是加载本页面的地址时) 都会在加载前调用UIWebViewDelegate的一个方法: - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 如果这个方法的返回值是YES的话就继续加载这个请求,如果是NO的话就不加载了。 Javascript调用Objective C代码的秘诀就在这里面。

第一步. 匹配url格式

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType: (UIWebViewNavigationType)navigationType
{
if (request.URL.absoluteString match urlSchemePattern) {
[self executeSomeObjectiveCCode];
return NO;
} else {
return YES;
}
}

request.URL.absoluteString match urlSchemePattern 这句的意思是: 如果页面的url格式满足某种特定格式, 就不加载那个请求,而是执行Objective-C代码。

第2步 协商url格式以及参数传递方式

Javascript想要调用Objective-C代码时,Javascript代码就需要和Objective-C协商一个请求的协议,例如:凡是请求的url scheme 是"js-call://" 这样格式开头的就是Javascript需要调用Objective C的代码,再具体点,比如"js-call://user/get" 就是要调用Objective-C 代码中一个getUser的方法的。 如果Javascript需要传递参数给Objective-C, 最简单的方法是像http的query string一样传参数, 例如:"js-call://user/set?uid=1&name=jpx",然后在分析url的时候将query string提取出来传给Objective -C的方法即可。 代码如下:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString hasPrefix:@"js-call://user/set"]) {
NSDictionary *parameters = [self parseQueryString:request.URL.absoluetString];
[self executeSomeObjectiveCCodeWithParameters:parameters];
return NO;
} else if ([request.URL.absoluteString hasPrefix:@"js-call://user/get"]) {
NSDictionary *parameters = [self parseQueryString:request.URL.absoluetString];
[self executeSomeObjectiveCCodeWithParameters:parameters];
return NO;
}
return YES;
}

如果Javascript需要调用好几个 Objective C的接口,那么在shouldStartLoadWithRequest的delegate方法里面就会有很多if ... else if分支代码, 此外,解析query string的那部分代码也是重复的,最好的办法是将这一切封装起来,可以定义一个JPXUIWebViewJSBridge方法。

 
self.bridge = [[JPXUIWebViewJSBridge alloc] initWithHandler:self];
self.bridge.routines = @[@[@"^js-call://user/set.*$", @"setUser"],
@[@"^js-call://user/get.*$", @"getUser"]
];

定义了这套规则之后,只需要比如说在ViewController里面实现一个叫setUser的方法即可: - (void)setUser:(NSDictionary *)parametersFromWeb , 其中parametersFromWeb就是query string对应的字典!

然后在 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 只需要这样写就可以了:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSError *error;
BOOL canHandleRequest = [self.bridge canHandleRequest:request error:&error];
if (canHandleRequest) {
[self.bridge handleRequest:request error:&error];
NSLog(@"error1:%@", [error localizedDescription]);
return NO;
} else {
NSLog(@"error2:%@", [error localizedDescription]);
}
return YES;
}

Javascript调用Objective C时,很多人第一反应就是在a标签里面的href写url调用,例如: <a href="js-call://user/set?uid=1&name=jpx" >测试</a>, 但是这样的调用会如下的一些问题:

如果我们连续 2 个 js 调 native,连续 2 次改 <a href> 的话,在 native 的 delegate 方法中,只能截获后面那次请求,前一次请求由于很快被替换掉,所以被忽略掉了。还有这种改url的方式也不太安全。

而合理的做法应该是通过加载一个iframe:

function execute(url)
{
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", url);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
从iOS7开始,我们可以使用JavaScriptCore框架来让我们的Objective-C代码和JavaScript进行深度交互,简单的说我们可以在Objective-C代码中访问JavaScript中的变量或调用JavaScript的函数,也可以JavaScript中使用Objective-C的对象和方法
 

同步和异步

因为 iOS SDK 没有天生支持 js 和 native 相互调用,大家的技术方案都是自己实现的一套调用机制,所以这里面有同步异步的问题。细心的同学就能发现,js 调用 native 是通过插入一个 iframe,这个 iframe 插入完了就完了,执行的结果需要 native 另外用 stringByEvaluatingJavaScriptFromString 方法通知 js,所以这是一个异步的调用。

而 stringByEvaluatingJavaScriptFromString 方法本身会直接返回一个 NSString 类型的执行结果,所以这显然是一个同步调用。

所以 js call native 是异步,native call js 是同步。在处理一些逻辑的时候,不可避免需要考虑这个特点。

方法。

Android:Java 和 JavaScript 相互调用

在Android 4.2之前可以使用addJavascriptInterface方式注入原生Java方法给JavaScript调用, 这种方案有一定的安全风险,在页面中执行一些不可信的Javascript代码即可能控制用户的手机,
因此在Android 4.2之后Android提供了@JavascriptInterface对象注入的方式建立Javascript对象和android原生对象的绑定,提供给javascript调用的函数必须带有@JavascriptInterface。本文以@JavascriptInterface为例,讲解一下Android:Java和JavaScript之间相互调用的方法。

第一步: 加载本地html文件

有的时候我们在使用webview开发的时候会使用本地的html文件,在这里为了方便我们把html文件都放在assets文件夹中,使用本地加载的方式,不需要server支持。
先定义一个html文件:

<!DOCTYPE html>
<html>
<body>
<h1>this is html</h1>
</body>
</html>

使用file:///android_asset/index.html加载到webview中:

    private void initView() {
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("file:///android_asset/index.html");
}

Javascript调用Java方法

以Android的Toast的为例,从Javascript代码中调用系统的Toast。
我们定义一个AndroidToast的Java类,它有一个show的方法用来显示Toast:

    public class AndroidToast {
@JavascriptInterface
public void show(String str) {
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
}
}

需要对WebView设置一些参数,开启JavaScipt,注册JavascriptInterface:

private void initView() {
webView = (WebView) findViewById(R.id.webView); WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDefaultTextEncodingName("UTF-8");
webView.addJavascriptInterface(new AndroidToast(), "AndroidToast");
webView.loadUrl("file:///android_asset/index.html");
}

addJavascriptInterface的作用是把AndroidToast类映射为Javascript中的AndroidToast对象。

在Javascript中调用Java代码:

function toastClick(){
window.AndroidToast.show('from js');
}

通过window的属性可以找到Java映射的对象AndroidToast,调用它的show方法。
注意这里传输的数据只能是基本数据类型和string,可以传输string意味着我们可以使用json传输结构化数据。

Javascript调用有返回值Java函数

如果想从Javascript调的方法里面获取到返回值,只需要定义一个带返回值的@JavascriptInterface方法:


public class AndroidMessage {
@JavascriptInterface
public String getMsg() {
return "form java";
}
}

添加Javascript的映射Webview:

webView.addJavascriptInterface(new AndroidMessage(), "AndroidMessage");

Javascript直接调用Java方法:

function showAlert(){
var str=window.AndroidMessage.getMsg();
console.log(str);
}

Java调用Javascript方法

Java在调用js的时候,使用的是WebView.loadUrl()方法,可以直接在HTML页面里面执行JavaScript方法,首先定义一个Javascript方法给Java调用:

Java调用有参数无返回值的js函数

function callFromJava(str){
console.log(str);
}

Java端调用Javascript方法:

public void  javaCallJS(){
webView.loadUrl("javascript:callFromJava('call from java')"); // 可以在loadUrl中直接给Javascript方法直接传值
}

调用js有参数有返回值的函数

Android在4.4之前并没有提供直接调用js函数并获取值的方法,所以在此之前,常用的思路是 java调用js方法,js方法执行完毕,再次调用java代码将值返回。

1.Java调用js代码

String call = "javascript:sumToJava(1,2)";
webView.loadUrl(call);

2.js函数处理,并将结果通过调用java方法返回

function sumToJava(number1, number2){
window.control.onSumResult(number1 + number2)
}

3.Java在回调方法中获取js函数返回值

@JavascriptInterface
public void onSumResult(int result) {
Log.i(LOGTAG, "onSumResult result=" + result);
}

Android 4.4处理

Android 4.4之后使用evaluateJavascript即可。这里展示一个简单的 具有返回值的js方法

function getGreetings() {
return 1;
}

java代码时用evaluateJavascript方法调用

private void testEvaluateJavascript(WebView webView) {
webView.evaluateJavascript("getGreetings()", new ValueCallback<String>() { @Override
public void onReceiveValue(String value) {
Log.i(LOGTAG, "onReceiveValue value=" + value);
}});
}

注意事项:

  • 上面限定了结果返回结果为String,对于简单的类型会尝试转换成字符串返回,对于复杂的数据类型,建议以字符串形式的json返回。
  • evaluateJavascript方法必须在UI线程(主线程)调用,因此onReceiveValue也执行在主线程。
 
 

Hybrid App开发模式中, IOS/Android 和 JavaScript相互调用方式的更多相关文章

  1. Hybrid App 开发模式

    开发移动App主要有三种模式:Native. Hybrid 和 Web App. 需要注意的一点是在选择开发模式的时候,要根据你的项目类型(图片类?视频类?新闻类?等),产品业务和人员技术储备等做权衡 ...

  2. Android和JavaScript相互调用的方法

    转载地址:http://www.jb51.net/article/77206.htm 这篇文章主要介绍了Android和JavaScript相互调用的方法,实例分析了Android的WebView执行 ...

  3. android与javascript相互调用

    下面这一节来介绍android和javascript是怎么相互调用的,这样我们的UI界面设计起来就简单多了,而且UI设计起来也可以跨平台.现在有好多web app前台框架了,比如sencha和jque ...

  4. Hybrid App—Hybrid App开发模式介绍和各种开发模式对比

    什么是Hybrid App 最开的App开发只有原生开发这个概念,但自从H5广泛流行后,一种效率更高的开发模式Hybrid应运而生,它就是"Hybrid模式".Hybrid APP ...

  5. IOS Object和javaScript相互调用

    在IOS开发中有时会用到Object和javaScript相互调用,详细过程例如以下: 1. Object中运行javascript代码,这个比較简单,苹果提供了非常好的方法 - (NSString ...

  6. Android Webview 和Javascript交互,实现Android和JavaScript相互调用

    在Android的开发过程中.遇到一个新需求.那就是让Java代码和Javascript代码进行交互.在IOS中实现起来很麻烦.而在Android中相对来说容易多了.Android对这种交互进行了很好 ...

  7. 【Hybrid App】Hybrid App开发 四大主流移平台分析

    转自http://dev.yesky.com/238/34657738.shtml Hybrid App在过去的两年中已经成为移动界的核心话题,但是作为一名Web开发者来说要如何站在移动互联网的浪潮之 ...

  8. Hybrid App 开发初探:使用 WebView 装载页面

    Hybrid App 是混合模式应用的简称,兼具 Native App 和 Web App 两种模式应用的优势,开发成本低,拥有 Web 技术跨平台特性.目前大家所知道的基于中间件的移动开发框架都是采 ...

  9. hybrid app开发中:苹果移动设备实用Meta标签

    hybrid app开发中:苹果移动设备实用Meta标签 “apple-mobile-web-app-status-bar-style”作用是控制状态栏显示样式 具体效果如下: status-bar- ...

随机推荐

  1. UIWebView加载html 图片大小自适应的方法汇总

    方法一 处理HTMLString的方法: NSString *htmls = [NSString stringWithFormat:@"<html> \n" " ...

  2. 让 Dreamweaver 支持 Emmet(原ZenCoding)

    注:目前暂不支持 DW CC,期待作者早是更新.Update:2013/10/12 鉴于某些原因,每个 Coder 所钟爱的 IDE 各不相同.而作为一个软件爱好者,我几乎所有 IDE 都使用过一段时 ...

  3. Android 之 资源文件的介绍及使用

    Android 之 资源文件的介绍及使用 1.资源的简单介绍:  在res文件夹中定义:字符串.颜色.数组.菜单.图片.视频等:在应用程序中使用这些资源.  2.使用资源的长处:降低代码量,同一时候为 ...

  4. 【ThinkingInC++】64、重载new和delete,来模仿内存的分配

    /** * 书本:[ThinkingInC++] * 功能:重载new和delete.来模仿内存的分配 * 时间:2014年10月5日14:30:11 * 作者:cutter_point */ #in ...

  5. CentOS7 安装chrome浏览器

    本篇文章主要记录如何在CentOS7.0上安装chrome. 1.配置yum下载源: 在目录 /etc/yum.repos.d/ 下新建文件 google-chrome.repo, 并且在该文件中添加 ...

  6. ZCTF-Restaurant-Pwn500

    版权声明:本文为博主原创文章,未经博主允许不得转载. 这道压轴的题也是名副其实,很有分量.这也是自己第二次做C++类型的PWN.含有两个漏洞,缺一不可,一个漏洞将指定位置覆盖为对象虚表的地址,另外一个 ...

  7. sqlserver获取当前id的前一条数据和后一条数据

    一.条件字段为数值的情况   select * from tb where id=@id; --当前记录   select top 1 * from tb where id>@id order  ...

  8. Ring对象

    Ring是一个封闭的Path即起始和终止点有相同的坐标值,它有内部和外部属性.

  9. angularjs使用ng-messages-include实例

    <!DOCTYPE html> <html lang="zh-CN" ng-app="app"> <head> <me ...

  10. javascript字符类型操作函数

    //获取字符串的长度 String.prototype.getByteLength = function() { var bytes=0,i=0; for (; i<this.length; + ...