质检是转转履约体系中的重要一环,通过对手机、平板、笔记本、耳机、手表等品类商品的软硬件功能、外观成色等进行全方面检测,为买家、卖家把好质量关,让二手交易变得更透明、更靠谱,促进绿色消费。
在质检环节中,通过标准化产线,结合自动化设备、质检 APP、桌面应用程序等,最终输出全面可信的“质检报告”呈现给用户。其中,桌面应用程序发挥着举足轻重的作用,本文将重点介绍桌面应用程序架构的演进及其落地。
1 背景
转转质检的桌面应用程序,前期主要由 Qt 构建,C/C++提供底层支持。这些桌面应用的视图层、应用层、以及底层能力支持,均由 C/C++开发人员承担全部开发迭代工作。其次随着业务的不断发展,部分桌面应用程序逐渐暴露出拓展性差、迭代难的问题。
在转转质检技术团队,除了 C/C++开发同学外,还有配套成熟的前端和 Java 后端,综合来说:对于视图层,前端的技术生态、以及开发人员的技术经验,在视图层开发方面具有很大的优势;在应用层方面,Java 技术生态的优势不言而喻,同时 Java 后端同学对整体业务和系统都有着相对全面深入的了解,简而言之,Java 同学在应用层架构设计和落地方面,是非常合适的。
综上,基于团队实际,笔者团队对桌面应用程序,提出了新的技术架构——EJC(Electron、Java、C/C++)。该架构的主要优势在于:
- 让 C/C++开发同学更偏向于底层能力的研究,发挥更大的价值。
- Electron 本质是前端技术栈,Java 同学更理解整体业务,在应用层设计经验方面更擅长;且前端和 Java 资源更容易灵活调配。
- Electron 和 Java 本身是跨端的,对于后续质检桌面应用各端的整合(Windows&Mac),具有不错的优势。
2 EJC 架构
简单说,EJC 技术架构即:Electron(视图层/用户层),Java(应用层),C/C++(基础能力层)。
2.1 Electron
Electron 是一个基于 Chromium 和 Node.js 的框架,一套多端生成 Windows、macOS 和 Linux 的跨平台桌面应用程序。
- 本质是前端技术栈,内置了 Chromium 内核使得应用程序具有最新的 web 标准,开发人员可以专注应用程序的逻辑和界面设计,不用再束手束足做浏览器兼容性操作。
- 整包更新和热更新,使程序保持最新的状态,类似混合移动应用(Hybrid APP)的快感。
- 安全的跨平台运行环境,可以有效地降低程序崩溃和系统错误的机率,实现更加可靠和稳定的应用程序。
- 丰富的 API 在 C/C++能力的加持下,让软硬件结合更加丝滑,扩展能力更进一步。
2.2 Java
Java 应用层,主要包括:
- 通讯模块:提供基于 HTTP、WebSocket 等协议的通信能力。
- 底层交互模块: 封装了 Java 调用本地代码(动态库)的技术(JNI/JNA)。
- 数据存储:使用轻量级数据库 SQLite 来持久化数据,为数据的高可用及容错提供基础能力。
- 事件监听:基于 Spring 的事件和监听机制实现了基于事件驱动的编程模型,有松耦合、高扩展性和可测试性等优点。
- 业务模块:必要的业务逻辑处理。
- 监控模块:对客户端的实时运行参数、硬件异常情况等数据进行记录,定时上报云端。
- 配置管理:定时从云端拉取最新的配置,覆盖本地配置。
- 调度策略:根据设备的历史状态,预判硬件(如货架货位上的 USB 通讯口、USB 集线器等设备)是否出现故障,当判定故障时,可以优先调度其它设备并将故障信息上报。
2.3 C/C++
基础能力层,核心 SDK 的实现。提供与 Windows、IOS、安卓、相机、机械臂等底层通用能力。
基于上述说明,在转转质检中,笔者呈现的 EJC 技术架构,如下:
EJC架构图
下面重点对 Java 应用层的前端通讯模块、底层通讯模块进行介绍。
2.4 前端通讯模块
在 Java 应用层兼容了 HTTP 协议、WebSocket 协议的通信方式。接下来介绍几种与前端通讯的方案以及我们在 EJC 架构中的选型和考量:
2.4.1 HTTP 短轮询
客户端周期性的向服务器发送请求,以获得最新的数据,这种方式会造成服务器和网络资源的浪费。适用于实时性要求不高的场景:在 Java 客户端从云端拉取配置时,采用的就是此机制。
2.4.2 HTTP 长轮询
与 HTTP 短轮询相比,HTTP 长轮询能够避免客户端频繁向服务器发送请求,节省了网络和服务器资源的开销,同时能实现更及时和可靠的数据推送。
2.4.3 SSE(Server-Sent Events)
本质上是一个 HTTP 长连接,服务端发送给客户端不是一个数据包,而是一个 stream 流,格式为 text/stream,所以客户端不会关闭连接,会一直等着服务器发过来新的数据流。适合一些只需要服务端单向推送事件给客户端的场景。
在实际的应用场景中,服务端只需要推送一次信息给前端(如:前端调用 Java 服务端获取系统硬件配置信息),我们选择 SSE 作为前后端的通信方式,有以下优势:
- 比 http 短轮询性能更好。
- 比 http 长轮询更可靠。
- 比 WebSocket 更轻量。
- 可以在现有的基础设施和技术上使用,而不需要进行任何额外的配置或部署。
2.4.4 WebSocket
WebSocket 是基于 TCP 的双向通信协议,可以实现实时通信。适合实时性要求很高而且需要双工通信的系统。在实际的应用中,如隐私清除工具,从插入手机到隐私清除完成只需要 3~5 秒,质检人员需要实时的看到手机状态的变更,这时候我们选用 Websocket 实时的将数据状态推送到前端进行展示。
2.5 底层通讯模块
Java 调用 C/C++ 有 JNI (Java Native Interface) 与 JNA (Java Native Access) 两种方式,都是 Java 中用于调用本地底层 SDK 的技术。
下面通过简单的代码示例(获取 IOS 设备名称)来说明 Java 是如何调用底层 SDK 的。为了节约篇幅,仅展示了部分关键代码。
2.5.1 JNI 介绍和使用
Java 语言提供的标准接口,它提供了一组函数和数据类型,允许 Java 应用程序调用和被 C/C++ 语言调用。JNI 通过编写本地方法实现与 C/C++ 语言的交互。
- 使用 native 关键字声明本地方法。
- 通过 javah 命令,将代码中的 native 方法生成对应的 C 语言的头文件。
- 执行上述命令后,将生成一个名为 JniDemo.h 的 C/C++ 头文件。
- C/C++ 实现头文件来实现 Java_JniDemo_getDeviceNameByUDID 方法,并将其编译为动态库。
- Java 使用
2.5.2 JNA 介绍和使用
JNA 是在 JNI 基础上实现的编程框架,实现了 Java 类型到 C 类型的自动转换。Java 开发人员只要在一个 Java 接口中描述目标 native library 的函数与结构,不再需要编写任何 Native/JNI 代码,极大的降低了 Java 调用动态库的开发难度。
- 编写 C/C++代码,声明头文件(需要使用 extern “C”关键字才能被 JNA 调用)。
- 实现头文件,并将其编译为动态库。
- Java 中使用。
首先在项目中引入 JNA 库:
声明与动态库对应的 Java 接口类:
加载动态库并调用方法:
2.5.3 选型和考量
通过上述的示例代码我们对比了两种方案的优缺点,并进行了性能测试。
JNI | JNA | |
优点 | 本地方法编译后可以选择 C 或 C++ 来实现。调用本地方法时效率相对 JNA 高。 | 封装了系统常用的动态库,可以直接使用。开发效率相对 JNI 高,无需 Java 编写本地方法。 |
缺点 | 开发效率相对较低,需要 Java 编写本地方法并编译生成 C/C++头文件,C/C++ 需要按照生成的头文件进行编码实现。 | 不支持 C++编译生成的动态库,需要在 C++ 接口的上层用 C 语言进行一次封装。 |
从开发者的角度来说:JNA 对 Java 开发者比较友好,JNI 则对 C/C++开发者比较友好。
同时我们分别用 JNI 和 JNA 进行 100 次到 500 次读取 IOS 设备名称的性能测试,得到耗时对比。在 8 核 16G 机器上运行得到如下结果:
计算数量(百次) | JNI | JNA |
1 | 1197ms | 26957ms |
2 | 2196ms | 52800ms |
3 | 2759ms | 79260ms |
4 | 4573ms | 106377ms |
5 | 6299ms | 132482ms |
通过上面的对比和性能测试,我们制定了如下选型标准:
- 自研的 SDK:高优使用 JNI 作为底层通信方式。优势在于:JNI 的性能更好,底层数据交互的接口由 Java 定义,C/C++开发者可以选择 C 或 C++进行实现,有更多的选择性及灵活性。
- 外部厂商提供的 SDK:优先调用厂商自带的 SDK。优势在于:无需 C/C++再次封装一层动态库,减少开发资源的投入。
3 EJC 架构的落地
EJC 架构在转转质检已成功落地了多个应用,下面主要介绍 EJC 在 Windows 笔记本质检工具中的落地。
3.1 项目背景
随着质检业务的发展,笔记本质检量再创新高。早期由 C/C++开发、使用 Qt 构建的笔记本验机工具已不能满足业务的需求,主要体现有以下几点:
- 维护成本高:代码的复杂性较高,维护需要开发者投入更多的时间和精力。
- 覆盖率低:功能不够完善,易用性较差,使用覆盖率低。
- 移植性差:无法移植到 Mac 平台。
基于上述的项目背景,我们使用了 EJC 架构来重构笔记本验机工具。
3.2 架构实现
3.2.1 名词解释
- WMI:Windows Management Instrumentation;是 Windows 系统标准的信息服务。
- WinAPI:Windows 系统提供的底层接口。
- DLL(C):即 EJC 中的 C,由我们 C/C++的同学研发的底层 SDK。
3.2.2 流程描述
- 录入获取数据流程:Electron 启动后 -> 后台异步启动 Java 服务端 -> 开启异步全局扫描笔记本基本数据项注解 -> 获得需读取的电脑属性 -> 调度器分类执行属性获取命令 -> 执行获取命令执行链(可横向扩展获取方式)-> 数据加工 -> 数据纠错 -> 经 SSE 通道推送页面渲染。
- 质检流程辅助流程:进入质检流程 -> 请求质检项辅助(某个功能,例如指纹是否正常)-> Java 调用底层(Dll、Wmi 等其他方式)-> 返回辅助质检结果 -> 页面回传渲染质检项支持结果。
3.2.3 流程释义
- 执行链:考虑到电脑某项属性需要多种方式获取并互相就纠正,因此可以针对性的配置其特有的执行链,以达到更好的读取准确率,也更加方便扩展。
- 数据纠错:某些属性比如电池健康值;默认采取 WMI 读取;但是部分厂商没有按照 WMI 的标准写值,导致获取为空;因此需要调取 DLL(C)的其他获取方式作为补充。
3.3 项目呈现
3.3.1 录入模块
通过 Java、C/C++读取笔记本关键信息,获取笔记本基本情况。通过录入功能,辅助一线人员选择系统标品项,同时与质检码进行关联入库。在此基础上产生原始信息与标品 ID 的映射关系,减少下次相同机型的一个操作步骤,方便一线操作人员在质检相同机型的一个操作便携性。
笔记本录入模块
3.3.2 质检模块
通过品牌机型获取系统对应的质检模版,提供自动&辅助质检能力,协助一线质检人员对笔记本的质检能力更快捷、精准。
4 总结
本文对转转质检的 EJC 架构做了一些分享,并给出一些实践经验,希望能为大家解决类似的问题提供一些帮助。目前 EJC 架构体系已经在质检业务中上线了多个桌面应用并稳定运行,未来将会覆盖更多的应用场景,助力业务得到实质发展。
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