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

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能力,为应用的开发、上架插上腾飞的翅膀。

相关推荐
塞尔维亚大汉8 小时前
移植案例与原理 - startup子系统之bootstrap_lite服务启动引导部件(1)
harmonyos
轻口味14 小时前
【每日学点鸿蒙知识】跳转三方地图、getStringSync性能、键盘避让模式等
华为·harmonyos
儿歌八万首14 小时前
鸿蒙ArkUI实现部门树列表
华为·harmonyos·鸿蒙·arkui
万少15 小时前
鸿蒙元服务实战-笑笑五子棋(5)
前端·harmonyos·canvas
塞尔维亚大汉16 小时前
移植案例与原理 - startup子系统之syspara_lite系统属性部件
操作系统·harmonyos
HarmonyOS_SDK20 小时前
“面面俱到”!人脸活体检测让应用告别假面攻击
harmonyos
SuperHeroWu71 天前
【HarmonyOS】鸿蒙应用点9图的处理(draw9patch)
华为·harmonyos·鸿蒙·image·图片拉伸·点9图·不变形
轻口味1 天前
【每日学点鸿蒙知识】多个har依赖、指定编译架构、ArkTS与C++互相调用
c++·华为·harmonyos
gkkk_11 天前
鸿蒙应用开发(2)
华为·harmonyos
HarmonyOS_SDK2 天前
多样化消息通知样式,帮助应用提升日活跃度
harmonyos