H5 與App的相互調(diào)用/傳值
h5 與 App 交互有以下幾種方式:
方法一:URL 傳參
App 通過 URL 傳遞參數(shù)邻奠,例如: https://xxxx?id=123
绰精。
這種方式有很大的局限辩尊,比如:
- URL 的長度有最大限制:在傳遞參數(shù)的時(shí)候洋只,需要保證 URL 的長度不能超過最大限制唠倦。
- 考慮編碼:在傳遞參數(shù)的時(shí)候厂镇,需要考慮編碼的問題,例如在傳遞中文時(shí)需要對參數(shù)進(jìn)行 URL 編碼
- 單向:只適合 App 向 H5 傳值
- 不能定制:App 只能將 H5 所有可能需要用到的參數(shù)傳遞過去雅镊,不能按需傳遞
方法二:攔截 URL Schemes
H5 和 App 首先約定一個(gè)特定的 URL Schemes,然后 App 將 H5 中約定的 URL Schemes 進(jìn)行攔截刃滓。
例如:約定的 URL Schemes 為 zoneyet
仁烹,當(dāng) H5 向 App 傳值時(shí),H5 向 zoneyet://jumpToHomePage?id=123
地址進(jìn)行跳轉(zhuǎn)咧虎,App 將 URL Schemes 為 zoneyet
的地址進(jìn)行攔截卓缰,然后分析其中的 URL 和傳遞的參數(shù)。
可以實(shí)現(xiàn)的功能:
- H5 向 App 傳遞參數(shù)砰诵,App 接收后進(jìn)行處理或頁面跳轉(zhuǎn)
前提條件:
- App 和 H5 需要提前約定好 URL Schemes征唬,URL 地址,參數(shù)名稱茁彭,然后 App 才能對指定的 URL Schemes 進(jìn)行攔截
局限:只能單向傳值总寒,即只能 H5 向 App 傳遞參數(shù)。所以一般用于 H5 控制 App 的跳轉(zhuǎn)
方法三:JavaScriptBridge
使用 WebViewJavaScriptBridge(Start 13.5K), DSBridge(Android(Start 2.9K)理肺,iOS(Start 1.5K) ) 可以實(shí)現(xiàn) H5 與 App 的雙向調(diào)用和雙向傳值摄闸。
可以實(shí)現(xiàn)下面的功能:
- H5 調(diào)用 App 的方法,可以用于實(shí)現(xiàn):
- H5 向 App 傳遞參數(shù)
- H5 控制 App 從一個(gè)頁面跳轉(zhuǎn)到另一個(gè)頁面
- App 調(diào)用 H5 的方法妹萨,可以用于實(shí)現(xiàn):
- App 修改 H5 的參數(shù)
- App 控制 H5 的邏輯
- H5 調(diào)用 App 的方法年枕,App 處理結(jié)束后,將結(jié)果傳遞給 H5乎完,可以用于實(shí)現(xiàn):
- H5 將復(fù)雜的運(yùn)算交給 App 進(jìn)行處理熏兄,然后將處理結(jié)果傳遞給 H5
- H5 將網(wǎng)絡(luò)請求的參數(shù)交給 App,然后將請求結(jié)果傳遞給 H5
前提條件:
- 在使用 WebViewJavaScriptBridge 時(shí)树姨,H5 與 App 需要提前約定好相互調(diào)用的方法名稱(和參數(shù)名稱)摩桶,然后才可以相互調(diào)用。
實(shí)現(xiàn)原理:
(1)App 調(diào)用 H5 的方法時(shí)帽揪,首先將傳遞的數(shù)據(jù)轉(zhuǎn)換成一個(gè)字符串 messageJSON
典格,
// message 為 App 向 H5 傳遞的一個(gè)字典數(shù)據(jù)
- (NSString *)_serializeMessage:(id)message pretty:(BOOL)pretty{
return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:message options:(NSJSONWritingOptions)(pretty ? NSJSONWritingPrettyPrinted : 0) error:nil] encoding:NSUTF8StringEncoding];
}
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
然后在 WebView 執(zhí)行這段字符串
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
(2)H5 調(diào)用 App 的方法的實(shí)現(xiàn)原理是:在 WKWebView 跳轉(zhuǎn)代理中,判斷是不是特定的 url(https://__wvjb_queue_message__
)台丛,如果是的話耍缴,將其攔截砾肺,然后從特定的地方獲取 H5 調(diào)用的方法名和參數(shù),再交給 App 進(jìn)行處理防嗡。
// WKWebView 的代理
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
[self WKFlushMessageQueue];
} else {
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
// *** 刪除了非核心代碼 **
}
// _base 中判斷是否攔截 url 的方法
- (BOOL)isWebViewJavascriptBridgeURL:(NSURL*)url {
if (![self isSchemeMatch:url]) {
return NO;
}
return [self isBridgeLoadedURL:url] || [self isQueueMessageURL:url];
}
- (BOOL)isSchemeMatch:(NSURL*)url {
NSString* scheme = url.scheme.lowercaseString;
return [scheme isEqualToString:kNewProtocolScheme] || [scheme isEqualToString:kOldProtocolScheme];
}
- (BOOL)isQueueMessageURL:(NSURL*)url {
NSString* host = url.host.lowercaseString;
return [self isSchemeMatch:url] && [host isEqualToString:kQueueHasMessage];
}
- (BOOL)isBridgeLoadedURL:(NSURL*)url {
NSString* host = url.host.lowercaseString;
return [self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded];
}
方法四:JSCore
在iOS 7之后变汪,蘋果將 JSCore 作為一個(gè)系統(tǒng)級 Framework 提供給開發(fā)者∫铣茫可以通過 JSCore 傳遞參數(shù)裙盾。可以通過 深入理解JSCore 了解技術(shù)原理他嫡。
實(shí)現(xiàn)方法:
H5:
function scan() {
// 判斷iOS 番官、Android
const isAndroid = navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Linux') > -1;
const isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
// 傳遞的數(shù)據(jù)
data = {'id' : 123}
if (isAndroid) {
// 'scan'為標(biāo)志符 app定義
window.WebViewJavascriptBridge.callHandler('scan', data,
function(responseData) {});
} else if (isIOS) {
// iOS 如果不需要傳值 則data要傳null
window.webkit.messageHandlers.scan.postMessage(data)
}
}
iOS 定義 JSCoderViewController 并實(shí)現(xiàn) WKScriptMessageHandler 協(xié)議:
enum ScriptMessageName {
case scan //
var name: String {
switch self {
case .scan:
return "scan"
}
}
}
class JSCoderViewController: UIViewController {
// 定義 WKWebView
lazy var webview: WKWebView = {
let configuration = WKWebViewConfiguration()
let userContentController = WKUserContentController()
userContentController.add(self, name: ScriptMessageName.scan.name)
configuration.userContentController = userContentController
return WKWebView(frame: .zero, configuration: configuration)
}()
// ....
}
extension JSCoderViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case ScriptMessageName.scan.name:
// 參數(shù)從 message.body 中獲取
default:
debugPrint("undefined message name:\(message.name)")
}
}
}
提升 H5 的加載速度
方法一:對 H5 中的靜態(tài)資源進(jìn)行緩存
在用戶打開 H5 頁面之后,App 對 H5 中需要加載的靜態(tài)資源進(jìn)行緩存钢属,比如:對 js徘熔、css、image 資源進(jìn)行緩存淆党。
實(shí)現(xiàn)方案有兩種:
- App 利用 WebView 控件的緩存策略酷师,對 H5 中的資源進(jìn)行緩存,下次打開的時(shí)候染乌,從緩存中讀取數(shù)據(jù)山孔。
- App 攔截 WebView 的網(wǎng)絡(luò)請求,自己實(shí)現(xiàn)一套緩存機(jī)制
緩存策略為:App 將資源的 url 地址作為資源的唯一標(biāo)識荷憋,對資源進(jìn)行緩存台颠。當(dāng) HTML 中引用的資源發(fā)生變動時(shí),需要保證在 HTML 中的引用地址需要發(fā)生變化勒庄,比如 url 變更或者 url 中的參數(shù)變更蓉媳。App 發(fā)現(xiàn)本地沒有對應(yīng)的資源時(shí),需要再次緩存锅铅。
方法二: 預(yù)加載
對用戶將來可能會訪問到的 H5 頁面酪呻,App 可以對 H5 中的資源進(jìn)行預(yù)加載和緩存,當(dāng)用戶訪問 H5 頁面的時(shí)候盐须,App 從本地加載 HTML 和相關(guān)資源的數(shù)據(jù)玩荠,然后進(jìn)行顯示,從而提高打開速度贼邓。
實(shí)現(xiàn)步驟:
- App 提前請求 H5 頁面數(shù)據(jù)阶冈,獲取對應(yīng)的 HTML 文件,然后提取 HTML 中的靜態(tài)資源(js塑径,css, image)女坑,然后進(jìn)行緩存。
- 當(dāng) WebView 加載 H5 時(shí)统舀,App 攔截資源的請求匆骗,從本地進(jìn)行加載
方法三:資源的打包下載
將用戶將來會訪問的 H5 資源進(jìn)行打包(打包為 .zip 文件)劳景,實(shí)現(xiàn)版本控制,App 對資源進(jìn)行下載碉就、解壓盟广,放在緩存目錄當(dāng)中。當(dāng)用戶訪問時(shí)瓮钥,從本地加載對應(yīng)的資源筋量。當(dāng) H5 資源發(fā)生變更后,App 根據(jù)版本實(shí)現(xiàn)增量更新碉熄。
實(shí)現(xiàn)步驟:
- 將 H5 資源進(jìn)行整體打包桨武,實(shí)現(xiàn)版本控制
- App 啟動后,查看是否有需要下載的 H5 資源锈津,
- 如果有需要下載的資源呀酸,則首先查看 App 當(dāng)前是否有緩存對應(yīng)的資源,
- 如果沒有緩存一姿,請求資源時(shí),不攜帶緩存版本號跃惫,服務(wù)器返回全量更新的資源和對應(yīng)的版本號叮叹,App 緩存進(jìn)行
- 如果有緩存,請求資源時(shí)爆存,攜帶緩存的版本號蛉顽,服務(wù)器返回增量更新的資源,App 對之前緩存的資源進(jìn)行增加先较、替換携冤,并保存當(dāng)前版本號、
方法四:動態(tài)資源的處理
對 H5 中需要使用的動態(tài)數(shù)據(jù)闲勺,比如:H5 中的列表數(shù)據(jù)曾棕,可以使用下面的方法進(jìn)行加速:
- H5 將通過網(wǎng)絡(luò)請求到的動態(tài)數(shù)據(jù)傳遞給 App,App 進(jìn)行緩存處理菜循。下次打開 H5 時(shí)就可以從 App 的緩存中讀取數(shù)據(jù)
- H5 將網(wǎng)絡(luò)請求的交給 App翘地,App 請求結(jié)束后,將結(jié)果返回給 H5癌幕。
原生應(yīng)用中動態(tài)頁面
在運(yùn)營過程中衙耕,App 的一些活動頁面需要在不發(fā)新版本的情況下,實(shí)現(xiàn)動態(tài)變動勺远,比如:動態(tài)的彈窗頁面橙喘,活動提醒頁面。
為了實(shí)現(xiàn)這種需求胶逢,可以通過下面的方式進(jìn)行實(shí)現(xiàn):
通過 WebView 加載動態(tài)鏈接
動態(tài)的內(nèi)容可以通過 WebView 進(jìn)行動態(tài)展示厅瞎。
實(shí)現(xiàn)方案:具體的展示效果通過 H5 完成饰潜,App 通過 WebView 加載動態(tài)的 URL 進(jìn)行展示。
預(yù)設(shè)模板
在 App 中預(yù)設(shè)幾種動態(tài)模板磁奖,在需要暫時(shí)動態(tài)內(nèi)容時(shí)囊拜,從預(yù)設(shè)模板中選擇一個(gè)模板,然后設(shè)置相關(guān)數(shù)據(jù)比搭。
限制:必須先在 App 中完成可能會用到的模板冠跷,然后從中選擇相應(yīng)的模板,進(jìn)行展示身诺。
富文本
富文本中可以加載圖片蜜托,文字,按鈕霉赡,可以通過富文本來定制動態(tài)的 UI橄务。
可以使用的富文本框架 YYKit
HTML 轉(zhuǎn)原生頁面
動態(tài)頁面的樣式由 HTML 設(shè)計(jì)完成,在需要展示動態(tài)的頁面時(shí)穴亏,App 從 H5 或者服務(wù)獲取 HTML蜂挪、css 數(shù)據(jù),App 將 HTML 數(shù)據(jù)翻譯成原生 UI 進(jìn)行展示嗓化。
實(shí)現(xiàn)步驟:
- App 將 HTML 文本翻譯成 dom 樹
- App 通過 CSS 設(shè)置 dom 樹的屬性
- App 通過 dom 樹的信息構(gòu)建原生 UI棠涮,然后進(jìn)行展示
可以使用的布局框架 Texture(原名:AsyncDisplayKit)
JSON 配置原生頁面
使用 JSON 來描述 UI 的樣式,在需要展示動態(tài)的頁面時(shí)刺覆,App 從 H5 或者服務(wù)獲取 JSON 數(shù)據(jù)严肪,App 將 JSON 數(shù)據(jù)翻譯成原生 UI 進(jìn)行展示。
實(shí)現(xiàn)步驟:
- 通過約定好的 JSON 格式谦屑,來配置需要展示的 UI
- App 通過解析 JSON 格式驳糯,來繪制原生的 UI