抖音 JSBridge(舊版,現在統一使用via)
一、JSBridge是什么
- 主流App開發模式
- Native App:傳統原生APP開發模式。Android基於Java語言,底層調用Google的API,iOS基於OC或者Swift語言,底層調用ios官方提供的API。
- Web App:網站開發模式。將頁面部署在服務器上,用戶使用瀏覽器訪問,一般泛指 SPA(Single Page Application)模式開發出的網站。
- Hybrid App:半Native半web混合開發模式。介於Web App、Native App兩者之間,兼具Native良好交互體驗和Web頁跨平台開發優勢。
- React Native App:用JS寫出的原生應用。
2.Native vs Web
|
Native
|
Web
|
|
|
優點
|
可以調用系統底層API 交互順暢,轉場平滑 數據安全,穩定
|
跨多平台,表現力強 迭代快 即時上線,無需發版
|
|
缺點
|
任何改動需要發版(安卓熱更新除外) 兩端各一套實現方式,無法跨平台開發
|
無法調用系統底層API 性能取決於容器 安全性穩定性相對較弱
|
|
Native App
|
Web App
|
Hybrid App
|
React Native App
|
|
|
原生功能體驗
|
優秀
|
差
|
良好
|
接近優秀
|
|
渲染性能
|
非常快
|
慢
|
接近快
|
快
|
|
是否支持設備底層訪問
|
支持
|
不支持
|
支持
|
支持
|
|
網絡要求
|
支持離線
|
依賴網絡
|
支持離線(資源存本地情況)
|
支持離線
|
|
更新復雜度
|
高(幾乎總是通過應用商店更新)
|
低(服務器端直接更新)
|
較低(可以進行資源包更新)
|
較低(可以進行資源包更新)
|
|
編程語言
|
Android(Java) iOS(OC/Swift)
|
js+html+css3
|
js+html+css3
|
主要使用JS編寫 語法規則JSX
|
|
社區資源
|
豐富
|
豐富(大量前端資源)
|
有局限(不同的Hybrid相互獨立)
|
豐富(統一的活躍社區)
|
|
上手難度
|
難(不同平台需要單獨學習)
|
簡單(寫一次,支持不同平台訪問)
|
簡單(寫一次,運行任何平台)
|
中等(學習一次,寫任何平台)
|
|
開發周期
|
長
|
短
|
較短
|
中等
|
|
開發成本
|
昂貴
|
便宜
|
較為便宜
|
中等
|
|
跨平台
|
不跨平台
|
所有H5瀏覽器
|
Android,iOS,h5瀏覽器
|
Android,iOS
|
|
APP發布
|
App Store
|
Web服務器
|
App Store
|
App Store
|
3.Hybrid App
- 定義:Hybrid App(混合模式開發的移動應用)底層功能API均由原生容器通過某種方式提供,業務邏輯由H5頁面完成,最后原生容器加載H5,從而完成整個業務流程,只需要寫一套代碼即可,即可達到跨平台效果。
- 原生容器 :Native中的webview組件,用於加載HTML文件。Android中是webview,iOS7以下有UIWebview,iOS7以上有了WKWebview。
- hybrid架構:上層為web層,底層為native層,通信靠jsbridge
4.hybrid核心--JSBridge技術:構建了 Native 和非 Native 間消息雙向通信的通道
- JS 向 Native 發送消息 : 調用相關功能、通知 Native JS當前狀態。
- Native 向 JS 發送消息 : 回溯調用結果、消息推送、通知 JS 當前 Native 的狀態。
二、JSBridge實現原理
JSBridge是一種通用的交互理念,多種設計方式都可以實現,思路也不盡相同。
- JS 調用 Native 的幾種通信方案
- 特殊url scheme假跳轉的請求攔截:h5發出一條跳轉請求,其中跳轉目的地是一個非法的不存在的地址,客戶端攔截並分析請求從而調用native方法。
- 優點: 兼容性好。這是所有JS調用Native的通信方式里,唯一同時支持安卓webview/蘋果UIWebView/WKWebView的通信方式。
- 缺點:webview會把調用封裝為請求,時延達到200ms~400ms;跳轉的URL存在長度限制。
- JS上下文注入API:通過 WebView 提供的接口,向JS運行的Context(window)中直接注入對象或者方法,JavaScript調用時能直接執行相應的Native代碼邏輯。
- 優點: 由於同步返回調用速度非常快,參照alert。
- 缺點: 低版本iOS系統不支持此方式;安卓 4.2 之前,注入JavaScript的接口是 addJavascriptInterface,存在安全漏洞,4.2 后引入JavascriptInterface新接口做替代,所以存在兼容性問題。
2.請求攔截假跳轉通信方式詳解
- url地址分為幾部分:
協議://域名/路徑?參數
aweme://profile/?douyin_id=88 (假跳轉)
- JS發起跳轉3種方式:
1)在HTML中用a標簽直接填寫假請求地址
<a href="aweme://profile/?douyin_id=88">A標簽</a>
2) 原地跳轉:在JS中用location.href
location.href = 'aweme://profile/?douyin_id=88'
3) iframe跳轉:在JS中創建一個iframe,插入dom之中進行跳轉
$('body').append('<iframe src="' + 'aweme://profile/?douyin_id=88' + '" style="display:none"></iframe>');
- 攔截規則:因為客戶端內打開H5頁面的webview容器,會無差別攔截h5頁面發送所有請求,真正的url地址會正常跳轉,而協議域名符合一定規則的url地址則會被客戶端攔截,攔截下來的url不會導致webview繼續跳轉,因此用戶完全沒有感知。我們可以利用這個條件,定義一些scheme規則,客戶端讀取偽協議域名的部分作為通信識別,如bytedance:// , snssdk1128://, aweme:// 可與正常協議做區分,讀取路徑作為指令識別,讀取參數作為數據,並根據約定調用對應的native原生代碼。
- 客戶端請求攔截:不同種類webview通過不同方法,都實現了這樣的流程:首先根據協議/域名判斷是否需要攔截此調用,若需要則取出路徑,並匹配出指令,傳入js攜帶參數數據調用相應native方法,停止webview的繼續請求。
- 1)安卓 shouldOverrideUrlLoading
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//1 根據url,判斷是否是所需要的攔截的調用 判斷協議/域名
if (是){
//2 取出路徑,確認要發起的native調用的指令是什么
//3 取出參數,拿到JS傳過來的數據
//4 根據指令調用對應的native方法,傳遞數據
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
2)UIWebView webView:shouldStartLoadWithRequest:navigationType:
(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
//1 根據url,判斷是否是所需要的攔截的調用 判斷協議/域名
if (是){
//2 取出路徑,確認要發起的native調用的指令是什么
//3 取出參數,拿到JS傳過來的數據
//4 根據指令調用對應的native方法,傳遞數據
return NO;
//確認攔截,拒絕WebView繼續發起請求
}
return YES;
}
3)WKWebView webView:decidePolicyForNavigationAction:decisionHandler:
(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
//1 根據url,判斷是否是所需要的攔截的調用 判斷協議/域名
if (是){
//2 取出路徑,確認要發起的native調用的指令是什么
//3 取出參數,拿到JS傳過來的數據
//4 根據指令調用對應的native方法,傳遞數據
//確認攔截,拒絕WebView繼續發起請求
decisionHandler(WKNavigationActionPolicyCancel);
}else{
decisionHandler(WKNavigationActionPolicyAllow);
}
return YES;
}
3.Native調用 Js
- 使用evaluatingJavaScript 執行JS代碼:
相比於 JavaScript 調用 Native, Native 調用 JavaScript 較為簡單,畢竟不管是 iOS 的 UIWebView 還是 WKWebView,還是 Android 的 WebView 組件,都以子組件的形式存在於 View/Activity 中,Native想要調用JS的時候,可以把數據與調用的JS函數,通過字符串拼接成JS代碼,交給WebView進行執行,直接調用相應的 API 即可執行拼接 JavaScript 字符串。所以設計成為暴露一個如JSBridge的對象供native調用。
- 舉個例子
1)JS中存在函數jsfunction()
function jsfunction(data){
console.log(JSON.parse(data))
//1 識別客戶端傳來的數據
//2 對數據進行分析,從而調用或執行其他邏輯
}
2)客戶端用OC拼接字符串,拼出js代碼,並用json傳遞數據
//data是一個字典,把字典序列化
NSString *paramsString = [self _serializeMessageData:data];
NSString* javascriptCommand = [NSString stringWithFormat:@"jsfunction('%@');", paramsString];
//要求必須在主線程執行JS
if ([[NSThread currentThread] isMainThread]) {
[self.webView evaluateJavaScript:javascriptCommand completionHandler:nil];
} else {
__strong typeof(self)strongSelf = self;
dispatch_sync(dispatch_get_main_queue(), ^{
[strongSelf.webView evaluateJavaScript:javascriptCommand completionHandler:nil];
});
}
這段代碼只是用來拼接出這個字符串:jsfunction('{data:xxx,data2:xxx}');
再用evaluatingJavaScript或loadUrl對js代碼進行執行,即完成了native對js方法的調用
4.通信過程整理
- S1. 客戶端內H5里發送請求scheme,aweme://profile?douyin_id=233;
- S2. 客戶端內webview容器攔截所有請求,挨個請求做字符串匹配,判斷是否以 aweme:// 開頭;
- S3. scheme命中匹配,客戶端拆解出后面的參數得到操作名和對應的操作參數;
- S4. 客戶端執行對應的操作,打開個人profile頁;
- S5. Native功能調用完畢。
5.回調設計
- 場景問題:例如分享成功后增加積分等場景,h5網頁如何知道客戶端執行完畢,並執行相應回調呢?
- 參照JSONP信息傳遞的執行過程:
JSONP利用<script>標簽沒有跨域限制的特點來達到與第三方通訊的目的
調用方需要提供一個回調函數 比如cb20180725 來接收數據:
- <script src='https://www.douyin.com/get_data?callback= cb20180725'></script>
- 第三方包裝callback參數作為函數名來包裹住響應的JSON數據如cb20180725({"param": “1111”})
- 1. 在window上生成掛載一個隨機名的全局函數,函數里執行success的回調函數;
- 2.創建script標簽,載入請求地址;
- 3.服務端返回一段執行window上cb20180725函數的js代碼: cb20180725({"param": “1111”})
- 4.瀏覽器中執行完成jsonp回調。
- JSBridge回調過程:
- 1,H5發起scheme請求之前,隨機生成一個callback_id,掛到window上;
- 2,在callback_id指向的唯一函數里,執行想要的回調函數
- 3,H5發送請求scheme,bytedance://profile?douyin_id=233&callback_id=dy20180724;
- 4,客戶端內webview容器攔截所有請求,挨個請求做字符串匹配,匹配是否以 bytedance:// 開頭;
- 5, scheme命中匹配,客戶端拆解出后面的參數得到操作名和對應的操作參數;
- 6,客戶端執行對應的操作,打開個人profile頁;
- 7,執行完對應操作之后,客戶端調用webview接口執行回調 javascript:dy20180724(json_data)
關鍵點:生成回調函數callback_id,回調函數存儲(掛載在window)
