此篇文章的邏輯圖
圖0-0 此篇文章的邏輯圖
概述
iOS原生應用和web頁面的交互大致上有這幾種方法iOS7之後的JavaScriptCore、攔截協議、第三方框架WebViewJavaScriptBridge、iOS8之後的WKWebView在這裏主要講解JavaScriptCore和攔截協議這兩種辦法。WebViewJavaScriptBridge是基於攔截協議進行的封裝。學習成本相對JavaScriptCore較高,使用也不如JavaScriptCore方便本文不做敘述。WKWebView是iOS8之後推出的,還沒有成爲主流使用,所以本篇文章也不做詳細敘述。
Objective-C執行JavaScript代碼
相關方法
1
2
3
4
5
6
|
// UIWebView的方法
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
// JavaScriptCore中JSContext的方法
- (JSValue *)evaluateScript:(NSString *)script;
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL
|
相關應用
用這些方法去執行大段的JavaScript代碼是沒什麼必要的,但是有些小場景用起來還是比較順手和實用的,列舉兩個例子作爲參考:
1
2
3
4
5
|
// 獲取當前頁面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@
"document.title"
];
// 獲取當前頁面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@
"document.location.href"
];
|
JavaScriptCore
iOS7之後蘋果推出了JavaScriptCore這個框架,從而讓web頁面和本地原生應用交互起來非常方便,而且使用此框架可以做到Android那邊和iOS相對統一,web前端寫一套代碼就可以適配客戶端的兩個平臺,從而減少了web前端的工作量。
web前端
在三端交互中,web前端要強勢一些,一切傳值、方法命名都按web前端開發人員來定義,讓另外兩端去做適配。在這裏以調用攝像頭和分享爲例來詳細講解,測試網頁代碼取名爲test.html,其代碼內容如下:
test.html代碼內容(因識別問題,用方括號替換了代碼中的尖括號)
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
|
[!DOCTYPE html]
[html]
[head]
[meta charset=
"UTF-8"
]
[/head]
[body]
[div style=
"margin-top: 100px"
]
[h1>Objective-C和JavaScript交互的那些事[/h1]
[input type=
"button"
value=
"CallCamera"
onclick=
"Toyun.callCamera()"
]
[/div]
[div]
[input type=
"button"
value=
"Share"
onclick=
"callShare()"
]
[/div]
[script]
var
callShare =
function
() {
var
shareInfo = JSON.stringify({
"title"
:
"標題"
,
"desc"
:
"內容"
,
"shareUrl"
:
"http://www.jianshu.com/p/f896d73c670a"
,
"shareIco"
:
"http://upload-images.jianshu.io/upload_images/1192353-fd26211d54aea8a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
});
Toyun.share(shareInfo);
}
var
picCallback =
function
(photos) {
alert(photos);
}
var
shareCallback =
function
(){
alert(
'success'
);
}
[/script]
[/body]
[/html]
|
test.html代碼解釋
可能有些同學對web前端的一些知識不太熟悉,稍微對這段代碼做下解釋,先說Toyun是iOS和Android這兩邊在本地要注入的一個對象【參考下面iOS的代碼更容易明白】,充當原生應用和web頁面之間的一個橋樑。頁面上定義了兩個按鈕名字分別爲CallCamera和Share。點擊CallCamera會通過Toyun這個橋樑調用本地應用的方法- (void)callCamera,沒有傳參;而點擊Share會先調用本文件中的JavaScript方法callShare這裏將要分享的內容格式轉成JSON字符串格式(這樣做是爲了適配Android,iOS可以直接接受JSON對象)然後再通過Toyun這個橋樑去調用原生應用的- (void)share:(NSString *)shareInfo方法這個是有傳參的,參數爲shareInfo。而下面的兩個方法爲原生方法調用後的回調方法,其中picCallback爲獲取圖片成功的回調方法,並且傳回拿到的圖片photos;shareCallback爲分享成功的回調方法。
iOS
iOS這邊根據前端定義的方法名來寫代碼,但是有些時候web前端會讓我們定義,但是我們定義好之後他又要修改,這時候就會很煩啊。所以碰到三端交互的時候最好就是讓web前端去定義方法名,iOS和Android根據web前端定義好的去寫代碼。JavaScriptCore中web頁面調用原生應用的方法可以用Delegate或Block兩種方法,此文以按Delegate講解。
JavaScriptCore中類及協議:
JSContext:給JavaScript提供運行的上下文環境
JSValue:JavaScript和Objective-C數據和方法的橋樑
JSManagedValue:管理數據和方法的類
JSVirtualMachine:處理線程相關,使用較少
JSExport:這是一個協議,如果採用協議的方法交互,自己定義的協議必須遵守此協議
ViewController中的代碼
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
#import "ViewController.h"
#import [JavaScriptCore/JavaScriptCore.h](此處爲尖括號)
@protocol JSObjcDelegate [JSExport](此處爲尖括號)
- (void)callCamera;
- (void)share:(NSString *)shareString;
@end
@interface ViewController () [UIWebViewDelegate, JSObjcDelegate](此處爲尖括號)
@property (nonatomic, strong) JSContext *jsContext;
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end
@implementation ViewController
#pragma mark - Life Circle
- (void)viewDidLoad {
[
super
viewDidLoad];
NSURL *url = [[NSBundle mainBundle] URLForResource:@
"test"
withExtension:@
"html"
];
[self.webView loadRequest:[[NSURLRequest alloc] initWithURL:url]];
}
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
self.jsContext = [webView valueForKeyPath:@
"documentView.webView.mainFrame.javaScriptContext"
];
self.jsContext[@
"Toyun"
] = self;
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@
"異常信息:%@"
, exceptionValue);
};
}
#pragma mark - JSObjcDelegate
- (void)callCamera {
NSLog(@
"callCamera"
);
// 獲取到照片之後在回調js的方法picCallback把圖片傳出去
JSValue *picCallback = self.jsContext[@
"picCallback"
];
[picCallback callWithArguments:@[@
"photos"
]];
}
- (void)share:(NSString *)shareString {
NSLog(@
"share:%@"
, shareString);
// 分享成功回調js的方法shareCallback
JSValue *shareCallback = self.jsContext[@
"shareCallback"
];
[shareCallback callWithArguments:nil];
}
@end
|
ViewController中的代碼解釋
自定義JSObjcDelegate協議,而且此協議必須遵守JSExport這個協議,自定義協議中的方法就是暴露給web頁面的方法。在webView加載完畢的時候獲取JavaScript運行的上下文環境,然後再注入橋樑對象名爲Toyun,承載的對象爲self即爲此控制器,控制器遵守此自定義協議實現協議中對應的方法。在JavaStript調用完本地應用的方法做完相對應的事情之後,又回調了JavaStript中對應的方法,從而實現了web頁面和本地應用之間的通訊。
JavaScriptCore使用注意
JavaStript調用本地方法是在子線程中執行的,這裏要根據實際情況考慮線程之間的切換,而在回調JavaScript方法的時候最好是在剛開始調用此方法的線程中去執行那段JavaStript方法的代碼,我在實際運用中開始沒注意,就被坑慘了啊。什麼,說的太繞,看下面的代碼解釋:
1
2
3
4
5
6
7
8
9
|
// 假設此方法是在子線程中執行的,線程名sub-thread
- (void)callCamera {
// 這句假設要在主線程中執行,線程名main-thread
NSLog(@
"callCamera"
);
// 下面這兩句代碼最好還是要在子線程sub-thread中執行啊
JSValue *picCallback = self.jsContext[@
"picCallback"
];
[picCallback callWithArguments:@[@
"photos"
]];
}
|
運行效果
運行效果如圖3-1所示
圖3-1 運行效果
攔截協議
攔截協議這個適合一些比較簡單的一些情況,不需要引入什麼框架,只需要web前端配合一下就好。但是在具體調用哪一個方法上,以及在傳值的時候可能會有些不方便,而且調用完後無法在回調JavaScript的方法。
web前端
test.html中的代碼(因識別問題,用方括號替換了代碼中的尖括號)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
[!DOCTYPE html]
[html]
[head]
[meta charset=
"UTF-8"
]
[/head]
[body]
[div]
[input type=
"button"
value=
"CallCamera"
onclick=
"callCamera()"
]
[/div]
[script]
function
callCamera() {
}
[/script]
[/body]
[/html]
|
test.html中的代碼解釋
這段代碼相比上面的那段測試代碼是很簡單的,同樣有一個按鈕,名字爲CallCamera點擊之後調用自己的callCamera方法,window.location.href這裏是改變主窗口的指向從而馬上發出一個鏈接爲Toyun://callCamera請求,而想要傳給原生應用的參數也可已包含到此請求中,而在iOS方法中我們要攔截這個請求,根據請求內容去判斷JavaStript想要做的事情,從而實現web頁面和本地應用之間的交互。
iOS
iOS對應的代碼
1
2
3
4
5
6
7
8
9
10
|
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *url = request.URL.absoluteString;
// url的協議頭是Toyun
NSLog(@
"callCamera"
);
return
NO;
}
return
YES;
}
|
iOS對應的代碼的解釋
在webView的代理方法中去攔截自定義的協議Toyun://如果是此協議則據此判斷JavaStript想要做的事情,調用原生應用的方法,這些都是提前約定好的,同時阻止此鏈接的跳轉。
總結
隨着手機硬件的配置越來越強大和HTML5的興起,一個App完全可以由web頁面來寫。現在已經有部分應用這麼幹了,我是遇見過的,如古詩文網。儘管比較少但是web頁面和本地應用的交互不論是iOS還是Android都是會有遇到的。iOS我還是比較推薦JavaScriptCore,這樣三端可以相對統一起來,寫的時候都比較簡單。隨着時間的推移iOS8推出的WKWebView會逐漸成爲主流,這個的功能更強大。攔截協議也只能說用到比較簡單的一些情況吧,複雜的情況處理相互之間參數的傳遞還是比較麻煩的,而且這個不能回調JavaScript的方法,確實喜歡攔截協議的同學可以研究WebViewJavaScriptBridge這個第三方庫。對於Android本人也就是略知皮毛而已,就不班門弄斧了,對於一些Android開發者來說,可以看地第一段的test.html這個頁面的寫法完全是可以適配Android的。
參考
文章轉載自:http://www.cocoachina.com/ios/20160127/15105.html