H5與App的交互

H5 與App的相互調(diào)用/傳值

h5 與 App 交互有以下幾種方式:

方法一:URL 傳參

App 通過 URL 傳遞參數(shù)邻奠,例如: https://xxxx?id=123 绰精。

這種方式有很大的局限辩尊,比如:

  1. URL 的長度有最大限制:在傳遞參數(shù)的時(shí)候洋只,需要保證 URL 的長度不能超過最大限制唠倦。
  2. 考慮編碼:在傳遞參數(shù)的時(shí)候厂镇,需要考慮編碼的問題,例如在傳遞中文時(shí)需要對參數(shù)進(jìn)行 URL 編碼
  3. 單向:只適合 App 向 H5 傳值
  4. 不能定制: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)的功能:

  1. H5 向 App 傳遞參數(shù)砰诵,App 接收后進(jìn)行處理或頁面跳轉(zhuǎn)

前提條件:

  1. 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)下面的功能:

  1. H5 調(diào)用 App 的方法,可以用于實(shí)現(xiàn):
    1. H5 向 App 傳遞參數(shù)
    2. H5 控制 App 從一個(gè)頁面跳轉(zhuǎn)到另一個(gè)頁面
  2. App 調(diào)用 H5 的方法妹萨,可以用于實(shí)現(xiàn):
    1. App 修改 H5 的參數(shù)
    2. App 控制 H5 的邏輯
  3. H5 調(diào)用 App 的方法年枕,App 處理結(jié)束后,將結(jié)果傳遞給 H5乎完,可以用于實(shí)現(xiàn):
    1. H5 將復(fù)雜的運(yùn)算交給 App 進(jìn)行處理熏兄,然后將處理結(jié)果傳遞給 H5
    2. H5 將網(wǎng)絡(luò)請求的參數(shù)交給 App,然后將請求結(jié)果傳遞給 H5

前提條件:

  1. 在使用 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)方案有兩種:

  1. App 利用 WebView 控件的緩存策略酷师,對 H5 中的資源進(jìn)行緩存,下次打開的時(shí)候染乌,從緩存中讀取數(shù)據(jù)山孔。
  2. 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)步驟:

  1. App 提前請求 H5 頁面數(shù)據(jù)阶冈,獲取對應(yīng)的 HTML 文件,然后提取 HTML 中的靜態(tài)資源(js塑径,css, image)女坑,然后進(jìn)行緩存。
  2. 當(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)步驟:

  1. 將 H5 資源進(jìn)行整體打包桨武,實(shí)現(xiàn)版本控制
  2. App 啟動后,查看是否有需要下載的 H5 資源锈津,
  3. 如果有需要下載的資源呀酸,則首先查看 App 當(dāng)前是否有緩存對應(yīng)的資源,
    1. 如果沒有緩存一姿,請求資源時(shí),不攜帶緩存版本號跃惫,服務(wù)器返回全量更新的資源和對應(yīng)的版本號叮叹,App 緩存進(jìn)行
    2. 如果有緩存,請求資源時(shí)爆存,攜帶緩存的版本號蛉顽,服務(wù)器返回增量更新的資源,App 對之前緩存的資源進(jìn)行增加先较、替換携冤,并保存當(dāng)前版本號、

方法四:動態(tài)資源的處理

對 H5 中需要使用的動態(tài)數(shù)據(jù)闲勺,比如:H5 中的列表數(shù)據(jù)曾棕,可以使用下面的方法進(jìn)行加速:

  1. H5 將通過網(wǎng)絡(luò)請求到的動態(tài)數(shù)據(jù)傳遞給 App,App 進(jìn)行緩存處理菜循。下次打開 H5 時(shí)就可以從 App 的緩存中讀取數(shù)據(jù)
  2. 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)步驟:

  1. App 將 HTML 文本翻譯成 dom 樹
  2. App 通過 CSS 設(shè)置 dom 樹的屬性
  3. 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)步驟:

  1. 通過約定好的 JSON 格式谦屑,來配置需要展示的 UI
  2. App 通過解析 JSON 格式驳糯,來繪制原生的 UI
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市氢橙,隨后出現(xiàn)的幾起案子酝枢,更是在濱河造成了極大的恐慌,老刑警劉巖悍手,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隧枫,死亡現(xiàn)場離奇詭異,居然都是意外死亡谓苟,警方通過查閱死者的電腦和手機(jī)官脓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涝焙,“玉大人卑笨,你說我怎么就攤上這事÷刈玻” “怎么了赤兴?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵妖滔,是天一觀的道長。 經(jīng)常有香客問我桶良,道長座舍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任陨帆,我火速辦了婚禮曲秉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疲牵。我一直安慰自己承二,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布纲爸。 她就那樣靜靜地躺著亥鸠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪识啦。 梳的紋絲不亂的頭發(fā)上负蚊,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音颓哮,去河邊找鬼家妆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛题翻,可吹牛的內(nèi)容都是我干的揩徊。 我是一名探鬼主播腰鬼,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼嵌赠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了熄赡?” 一聲冷哼從身側(cè)響起姜挺,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎彼硫,沒想到半個(gè)月后炊豪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拧篮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年词渤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片串绩。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缺虐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出礁凡,到底是詐尸還是另有隱情高氮,我是刑警寧澤慧妄,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站剪芍,受9級特大地震影響塞淹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罪裹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一饱普、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坊谁,春花似錦费彼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鬓椭,卻和暖如春颠猴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背小染。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工翘瓮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裤翩。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓资盅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親踊赠。 傳聞我的和親對象是個(gè)殘疾皇子呵扛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容