轉(zhuǎn)轉(zhuǎn)質(zhì)檢桌面應(yīng)用程序的架構(gòu)演進(jìn)
質(zhì)檢是轉(zhuǎn)轉(zhuǎn)履約體系中的重要一環(huán),通過對(duì)手機(jī)、平板、筆記本、耳機(jī)、手表等品類商品的軟硬件功能、外觀成色等進(jìn)行全方面檢測(cè),為買家、賣家把好質(zhì)量關(guān),讓二手交易變得更透明、更靠譜,促進(jìn)綠色消費(fèi)。
在質(zhì)檢環(huán)節(jié)中,通過標(biāo)準(zhǔn)化產(chǎn)線,結(jié)合自動(dòng)化設(shè)備、質(zhì)檢 APP、桌面應(yīng)用程序等,最終輸出全面可信的“質(zhì)檢報(bào)告”呈現(xiàn)給用戶。其中,桌面應(yīng)用程序發(fā)揮著舉足輕重的作用,本文將重點(diǎn)介紹桌面應(yīng)用程序架構(gòu)的演進(jìn)及其落地。
1 背景
轉(zhuǎn)轉(zhuǎn)質(zhì)檢的桌面應(yīng)用程序,前期主要由 Qt 構(gòu)建,C/C++提供底層支持。這些桌面應(yīng)用的視圖層、應(yīng)用層、以及底層能力支持,均由 C/C++開發(fā)人員承擔(dān)全部開發(fā)迭代工作。其次隨著業(yè)務(wù)的不斷發(fā)展,部分桌面應(yīng)用程序逐漸暴露出拓展性差、迭代難的問題。
在轉(zhuǎn)轉(zhuǎn)質(zhì)檢技術(shù)團(tuán)隊(duì),除了 C/C++開發(fā)同學(xué)外,還有配套成熟的前端和 Java 后端,綜合來說:對(duì)于視圖層,前端的技術(shù)生態(tài)、以及開發(fā)人員的技術(shù)經(jīng)驗(yàn),在視圖層開發(fā)方面具有很大的優(yōu)勢(shì);在應(yīng)用層方面,Java 技術(shù)生態(tài)的優(yōu)勢(shì)不言而喻,同時(shí) Java 后端同學(xué)對(duì)整體業(yè)務(wù)和系統(tǒng)都有著相對(duì)全面深入的了解,簡(jiǎn)而言之,Java 同學(xué)在應(yīng)用層架構(gòu)設(shè)計(jì)和落地方面,是非常合適的。
綜上,基于團(tuán)隊(duì)實(shí)際,筆者團(tuán)隊(duì)對(duì)桌面應(yīng)用程序,提出了新的技術(shù)架構(gòu)——EJC(Electron、Java、C/C++)。該架構(gòu)的主要優(yōu)勢(shì)在于:
- 讓 C/C++開發(fā)同學(xué)更偏向于底層能力的研究,發(fā)揮更大的價(jià)值。
- Electron 本質(zhì)是前端技術(shù)棧,Java 同學(xué)更理解整體業(yè)務(wù),在應(yīng)用層設(shè)計(jì)經(jīng)驗(yàn)方面更擅長(zhǎng);且前端和 Java 資源更容易靈活調(diào)配。
- Electron 和 Java 本身是跨端的,對(duì)于后續(xù)質(zhì)檢桌面應(yīng)用各端的整合(Windows&Mac),具有不錯(cuò)的優(yōu)勢(shì)。
2 EJC 架構(gòu)
簡(jiǎn)單說,EJC 技術(shù)架構(gòu)即:Electron(視圖層/用戶層),Java(應(yīng)用層),C/C++(基礎(chǔ)能力層)。
2.1 Electron
Electron 是一個(gè)基于 Chromium 和 Node.js 的框架,一套多端生成 Windows、macOS 和 Linux 的跨平臺(tái)桌面應(yīng)用程序。
- 本質(zhì)是前端技術(shù)棧,內(nèi)置了 Chromium 內(nèi)核使得應(yīng)用程序具有最新的 web 標(biāo)準(zhǔn),開發(fā)人員可以專注應(yīng)用程序的邏輯和界面設(shè)計(jì),不用再束手束足做瀏覽器兼容性操作。
- 整包更新和熱更新,使程序保持最新的狀態(tài),類似混合移動(dòng)應(yīng)用(Hybrid APP)的快感。
- 安全的跨平臺(tái)運(yùn)行環(huán)境,可以有效地降低程序崩潰和系統(tǒng)錯(cuò)誤的機(jī)率,實(shí)現(xiàn)更加可靠和穩(wěn)定的應(yīng)用程序。
- 豐富的 API 在 C/C++能力的加持下,讓軟硬件結(jié)合更加絲滑,擴(kuò)展能力更進(jìn)一步。
2.2 Java
Java 應(yīng)用層,主要包括:
- 通訊模塊:提供基于 HTTP、WebSocket 等協(xié)議的通信能力。
- 底層交互模塊: 封裝了 Java 調(diào)用本地代碼(動(dòng)態(tài)庫)的技術(shù)(JNI/JNA)。
- 數(shù)據(jù)存儲(chǔ):使用輕量級(jí)數(shù)據(jù)庫 SQLite 來持久化數(shù)據(jù),為數(shù)據(jù)的高可用及容錯(cuò)提供基礎(chǔ)能力。
- 事件監(jiān)聽:基于 Spring 的事件和監(jiān)聽機(jī)制實(shí)現(xiàn)了基于事件驅(qū)動(dòng)的編程模型,有松耦合、高擴(kuò)展性和可測(cè)試性等優(yōu)點(diǎn)。
- 業(yè)務(wù)模塊:必要的業(yè)務(wù)邏輯處理。
- 監(jiān)控模塊:對(duì)客戶端的實(shí)時(shí)運(yùn)行參數(shù)、硬件異常情況等數(shù)據(jù)進(jìn)行記錄,定時(shí)上報(bào)云端。
- 配置管理:定時(shí)從云端拉取最新的配置,覆蓋本地配置。
- 調(diào)度策略:根據(jù)設(shè)備的歷史狀態(tài),預(yù)判硬件(如貨架貨位上的 USB 通訊口、USB 集線器等設(shè)備)是否出現(xiàn)故障,當(dāng)判定故障時(shí),可以優(yōu)先調(diào)度其它設(shè)備并將故障信息上報(bào)。
2.3 C/C++
基礎(chǔ)能力層,核心 SDK 的實(shí)現(xiàn)。提供與 Windows、IOS、安卓、相機(jī)、機(jī)械臂等底層通用能力。
基于上述說明,在轉(zhuǎn)轉(zhuǎn)質(zhì)檢中,筆者呈現(xiàn)的 EJC 技術(shù)架構(gòu),如下:
EJC架構(gòu)圖
下面重點(diǎn)對(duì) Java 應(yīng)用層的前端通訊模塊、底層通訊模塊進(jìn)行介紹。
2.4 前端通訊模塊
在 Java 應(yīng)用層兼容了 HTTP 協(xié)議、WebSocket 協(xié)議的通信方式。接下來介紹幾種與前端通訊的方案以及我們?cè)?EJC 架構(gòu)中的選型和考量:
2.4.1 HTTP 短輪詢
客戶端周期性的向服務(wù)器發(fā)送請(qǐng)求,以獲得最新的數(shù)據(jù),這種方式會(huì)造成服務(wù)器和網(wǎng)絡(luò)資源的浪費(fèi)。適用于實(shí)時(shí)性要求不高的場(chǎng)景:在 Java 客戶端從云端拉取配置時(shí),采用的就是此機(jī)制。
2.4.2 HTTP 長(zhǎng)輪詢
與 HTTP 短輪詢相比,HTTP 長(zhǎng)輪詢能夠避免客戶端頻繁向服務(wù)器發(fā)送請(qǐng)求,節(jié)省了網(wǎng)絡(luò)和服務(wù)器資源的開銷,同時(shí)能實(shí)現(xiàn)更及時(shí)和可靠的數(shù)據(jù)推送。
2.4.3 SSE(Server-Sent Events)
本質(zhì)上是一個(gè) HTTP 長(zhǎng)連接,服務(wù)端發(fā)送給客戶端不是一個(gè)數(shù)據(jù)包,而是一個(gè) stream 流,格式為 text/stream,所以客戶端不會(huì)關(guān)閉連接,會(huì)一直等著服務(wù)器發(fā)過來新的數(shù)據(jù)流。適合一些只需要服務(wù)端單向推送事件給客戶端的場(chǎng)景。
在實(shí)際的應(yīng)用場(chǎng)景中,服務(wù)端只需要推送一次信息給前端(如:前端調(diào)用 Java 服務(wù)端獲取系統(tǒng)硬件配置信息),我們選擇 SSE 作為前后端的通信方式,有以下優(yōu)勢(shì):
- 比 http 短輪詢性能更好。
- 比 http 長(zhǎng)輪詢更可靠。
- 比 WebSocket 更輕量。
- 可以在現(xiàn)有的基礎(chǔ)設(shè)施和技術(shù)上使用,而不需要進(jìn)行任何額外的配置或部署。
2.4.4 WebSocket
WebSocket 是基于 TCP 的雙向通信協(xié)議,可以實(shí)現(xiàn)實(shí)時(shí)通信。適合實(shí)時(shí)性要求很高而且需要雙工通信的系統(tǒng)。在實(shí)際的應(yīng)用中,如隱私清除工具,從插入手機(jī)到隱私清除完成只需要 3~5 秒,質(zhì)檢人員需要實(shí)時(shí)的看到手機(jī)狀態(tài)的變更,這時(shí)候我們選用 Websocket 實(shí)時(shí)的將數(shù)據(jù)狀態(tài)推送到前端進(jìn)行展示。
2.5 底層通訊模塊
Java 調(diào)用 C/C++ 有 JNI (Java Native Interface) 與 JNA (Java Native Access) 兩種方式,都是 Java 中用于調(diào)用本地底層 SDK 的技術(shù)。
下面通過簡(jiǎn)單的代碼示例(獲取 IOS 設(shè)備名稱)來說明 Java 是如何調(diào)用底層 SDK 的。為了節(jié)約篇幅,僅展示了部分關(guān)鍵代碼。
2.5.1 JNI 介紹和使用
Java 語言提供的標(biāo)準(zhǔn)接口,它提供了一組函數(shù)和數(shù)據(jù)類型,允許 Java 應(yīng)用程序調(diào)用和被 C/C++ 語言調(diào)用。JNI 通過編寫本地方法實(shí)現(xiàn)與 C/C++ 語言的交互。
- 使用 native 關(guān)鍵字聲明本地方法。
- 通過 javah 命令,將代碼中的 native 方法生成對(duì)應(yīng)的 C 語言的頭文件。
- 執(zhí)行上述命令后,將生成一個(gè)名為 JniDemo.h 的 C/C++ 頭文件。
- C/C++ 實(shí)現(xiàn)頭文件來實(shí)現(xiàn) Java_JniDemo_getDeviceNameByUDID 方法,并將其編譯為動(dòng)態(tài)庫。
- Java 使用
2.5.2 JNA 介紹和使用
JNA 是在 JNI 基礎(chǔ)上實(shí)現(xiàn)的編程框架,實(shí)現(xiàn)了 Java 類型到 C 類型的自動(dòng)轉(zhuǎn)換。Java 開發(fā)人員只要在一個(gè) Java 接口中描述目標(biāo) native library 的函數(shù)與結(jié)構(gòu),不再需要編寫任何 Native/JNI 代碼,極大的降低了 Java 調(diào)用動(dòng)態(tài)庫的開發(fā)難度。
- 編寫 C/C++代碼,聲明頭文件(需要使用 extern “C”關(guān)鍵字才能被 JNA 調(diào)用)。
- 實(shí)現(xiàn)頭文件,并將其編譯為動(dòng)態(tài)庫。
- Java 中使用。
首先在項(xiàng)目中引入 JNA 庫:
聲明與動(dòng)態(tài)庫對(duì)應(yīng)的 Java 接口類:
加載動(dòng)態(tài)庫并調(diào)用方法:
2.5.3 選型和考量
通過上述的示例代碼我們對(duì)比了兩種方案的優(yōu)缺點(diǎn),并進(jìn)行了性能測(cè)試。
JNI | JNA | |
優(yōu)點(diǎn) | 本地方法編譯后可以選擇 C 或 C++ 來實(shí)現(xiàn)。調(diào)用本地方法時(shí)效率相對(duì) JNA 高。 | 封裝了系統(tǒng)常用的動(dòng)態(tài)庫,可以直接使用。開發(fā)效率相對(duì) JNI 高,無需 Java 編寫本地方法。 |
缺點(diǎn) | 開發(fā)效率相對(duì)較低,需要 Java 編寫本地方法并編譯生成 C/C++頭文件,C/C++ 需要按照生成的頭文件進(jìn)行編碼實(shí)現(xiàn)。 | 不支持 C++編譯生成的動(dòng)態(tài)庫,需要在 C++ 接口的上層用 C 語言進(jìn)行一次封裝。 |
從開發(fā)者的角度來說:JNA 對(duì) Java 開發(fā)者比較友好,JNI 則對(duì) C/C++開發(fā)者比較友好。
同時(shí)我們分別用 JNI 和 JNA 進(jìn)行 100 次到 500 次讀取 IOS 設(shè)備名稱的性能測(cè)試,得到耗時(shí)對(duì)比。在 8 核 16G 機(jī)器上運(yùn)行得到如下結(jié)果:
計(jì)算數(shù)量(百次) | JNI | JNA |
1 | 1197ms | 26957ms |
2 | 2196ms | 52800ms |
3 | 2759ms | 79260ms |
4 | 4573ms | 106377ms |
5 | 6299ms | 132482ms |
通過上面的對(duì)比和性能測(cè)試,我們制定了如下選型標(biāo)準(zhǔn):
- 自研的 SDK:高優(yōu)使用 JNI 作為底層通信方式。優(yōu)勢(shì)在于:JNI 的性能更好,底層數(shù)據(jù)交互的接口由 Java 定義,C/C++開發(fā)者可以選擇 C 或 C++進(jìn)行實(shí)現(xiàn),有更多的選擇性及靈活性。
- 外部廠商提供的 SDK:優(yōu)先調(diào)用廠商自帶的 SDK。優(yōu)勢(shì)在于:無需 C/C++再次封裝一層動(dòng)態(tài)庫,減少開發(fā)資源的投入。
3 EJC 架構(gòu)的落地
EJC 架構(gòu)在轉(zhuǎn)轉(zhuǎn)質(zhì)檢已成功落地了多個(gè)應(yīng)用,下面主要介紹 EJC 在 Windows 筆記本質(zhì)檢工具中的落地。
3.1 項(xiàng)目背景
隨著質(zhì)檢業(yè)務(wù)的發(fā)展,筆記本質(zhì)檢量再創(chuàng)新高。早期由 C/C++開發(fā)、使用 Qt 構(gòu)建的筆記本驗(yàn)機(jī)工具已不能滿足業(yè)務(wù)的需求,主要體現(xiàn)有以下幾點(diǎn):
- 維護(hù)成本高:代碼的復(fù)雜性較高,維護(hù)需要開發(fā)者投入更多的時(shí)間和精力。
- 覆蓋率低:功能不夠完善,易用性較差,使用覆蓋率低。
- 移植性差:無法移植到 Mac 平臺(tái)。
基于上述的項(xiàng)目背景,我們使用了 EJC 架構(gòu)來重構(gòu)筆記本驗(yàn)機(jī)工具。
3.2 架構(gòu)實(shí)現(xiàn)
3.2.1 名詞解釋
- WMI:Windows Management Instrumentation;是 Windows 系統(tǒng)標(biāo)準(zhǔn)的信息服務(wù)。
- WinAPI:Windows 系統(tǒng)提供的底層接口。
- DLL(C):即 EJC 中的 C,由我們 C/C++的同學(xué)研發(fā)的底層 SDK。
3.2.2 流程描述
- 錄入獲取數(shù)據(jù)流程:Electron 啟動(dòng)后 -> 后臺(tái)異步啟動(dòng) Java 服務(wù)端 -> 開啟異步全局掃描筆記本基本數(shù)據(jù)項(xiàng)注解 -> 獲得需讀取的電腦屬性 -> 調(diào)度器分類執(zhí)行屬性獲取命令 -> 執(zhí)行獲取命令執(zhí)行鏈(可橫向擴(kuò)展獲取方式)-> 數(shù)據(jù)加工 -> 數(shù)據(jù)糾錯(cuò) -> 經(jīng) SSE 通道推送頁面渲染。
- 質(zhì)檢流程輔助流程:進(jìn)入質(zhì)檢流程 -> 請(qǐng)求質(zhì)檢項(xiàng)輔助(某個(gè)功能,例如指紋是否正常)-> Java 調(diào)用底層(Dll、Wmi 等其他方式)-> 返回輔助質(zhì)檢結(jié)果 -> 頁面回傳渲染質(zhì)檢項(xiàng)支持結(jié)果。
3.2.3 流程釋義
- 執(zhí)行鏈:考慮到電腦某項(xiàng)屬性需要多種方式獲取并互相就糾正,因此可以針對(duì)性的配置其特有的執(zhí)行鏈,以達(dá)到更好的讀取準(zhǔn)確率,也更加方便擴(kuò)展。
- 數(shù)據(jù)糾錯(cuò):某些屬性比如電池健康值;默認(rèn)采取 WMI 讀取;但是部分廠商沒有按照 WMI 的標(biāo)準(zhǔn)寫值,導(dǎo)致獲取為空;因此需要調(diào)取 DLL(C)的其他獲取方式作為補(bǔ)充。
3.3 項(xiàng)目呈現(xiàn)
3.3.1 錄入模塊
通過 Java、C/C++讀取筆記本關(guān)鍵信息,獲取筆記本基本情況。通過錄入功能,輔助一線人員選擇系統(tǒng)標(biāo)品項(xiàng),同時(shí)與質(zhì)檢碼進(jìn)行關(guān)聯(lián)入庫。在此基礎(chǔ)上產(chǎn)生原始信息與標(biāo)品 ID 的映射關(guān)系,減少下次相同機(jī)型的一個(gè)操作步驟,方便一線操作人員在質(zhì)檢相同機(jī)型的一個(gè)操作便攜性。
筆記本錄入模塊
3.3.2 質(zhì)檢模塊
通過品牌機(jī)型獲取系統(tǒng)對(duì)應(yīng)的質(zhì)檢模版,提供自動(dòng)&輔助質(zhì)檢能力,協(xié)助一線質(zhì)檢人員對(duì)筆記本的質(zhì)檢能力更快捷、精準(zhǔn)。
4 總結(jié)
本文對(duì)轉(zhuǎn)轉(zhuǎn)質(zhì)檢的 EJC 架構(gòu)做了一些分享,并給出一些實(shí)踐經(jīng)驗(yàn),希望能為大家解決類似的問題提供一些幫助。目前 EJC 架構(gòu)體系已經(jīng)在質(zhì)檢業(yè)務(wù)中上線了多個(gè)桌面應(yīng)用并穩(wěn)定運(yùn)行,未來將會(huì)覆蓋更多的應(yīng)用場(chǎng)景,助力業(yè)務(wù)得到實(shí)質(zhì)發(fā)展。
5 參考鏈接
- ??https://www.electronjs.org??
- ??http://java-native-access.github.io/jna/5.13.0/javadoc/overview-summary.html#overview_description??
- ??https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp914??
- ??https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events??