端云一体化,助力高考一分一段表元服务快速高效开发

1. 一分一段表元服务开发原因

做为高三就读孩子的家长,这两年对高考的志愿填报非常关注,希望在不浪费一分的情况下尽可能考上更合适的专业,要做到这一点很不容易,因为这不仅仅和孩子自己的分数有关,更和这个分数在全省的排名有关,需要了解到有多少考生可能构成竞争关系,比你分数低的不会和你竞争,比你分数高很多的也不会和你竞争,只有那些比你的分数高个十几二十分的人才会和你竞争,了解这个区间每个分数段的考生数量分布,对于合理填报志愿非常重要。当然,各个地区的招生办公室都会公布高考成绩的一分一段表,大部分是图片或者pdf格式的,可以人工去查,实际操作起来比较麻烦,为简化查找,决定自己开发一个这样的应用,恰好鸿蒙也在这个时间点发布了5.0版本,其中的元服务简直就是为这种类型的应用量身定制的,不需要安装,点开即用,简直太完美了,最终决定使用元服务的形式进行开发。

2. 为什么选择端云一体化开发

2.1. 数据量估算

确定开发形式后,就开始技术调研,首先估算数据量,以山东省为例,科目选择是3+3的形式,考试成绩除了有总分排名外,另外还有选考化学、物理、生物、思政、历史、地理的排名,也就是有7种分数排名,按照2022到2024年三年的夏季高考一分一段表数据量计算,总条数达到了11382条,这个数据量还会以每年7千条左右的速度增加。这只是山东省的,全国有31个高考省份,总数量可能会有数十万条,使用文件存储不太现实了,不但更新不方便,最后打包的大小也会超出元服务包2M的限制,所以,只能选择线上数据库的形式进行数据管理。

2.2. 数据库选型

决定使用数据库后,就要对数据库进行选型,无论是购买服务器安装数据库,还是直接租用数据库,都会产生较高的费用,维护工作量也会增加。幸好,华为应用市场应用一站式服务平台AppGallery Connect(AGC)提供了云数据库,可以方便的进行数据管理,而且鸿蒙的云开发服务Cloud Foundation Kit为此提供了全方位的支持,可以在鸿蒙应用中通过API直接操作数据库,开发效率直接飞起。最吸引人的还不止这些,云数据库可以免费试用 ,提供了充足的免费额度,对于一般的应用,免费额度用不完,根本用不完,额度明细如下图所示:

既然AGC这样慷慨,那我也就不客气了,直接选它作为数据库使用,在当前版本中设计了两个数据库表,一个存储分数类别,一个存储具体的分数信息,如图所示:

2.3. 首开速度的优化

在本应用的功能设计中,用户进入页面后会选择高考的地区、科目和年度,而且是联动选择的,也就是说,需要预先加载所有这些类别信息。问题在于,这些信息可能会有几百条数据,需要一定的下载时间,如果让用户等待时间过长,显然不太友好,特别是在用户第一次使用时,还要叠加元服务包本身的下载和安装时间,这个矛盾就更突出了。解决起来也不复杂,使用AGC提供的预加载服务即可,把需要在首页使用的主要信息通过预加载的形式加载到缓存中,用户首开页面时,直接通过缓存读取数据,从而极大地提高响应速度,优化用户体验,增加用户留存的概率。在具体的实现中,类别信息是存储在云数据库对象AreaScoreType中的,通过云函数get-all-score-type-list对该云数据库对象进行操作,读取所有的类别,转化为json字符串,然后预加载那里选择该函数作为实现预加载的形式。云函数和预加载的截图如下所示: 云函数:

预加载:

预加载需要使用云开发服务中的云函数模块cloudFunction,通过call接口实现预加载,本示例封装的预加载函数如下:

javascript 复制代码
export function functionPreload() {
 let promise = cloudFunction.call({
   name: "get-all-score-type-list", // 预加载缓存数据的云函数名称
   timeout: 3 * 1000, // 获取缓存数据的超时时间
   loadMode: cloudFunction.LoadMode.PRELOAD // 获取缓存数据必须设置为PRELOAD
 }
 );
 promise.then((data: cloudFunction.FunctionResult) => { // 接口调用成功处理缓存的应用数据
   hilog.info(0x0000, 'testTag', 'get preload cache successfully');
   AppStorage.setOrCreate('scoreTypeList', JSON.stringify(data.result));
 }).catch((err: Error) => {
   hilog.error(0x0000, 'testTag', 'fail to get preload cache: %{public}s', err.message);
   functionNormal(); // 使用普通方式获取应用数据
 })
}
复制

在EntryAbility的onCreate生命周期函数里调用此函数即可:

scss 复制代码
 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
   functionPreload()
 }
复制

这样,通过预加载就把分数类型信息序列化后的字符串存储到了全局变量scoreTypeList中。然后,在要使用该变量的页面中使用@StorageLink装饰该变量:

less 复制代码
@StorageLink('scoreTypeList') scoreTypeList: string = "";
复制

通过如下的方式进行使用:

javascript 复制代码
   //存储所有省区、科目、年度的列表
   let allAreaScoreTypeList: AreaScoreType[] = []

   //如果没有通过预加载获取到所有省区、科目、年度列表的字符串,直接从数据库读取
   if (this.scoreTypeList == undefined || this.scoreTypeList == "") {
     allAreaScoreTypeList = await findAllAreaScoreType()
   } else { //否则就从预加载的字符串解析
     allAreaScoreTypeList = JSON.parse(this.scoreTypeList) as AreaScoreType[]
   }
复制

2.4. 端云一体化的选择

上文解释了启用预加载的原因和大体流程,其实还是没有回答为什么要选择端云一体化的形式进行开发,我们接着讨论。上文提到了云函数get-all-score-type-list,该函数实现了对云数据库的访问,那么,具体的函数代码如何编写?如何上传到云端,如何进行调试?如果传统的方式,也是可以的,但是非常复杂,AGC里也有相关的文档,我也就不赘述了,但是,如果使用端云一体化的话,一切都变的简单高效了。按照AGC中关于端云一体化开发的文档进行操作,可以得到这样的HarmonyOS工程,包括两个项目,如下图所示:

工程上部为端侧的项目,下部为云侧的项目,端侧的和普通的HarmonyOS应用项目没什么区别,需要注意的是云侧的,它的结构和端侧是有区别的,运行环境为nodejs,代码扩展名为ts,本应用云函数的代码如下所示:

javascript 复制代码
import { CloudDbZoneWrapper } from './CloudDBZoneWrapper';

let myHandler = async function (event, context, callback, logger) {
 let cloudDBZoneWrapper = new CloudDbZoneWrapper();
 let result = await cloudDBZoneWrapper.queryScoreTypes();
 return callback(JSON.stringify(result));
};

export { myHandler };
复制

要部署函数到云端也非常简单,直接右键菜单单击Deploy"get-all-score-type-list"部署即可:

运行或者调试也一样简单,还是上面的右键菜单,然后单击"Run"或者"Debug"启动函数,再单击"视图"菜单"工具窗口"子菜单下的"Cloud Functions Requestor",弹出云函数请求窗口,如图所示:

选择要请求的函数,选择本地还是云端环境,然后输入触发事件需要的参数,再单击"Trigger"按钮即可获取调用结果,如图所示:

3. 一多特性的实现

现在的端侧机型越来越多,手机、平板、折叠屏都需要适配,而且还有横屏和竖屏的区别,如果每种情况都通过独立的页面实现,那开发工作量也太大了,维护也很不方便。鸿蒙针对这种情况提供了一多的支持,可以一次开发适配多种机型,极大地提高了应用开发的效率,本应用也利用了一多能力,实现了对手机、平板、折叠屏的适配,界面如下所示:

竖屏:

横屏:

平板:

折叠屏:

毕竟作者是个后端开发者,审美能力有限,也许界面有点简陋,大家重点关注下一多能力的技术实现方面。

本应用借鉴了官方示例中一多能力的代码,封装了OneMoreHelper工具类,可以计算屏幕基于宽度和高度的类别,代码如下:

ini 复制代码
import { display, window } from "@kit.ArkUI";

export function updateWidthBp(windowObj?: window.Window): void {
 if (windowObj === undefined) {
   return;
 }
 let mainWindow: window.WindowProperties = windowObj.getWindowProperties();
 let windowWidth: number = mainWindow.windowRect.width;
 let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels;
 let widthBp: string = '';
 if (windowWidthVp < 320) {
   widthBp = 'xs';
 } else if (windowWidthVp >= 320 && windowWidthVp < 600) {
   widthBp = 'sm';
 } else if (windowWidthVp >= 600 && windowWidthVp < 840) {
   widthBp = 'md';
 } else if (windowWidthVp >= 840 && windowWidthVp < 1440) {
   widthBp = 'lg';
 } else {
   widthBp = 'xl';
 }
 AppStorage.setOrCreate('currentWidthBreakpoint', widthBp);
}

export function updateHeightBp(windowObj?: window.Window): void {
 if (windowObj === undefined) {
   return;
 }
 let mainWindow: window.WindowProperties = windowObj.getWindowProperties();
 let windowHeight: number = mainWindow.windowRect.height;
 let windowWidth: number = mainWindow.windowRect.width;
 let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels;
 let windowHeightVp = windowHeight / display.getDefaultDisplaySync().densityPixels;
 let heightBp: string = '';
 let aspectRatio: number = windowHeightVp / windowWidthVp;
 if (aspectRatio < 0.8) {
   heightBp = 'sm';
 } else if (aspectRatio >= 0.8 && aspectRatio < 1.2) {
   heightBp = 'md';
 } else {
   heightBp = 'lg';
 }
 AppStorage.setOrCreate('currentHeightBreakpoint', heightBp);
}
复制

在EntryAbility的onWindowStageCreate生命周期函数进行调用:

kotlin 复制代码
   windowStage.getMainWindow().then((data: window.Window) => {
     this.windowObj = data;
     updateWidthBp(this.windowObj);
     updateHeightBp(this.windowObj);
     this.windowObj.on('windowSizeChange', (windowSize: window.Size) => {
       updateWidthBp(this.windowObj);
       updateHeightBp(this.windowObj);
     })
   })
复制

代码比较容易理解,就是在创建窗口和窗口大小变化时,重新计算宽度和高度分类,并记录到AppStorage的currentWidthBreakpoint和currentHeightBreakpoint中。在页面index.ets定义变量currentWidthBreakpoint和currentHeightBreakpoint,实时获取窗口宽高类型的变化:

less 复制代码
 @StorageLink('currentWidthBreakpoint') currentWidthBreakpoint: string = 'md';
 @StorageLink('currentHeightBreakpoint') currentHeightBreakpoint: string = 'sm';
复制

在定义组件的时候,通过这些变量实现一多界面的适配,以本应用两个柱状图和折线图的适配为例,代码如下所示:

kotlin 复制代码
     GridRow({
       columns: 12,
       gutter: { x: 5, y: 5 },
       direction: GridRowDirection.Row
     }) {
       //柱状图
       GridCol({
         span: {
           xs: 12,
           md: (this.currentHeightBreakpoint == "lg") ? 12 : 6
         }
       }) {
         McBarChart({
           options: this.barSeriesOption
         })
           .width("100%")
           .height("100%")
           .padding({
             left: ((this.currentHeightBreakpoint == "sm") && this.currentWidthBreakpoint != "xl") ? 15 : 0
           })
       }
       .height((this.currentHeightBreakpoint == "sm" || this.currentHeightBreakpoint == "md") ? "100%" : "50%")

       //折线图
       GridCol({
         span: {
           xs: 12,
           md: (this.currentHeightBreakpoint == "lg") ? 12 : 6
         }
       }) {
         McLineChart({
           options: this.lineSeriesOption
         })
           .width("100%")
           .height("100%")
       }
       .height((this.currentHeightBreakpoint == "sm" || this.currentHeightBreakpoint == "md") ? "100%" : "50%")
     }
     .width('100%')
     .height(250)
     .flexGrow(1)
复制

这样,只要明确了目标屏幕的分类,就可以很方便的进行适配。

4. 借助云测试保证应用质量

应用开发是一个牵涉到各个层面的复杂工程,在目前支持HarmonyOS 5.0的设备比较少的情况下,这一复杂性就更突出了,为了更好的服务于广大开发者,AGC提供了云测试能力,可以使用真机对兼容性、稳定性、性能、功耗、UX进行全方位测试,同样每天提供300分钟的免费额度,真是量大管饱。本应用的云测试效果如下所示:

云测试详情:

通过了云测试,这下提交应用上架的底气更足了!

5. 结语

HarmonyOS 5.0版本提供的开发能力非常强大,AGC在此基础上扩展了更多的功能性、易用性能力,特别是针对开发者开发过程中的痛点、难点,AGC提供的解决方案简直称得上完美,这里呼吁广大开发者,积极了解、合理利用AGC能力,为应用的开发、上架插上腾飞的翅膀。

相关推荐
前端菜鸟日常13 小时前
鸿蒙版(ArkTs) 贪吃蛇,包含无敌模式 最高分 暂停和继续功能
华为·harmonyos
鸿蒙布道师19 小时前
鸿蒙NEXT开发数值工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
塞尔维亚大汉1 天前
鸿蒙南向开发 ——轻量系统芯片移植指南(二)
物联网·嵌入式·harmonyos
别说我什么都不会1 天前
OpenHarmony内核系统调用hats测试用例编写指南
物联网·嵌入式·harmonyos
90后的晨仔1 天前
鸿蒙ArkTS是如何实现并发的?
harmonyos
鸿蒙布道师1 天前
鸿蒙NEXT开发日期工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
HMSCore1 天前
在应用内购票、寄件时,如何一键填充所需信息?
harmonyos
HarmonyOS_SDK1 天前
在应用内购票、寄件时,如何一键填充所需信息?
harmonyos
枫叶丹41 天前
【HarmonyOS Next之旅】DevEco Studio使用指南(十一)
华为·harmonyos·deveco studio·harmonyos next
H_MY1 天前
鸿蒙 —— 系统图标大全
harmonyos