如果大家覺得有用,更多的模組請點選檢視
-
系列文章
掃碼訪問
為什麼到了今天,還要提hybrid開發,就我所在團隊從中獲得的好處有:
- 團隊較小、業務較重、迭代頻繁、需要緊急響應的團隊和專案比較適合用
- 使用單頁應用技術團隊比較適合,這一點是我在實踐了雲南農信手機銀行這樣一個企業級金融app得到的結論,因為單頁應用如果首屏載入和頁面元件懶載入等機制做的好的話,其實一般單頁應用的載入速度不亞於普通的原生開發,針對這種金融專案,建議直接將前端專案打包之後放到web sevrer,而不是直接打包到客戶端,這樣方便維護,因為這種專案,甲方其實對頁面的渲染速度的要求還是其次,安全和bug的修復速度才是放在首位
JSBridge是前端和客戶端互動的核心概念,下面就分享一下我們的經驗,因為目前android裝置一般都是4.4以上系統,故我們直接廢棄了protocol
自定義協議的互動方式,直接使用上下文方式,這點需要客戶端開發人員注意。
下面介紹一下vue-viewplus 一個簡化Vue應用開發的工具庫中的JSBridge橋接模組js-bridge-context.js:
js-bridge-context.js JSBridge橋接模組,用於簡化前端和客戶端(Android && IOS)直接的互動,配合Jiiiiiin/android-viewplus 一個安卓混合客戶端開發庫可以讓hybrid開發易如反掌 :)
這個模組需要和客戶端配合使用,有以下一些約束和條件:
- 客戶端只需要暴露一個
上下文物件
、一個介面
,因為模組再呼叫客戶端時候會約定一個資料傳輸物件,來滿足客戶端的內容分發,如: android那邊可能是這樣的:
mWebView.addJavascriptInterface(new Object(){
@JavascriptInterface
public String event(String params) {
final JSONObject jsonObj = JSONObject.parseObject(params);
String event = jsonObj.getString("event");
String action = jsonObj.getString("action");
String callback = jsonObj.getString("callback");
String listener = jsonObj.getString("listener");
JSONObject rparams = jsonObj.getJSONObject("params");
switch (action) {
case "toast":
ToastUtils.showLong(rparams.getString("msg"));
// 模擬非同步執行其他事情
new Thread(() -> {
// 非同步通知前端,即java呼叫前端js
HANDLER.post(() -> mWebView.evaluateJavascript(listener + "('" + params + "');", null));
}).start();
// 同步返回標識請求成功或失敗
HANDLER.post(() -> mWebView.evaluateJavascript(callback + "('seccuess');", null));
break;
default:
}
return params;
}
}, "ViewPlus");
複製程式碼
上面簡單來說就是,安卓客戶端向瀏覽器暴露了一個window.ViewPlus#event(command)
,這樣一個介面,然後通過解析command
這個字串型別的json物件,來判斷前端希望客戶端做什麼,做完之後怎麼處理;
那麼當前模組就是為了簡化和客戶端的互動,讓每一次請求,就行完成一個ajax
操作一樣,如:
this.$vp.fireEvent({
event: 'UIEvent',
action: 'toast',
params: {
msg: 'hello world'
}
}).then(res => {
console.log('請求成功,客戶端返回的同步請求結果', res)
}).catch(err => {
console.log('請求出錯,客戶端返回的同步錯誤資訊', res)
})
複製程式碼
使用模組提供的$vp.fireEvent
方法,我們只需要通過傳遞一條command指令就可以得到一個{Promise}
,這裡和util-http.js
模組的請求方法一樣;
command的含義:
{
// [*] event用來標識請求那個客戶端的模組,方便客戶端根據業務組織“內部JSBridge介面”
event: 'UIEvent',
// action標識請求對應模組的那個方法或者說交易,客戶端據此去呼叫該方法
action: 'toast',
// 【可選】params用來傳遞對應action需要的引數
params: {
msg: 'hello world'
}
// 【可選】listener用來告訴客戶端執行完(一般而言是非同步操作)方法只會需要回撥該方法通知前端
listener: ([客戶端傳遞]) => {}
}
複製程式碼
至於同步訊息的處理,因為ios和android的處理不同,外掛已經幫你磨平了,客戶端程式設計師需要注意的是command中針對ios一定會有一個callback
引數,
標識action方法處理完成需要“馬上”呼叫反饋給前端,當前請求是否處理完成,而android則沒有這個引數,是因為public String event(String params)
這個暴露的介面的返回值,就完成了這個需求;
還需要客戶端程式設計師注意的是:
ios,模組呼叫的是global.webkit.messageHandlers[name].postMessage(JSON.stringify(command))
來呼叫,這裡的name是當前模組的context.name
配置項;
android,模組呼叫的是global[name].event(JSON.stringify(command))
,這裡的event是寫死的!!!
所以這裡推薦一個安卓類庫,專門為當前外掛而訂製,幫大家完成了這一系列工作:android-viewplus一個安卓混合客戶端開發庫
哈哈哈,強行安利一波;
示例
模擬前端呼叫客戶端toast
列印一個'hello vplus'
<template>
<div id="JsBridgeContext">
<group title="使用$vp#fireEvent請求客戶端彈出一個toast:" label-width="15em" class="bottom-group">
<box gap="10px 10px">
<x-button mini plain @click.native="doFireEvent" class="fl-right">執行</x-button>
</box>
</group>
</div>
</template>
<script type="text/ecmascript-6">
import demoMixin from './demo-mixin'
export default {
mixins: [demoMixin],
methods: {
doFireEvent() {
try {
this.$vp.fireEvent({
event: 'UIEvent',
action: 'toast',
params: {
msg: 'hello vplus'
}
}).then(res => {
this.$vp.uiDialog(res, {
title: '橋接呼叫成功',
showCode: true
})
}).catch(err => {
this.$vp.uiDialog(`橋接呼叫失敗 ${err.message}`)
})
} catch (e) {
this.$vp.uiDialog(`橋接呼叫失敗 ${e.message}`)
}
}
}
}
</script>
複製程式碼
除了上面這種直白的互動,我們針對客戶端資料傳輸安全和前端跨域問題,也依賴當前模組整合了一個通過客戶端代理前端傳送請求的util-http.js模組,後期如有需要將會分享;
配置
onParseClientResp[*]
/**
* [*] `$vp#onParseClientResp(res)`
* 當客戶端返回結果之後會回撥該鉤子,應用可以通過該函式來判斷客戶端返回的訊息是否正確,意思就和`util-http.js`模組一樣,這裡的是否正確,
* 是業務級別的;
* return true 標識業務級別成功,否則為失敗,這裡的判斷直接影響`$vp#fireEvent`返回的Promise是呼叫失敗還是成功處理流程,如果不定義該配置項,那麼`$vp#fireEvent`將會直接返回成功
*/
onParseClientResp
複製程式碼
name[*]
/**
* 客戶端暴露給前端的全域性物件名稱
* [*] {String}
* <p>
* 模組安裝的時候回檢測當前執行環境中是否存在這樣一個名稱的上下文物件
*/
name = 'ViewPlus'
複製程式碼
enable
/**
* 標識是否啟用當前模組
* [可選] {Boolean}
* <p>
* + 有一種情況,應用希望手動設定`$vp.runNative`標識,以便程式可以方便知道自己的執行環境,但是又不想使用當前模組,這種情況,就可以單獨把這裡配置為false
* 當然如果`$vp.runNative`已經被設定為false,那麼還需要這個模組幹嘛呢?
*/
enable = runNative
複製程式碼
API介面
onParseClientResp
/**
* $vp.onParseClientResp()
* 方便應用呼叫該方法判斷`command#listener`的返回結果,直接代理到`js-bridge-context`配置項`onParseClientResp`
* @returns {Boolean} true 標識業務級別成功,否則為失敗
*/
onParseClientResp() {
if (_.isFunction(_onParseClientResp)) {
return this::_onParseClientResp()
} else {
emitErr(new Error('on_parse_client_resp_func_not_config'))
}
}
複製程式碼
fireEvent
/**
* $vp.fireEvent(command = null)
* 應用可以直接呼叫該方法完成和客戶端的互動
* <p>
* 協議方式請求客戶端
* command的格式:
* const command = {
* // [*] event用來標識請求那個客戶端的模組,方便客戶端根據業務組織“內部JSBridge介面”
* event: 'UIEvent',
* // action標識請求對應模組的那個方法或者說交易,客戶端據此去呼叫該方法
* action: 'toast',
* // 【可選】params用來傳遞對應action需要的引數
* params: {
* // 自定義引數
* msg: 'hello world'
* }
* // 【可選】listener用來告訴客戶端執行完(一般而言是非同步操作)方法只會需要回撥該方法通知前端
* listener: ([客戶端傳遞]) => {}
* }
* <p>
* @param {Object} [command=null] 客戶端所需的呼叫訊息
*/
fireEvent(command = null)
複製程式碼
如果大家覺得有用,更多的模組請點選檢視