H5与原生交互的两种方式

之前做过一些 H5 和 iOS 原生交互的相关工作,在这里做下总结。

H5 和 iOS 原生交互现在主流的有两种方式:

  1. 通过 Webview 拦截 url 请求和 Webview 的执行 js 方法来实现
  2. 通过 JavaScriptCore 来实现

目前用的比较多的方式是第一种,一方面是因为 JavaScriptCore 是 iOS7 之后才出现的,另一方面也因为 WebViewJavascriptBridge 这个框架,它是采用第一种方式实现的,而且封装的也比较好。

拦截请求方式

原生调用 H5

原生调用 H5 比较简单,直接调用 UIWebView(WKWebView 同理)当中的

1
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

方法即可。同时 WKWebView 当中还增加了一个异步调用的方法

1
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;

H5 调用原生

H5 调用原生通过在 UIWebView(WKWebView 同理)的

1
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

方法中拦截请求的 url,通过 url 携带的信息调用相关原生方法。(实例代码来源:WebViewJavascriptBridge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
NSURL *url = [request URL];
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
if ([_base isCorrectProcotocolScheme:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}

以上代码通过 isCorrectProcotocolScheme:isBridgeLoadedURL:isQueueMessageURL:三个方法对拦截的 url 进行判断需要执行哪个原生的方法。

一些 H5 和原生的交互的比较简单的使用将参数拼在 url 中,在 url 拦截时将 url 截取获得相关参数。而 WebViewJavascriptBridge 则是将需要获取的数据放到 js 对象当中,让原生通过[self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]调用 js 去取到数据。

JavaScriptCore 方式

JavaScriptCore 是 iOS7 之后添加的库,这个库使 H5 和原生的交互变得比较简单。

原生调用 H5

1
2
3
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *textJS = @"showAlert('这里是JS中alert弹出的message')";
[context evaluateScript:textJS];

H5 调用原生

JavaScriptCore 方式 H5 和原生交互更像是将一个原生对象注册到 H5 的 js 当中,通过 js 来调用。

js 直接调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"test"] = ^() {
//此处获得方法传过来的参数,参数数量不固定,由js在调用时传递个数决定
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(@"%@",obj);
}
};
}

//我们模拟一下H5当中的js调用
NSString *jsStr1=@"test()";
[context evaluateScript:jsStr1];
NSString *jsStr2=@"test('参数1')";
[context evaluateScript:jsStr2];
NSString *jsStr3=@"test('参数A','参数B')";
[context evaluateScript:jsStr3];

js 通过对象调用方法,此时需要向 js 当中传递一个实现了 JSExport 协议的对象,通过该对象调用在协议中声明并实现的自定义方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
创建一个继承JSExport协议的协议,并添加自定义方法
@protocol TestProtocol <JSExport>
//无参数
-(void)test;
//一个参数
-(void)testOne:(NSString *)one;
//两个参数
-(void)testOne:(NSString *)one two:(NSString *)two;
@end

//在需要调用的对象当中实现这个协议
@interface TestJSObject : NSObject<TestProtocol>

@end

@implementation TestJSObject

-(void)test
{
NSLog(@"this is a test");
}

-(void)testOne:(NSString *)one
{
NSLog(@"this is a test,one=%@",one);
}
-(void)testOne:(NSString *)one two:(NSString *)two
{
NSLog(@"this is a test,one=%@,two=%@",one,two);
}

@end

//将实现TestProtocol协议的对象注册进js当中即可
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
TestJSObject *testJS=[[TestJSObject alloc] init];
context[@"testJS"]=testJS;
}

在 js 当中调用时需要注意当调用多个参数的方法时需要需要将方法名拼接起来调用,比如我们写的两个参数调用就需要写成 testJS.testOnetwo(‘参数 1’,’参数 2’),如下:

1
2
3
4
5
6
7
//我们模拟一下H5当中的js调用
NSString *jsStr1=@"testJS.test()";
[context evaluateScript:jsStr1];
NSString *jsStr2=@"testJS.testOne('参数1')";
[context evaluateScript:jsStr2];
NSString *jsStr3=@"testJS.testOnetwo('参数A','参数B')";
[context evaluateScript:jsStr3];