述
在混合應用開發中,一種常見且成熟的技術方案是將原生應用與 WebView 結合,使得復雜的業務邏輯可以通過網頁技術實現。實現這種類型的混合應用時,就需要解決H5與Native之間的雙向通信。JSBridge 是一種在混合應用中實現 Web 和原生代碼之間通信的重要機制。
混合開發
混合開發(Hybrid)是一種開發模式,指使用多種開發模型開發App,通常會涉及到兩大類技術:原生 Native、Web H5。
混合開發的意義就在于吸取兩者的優點,而且隨著手機硬件的升級迭代、系統(Android 5.0+、ISO 9.0+)對于Web特性的較好支持,H5的劣勢被逐漸縮小。
JSBridge 的概念和作用
為什么在混合應用開發中 JSBridge 如此重要
JSBridge 做了什么
在Hybrid模式下,H5會需要使用Native的功能,比如打開二維碼掃描、調用原生頁面、獲取用戶信息等,同時Native也需要向Web端發送推送、更新狀態等,而JavaScript是運行在單獨的 JS Context 中(Webview容器)與原生有運行環境的隔離,所以需要有一種機制實現Native端和Web端的 雙向通信 ,這就是JSBridge:以JavaScript引擎或Webview容器作為媒介,通過協定協議進行通信,實現Native端和Web端雙向通信的一種機制。
通過JSBridge,Web端可以調用Native端的Java接口,同樣Native端也可以通過JSBridge調用Web端的JavaScript接口,實現彼此的雙向調用。
JSBridge 實現原理
把 Web 端和 Native 端的通信比作 Client/Server 模式。JSBridge 充當了類似于 HTTP 協議的角色,實現了 Web 端和 Native 端之間的通信。
將 Native 端原生接口封裝成 JavaScript 接口:在 Native 端將需要被調用的原生功能封裝成 JavaScript 接口,讓 JavaScript 代碼可以調用。JavaScript 接口會被注冊到全局對象中,以供 JavaScript 代碼調用。
將 Web 端 JavaScript 接口封裝成原生接口:這一步是在 Web 端將需要被調用的 JavaScript 功能封裝成原生接口。這些原生接口會通過 WebView 的某些機制暴露給原生代碼,以供原生代碼調用。
Native -> Web
Native端調用Web端,JavaScript作為解釋性語言,最大的一個特性就是可以隨時隨地地通過解釋器執行一段JS代碼,所以可以將拼接的JavaScript代碼字符串,傳入JS解析器執行就可以,JS解析器在這里就是webView。
1. Android
Android 提供了 evaluateJavascript 來執行JS代碼,并且可以獲取返回值執行回調:
String jsCode = String.format("window.showWebDialog('%s')", text);
webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
2. IOS
IOS的 WKWebView 使用 evaluateJavaScript:
[webView evaluateJavaScript:@"執行的JS代碼"
completionHandler:^(id _Nullable response, NSError * _Nullable error) {
//
}];
Web -> Native
Web調用Native端主要有兩種方式:
1. URL Schema
URL Schema是類URL的一種請求格式,格式如下:
<protocol>://<host>/<path>?<qeury>#fragment
// 我們可以自定義JSBridge通信的URL Schema,比如:
hellobike://showToast?text=hello
Native加載WebView之后,Web發送的所有請求都會經過WebView組件,所以Native可以重寫WebView里的方法,從來攔截Web發起的請求,我們對請求的格式進行判斷:
例如:
get existOrderRedirect() {
let url: string;
if (this.env.isHelloBikeApp) {
url = 'hellobike://hellobike.com/xxxxx_xxx?from_type=xxxx&selected_tab=xxxxx';
} else if (this.env.isSFCApp) {
url = 'hellohitch://hellohitch.com/xxx/xxxx?bottomTab=xxxx';
}
return url;
}
這種方式從早期就存在,兼容性很好,但是由于是基于URL的方式,長度受到限制而且不太直觀,數據格式有限制,而且建立請求有時間耗時。
2. 在Webview中注入JS API
通過webView提供的接口,App將Native的相關接口注入到JS的Context(window)的對象中
Web端就可以直接在全局 window 下使用這個暴露的全局JS對象,進而調用原生端的方法。
Android注入方法:
IOS注入方法:
例如:
// 注入全局JS對象
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx) {
this.ctx = ctx;
}
// 綁定方法
@JavascriptInterface
public void showNativeDialog(String text) {
new AlertDialog.Builder(ctx).setMessage(text).create().show();
}
}
// 調用nativeBridge的方法
window.NativeBridge.showNativeDialog('hello');
H5具體實現
將功能抽象為一個 AppBridge 類,封裝兩個方法,處理交互和回調。
具體步驟:
具體實現代碼:
// 定義一個名為 callNative 的方法,用于在 JavaScript 中調用原生方法
callNative<P, R>(classMap: string, method: string, params: P): Promise<R> {
return new Promise<R>((resolve, reject) => {
// 生成一個唯一的回調 ID
const id = v4();
// 將當前的回調函數保存到 __callbacks 對象中,以 callbackId 作為鍵
this.__callbacks[id] = { resolve, reject, method: `${classMap} - ${method}` };
// 構造通信數據,包括原生類映射、要調用的方法、參數和 callbackId
const data = {
classMap,
method,
params: params === null ? '' : JSON.stringify(params),
callbackId: id,
};
const dataStr = JSON.stringify(data);
// 根據當前環境判斷是 iOS 還是 Android,并調用相應平臺的原生方法
if (this.env.isIOS && isFunction(window?.webkit?.messageHandlers?.callNative?.postMessage)) {
// 如果是 iOS 平臺,則調用 iOS 的原生方法
window.webkit.messageHandlers.callNative.postMessage(dataStr);
} else if (this.env.isAndroid && isFunction(window?.AppFunctions?.callNative)) {
// 如果是 Android 平臺,則調用 Android 的原生方法
window.AppFunctions.callNative(dataStr);
}
});
}
// 初始化橋接回調函數,該參數在 constructor 中調用
private initBridgeCallback() {
// 保存舊的回調函數到 oldCallback 變量中
const oldCallback = window.callBack;
// 重新定義 window.callBack 方法,用于處理原生應用的回調數據
window.callBack = (data) => {
// 如果存在舊的回調函數,則調用舊的回調函數
if (isFunction(oldCallback)) {
oldCallback(data);
}
// 獲取原生應用的回調信息,包括數據和回調 ID
console.info('native callback', data, data.callbackId);
// 從回調數據中獲取回調 ID
const { callbackId } = data;
// 根據回調 ID 查找對應的回調函數
const callback = this.__callbacks[callbackId];
// 如果找到了對應的回調函數
if (callback) {
// 如果回調數據中的 code 為 0,則表示執行成功,調用 resolve 方法處理成功的結果
if (data.code === 0) {
callback.resolve(data.data);
} else {
// 否則,表示執行失敗,構造一個錯誤對象并調用 reject 方法處理錯誤信息
const error = new Error(data.msg) as Error & {response:unknown};
error.response = data;
callback.reject(error);
}
// 刪除已經處理過的回調函數
delete this.__callbacks[callbackId];
}
};
}
// 調用原生方法的封裝函數
callNative<P, R>(classMap: string, method: string, params: P) {
// 從容器中解析出 AppBridge 實例
const bridge = container.resolve<AppBridge>(AppBridge);
// 使用 bind 方法將 AppBridge 實例中的 callNative 方法綁定到 bridge 對象上,并保存到 func 變量中
const func = bridge.callNative.bind(bridge);
// 調用 func 方法,并傳入 classMap、method 和 params 參數,實現調用原生方法的功能
return func<P, R>(classMap, method, params);
}
// 打開 webview
// 調用 callNative 方法,傳入參數 url,classMap 為 'xxxxx/hitch',method 為 'openWebview'
openWebView(url: string): Promise<void> {
return this.callNative<{url:string}, void>('xxxxx/hitch', 'openWebview', { url });
}
// 獲取駕駛證 OCR 信息
getDriverLicenseOcrInfo(
params: HBNative.getDriverLicenseOcrInfo.Params,
): Promise<HBNative.getDriverLicenseOcrInfo.Result> {
// 調用 callNative 方法,傳入參數 params,classMap 為 'xxxxx/hitch',method 為 'getOcrInfo'
// 返回一個 Promise 對象,該 Promise 對象用于處理異步結果
return this.callNative<
HBNative.getDriverLicenseOcrInfo.Params,
HBNative.getDriverLicenseOcrInfo.Result>(
'xxxxx/hitch', 'getOcrInfo', params,
);
}
作者:佟健
來源-微信公眾號:哈啰技術
出處:https://mp.weixin.qq.com/s/h6vlkf5rgyI9GpEpPxO9cg
素顯示模式就是元素(標簽)以什么方式進行顯示,比如div標簽自己占一行,一行可以放多個span標簽。
HTML 元素分為塊元素、行內元素、行內塊元素,它們具體的用法和區別如下:
塊元素是用于定義一個獨立的區域,比如一個段落、一個標題、一個表格等。
常見的塊元素有:h1-h6、div(最典型)、p、ol、ul、li等。
特點
行內元素是用于在同一行內顯示文本,并不需要包含在其他元素內。
常用的行內元素有:a、strong、em、del、ins、b、i、u、span(最典型)等。
特點:
注意:
行內塊元素是用于在同一行內顯示文本,并不需要包含在其他元素內,但是可以包含圖片或其他多個文本元素。
常用的行內塊元素有:
特點:
在行內元素中有幾個特殊的標簽,img、input、td,它們同時具有行內元素和塊元素的特點。
1.塊元素定義了一個獨立的區域,不會和其他元素融合在一起,而行內元素和行內塊元素都屬于行內元素,它們都可以包含其他元素。
2.塊元素可以設置高度和外邊距,而行內元素和行內塊元素都不能設置高度和外邊距。
3.塊元素只能包含文本或其他圖片等元素,而行內元素和行內塊元素都可以包含其他任意類型的元素。
4.塊元素的外邊距、填充、寬度等屬性可以根據實際需求設置,而行內元素和行內塊元素的樣式則完全根據實際需求設置。
特殊情況下,我們需要元素模式的轉換,比如要增加a標簽鏈接的觸發范圍。
今開發出一款成功的APP已經成為公司運營的重要一環,但APP的開發模式很多,不同模式的實現機理不同,因而會在APP的開發成本、運行性能、升級維護和用戶體驗等方面造成不同的影響。
目前來說,APP的主要開發模式主要分為四類:
第一類是原生應用開發,即NativeApp。基于Android平臺的Java語言開發和基于iOS平臺的Objective-C語言/Swift語言開發。
第二類是移動網頁應用開發,即WebApp。網頁應用開發是利用Web技術,使用HTML、CSS和JavaScript開發用于移動端顯示的網頁。
第三類是把NativeApp和WebApp結合的混合開發模式,即HybridApp。
第四類是基于ReactNative框架的JavaScript語言開發的類原生應用,即ReactNativeApp。
下面主要是對比較常用到的NativeApp開發模式做一個具體的分析,希望有助于大家對于APP開發有一個了解!
NativeApp是本地開發方式,基于手機操作系統進行開發,利用Java、Objective-C或Swift語言進行程序開發,然后編譯成字節碼或機器碼后經操作系統調度運行。
由于操作系統不同以及開發語言的不同,當今最流行的兩大移動端平臺Android和iOS各有自己的一套獨立的開發模式,兩大平臺差異較大。
現以iOS平臺為例簡述下NativeApp的開發模式。
首先需要在MacOSX系統上安裝蘋果公司開發的IDE――Xcode。Xcode內擁有開發iOSNativeApp快捷高效的CocoaTouch框架,是開發原生iOS程序的不二選擇。
在Xcode中創建一個SingleViewApplication工程后選用Swift語言開發,會自動生成以Main.storyboard、AppDelegate.swift、ViewController.swift開發核心的若干文件。其中Main.storyboard用于構建各個頁面之間跳轉關系和具體頁面布局,在Xcode的右下角有可拖拽的若干控件,拖拽控件到Main.storyboard后結合AutoLayout為各個控件添加各種布局約束,來保證界面在不同大小的屏幕上的適配。
AppDelegate.swift是為iOS程序做一些初始化設置,主要用于在APP啟動時為重要的數據結構進行初始化,以及響應APP運行時事件,如程序啟動、程序運行內存不足、程序切換等。程序員可在AppDelegate.swift中的相關函數里對事件響應進行操作。
ViewController.swift就是iOS程序中的初始界面,是UI控件和程序邏輯的控制器。程序員主要通過ViewController.swift中的viewDidLoad和viewWillAppear等方法對iOS程序的數據層和視圖層進行控制,來表達APP的業務邏輯。
NativeApp開發模式的優勢在于NativeApp是編譯后的文件,執行速度快,界面動畫十分流暢,對網絡的依賴性小,用戶體驗很好。基于平臺層可以非常方便地調用操作系統提供的各種功能,如調用攝像頭、推送信息和讀取本地通訊錄等。
NativeApp開發模式的劣勢主要是:開發周期長,兩套獨立的知識體系復雜且學習成本高,依賴操作系統而無法進行跨平臺開發,APP版本升級繁瑣需要重新把源文件編譯打包再由用戶下載覆蓋安裝。
移動互聯網的浪潮,推動著移動開發技術的不斷發展,移動App的開發模式也豐富了起來。每個開發模式都有自身的優勢與不足,在實際開發前應仔細權衡開發人員的知識體系和開發成本。
延伸閱讀
————————
“廣州優匠科技”是一家由經驗豐富的技術設計開發團隊創辦的軟件外包公司。專注于小程序、公眾號、APP、軟件系統等相關技術的定制與開發。
優匠科技深耕在互聯?服務領域多年,有足夠的成功案例與資深專業的技術團隊,能夠幫助企業解決很多技術上的難題。多年來一直都是用“匠心精神”去做好技術和產品服務。
如有興趣合作或是產品開發需求的朋友,歡迎前來咨詢了解!!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。