鸿蒙入门开发教程:一文带你详解工具箱元服务的开发流程
一,基本概念
元服务(原名原子化服务)是一种基于 HarmonyOS API 的全新服务提供方式,以 HarmonyOS 万能卡片等多种呈现形态,向用户提供更轻量化的服务。具有即用即走、信息外显、服务直达的特性。
万能卡片(简称卡片)是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。
ArkUI 框架是一套构建分布式应用界面的声明式 UI 开发框架,其使用极简的 UI 信息语法、丰富的 UI 组件、以及实时界面预览工具,帮助开发者提升 HarmonyOS 应用界面开发效率。
AppGallery Connect(简称 AGC)致力于为应用的创意、开发、分发、运营、经营各环节提供一站式服务,构建全场景智慧化的应用生态体验。
端云一体化是为丰富 HarmonyOS 对云端开发的支持、实现 HarmonyOS 生态端云联动,DevEco Studio 推出了云开发功能,开发者在创建工程时选择云开发模板,即可在 DevEco Studio 内同时完成 HarmonyOS 应用/服务的端侧与云侧开发。
二,主要功能
本次开发的实用小工具主要功能有:
1.油价查询,点击城市列表之后,就可以查看当前城市的油价,包括 92,95,98,柴油等。
2.垃圾分类查询,输入物品名称就可以查看垃圾的详细分类,而不再害怕垃圾分类出错。
3.当前定位查看,我们可以很方便的知道自己当前的位置信息。
4.提供 12,22,24,44 卡片。
API 版本:API9
应用包名:com.jianguo.utilitybox
三,项目初始化
首先创建项目,选择原子化服务,第四个模版端云一体化模版
点击下一步
这个时候我们需要为工程关联云开发所需的资源,即在 DevEco Studio 中选择您的华为开发者账号加入的开发者团队,将该团队在 AGC 的同包名应用关联到当前工程。
然后我们点击登陆就好
这个时候会来到下面的授权页面,我们点击允许就可以。
单击“AppGallery Connect”打开 AGC 应用创建向导,填写应用信息,单击“确认”按钮创建应用
我们点击箭头这儿就会跳转到我们的
agc 页面。这个时候我们选择创建项目,然后再创建应用。我之前已经有项目了。所以我们直接在项目里创建应用就可以
比如我这里就选择
https://developer.huawei.com/consumer/cn/service/josp/agc/index.html#/myProject?appPackageName=com.jianguo.utilitybox&isAtomic=1
下面这个就可以
然后就会跳转到设置位置处理页面
我们点击下一步就会到下面的添加应用界面,然后我们只需要填写应用名称就可以,其他的都会默认,如果默认失败的话,我们就设置应用包名:com.jianguo.utilitybox
然后确定
这个时候就会有恭喜您!
应用创建已完成。
我们选择前往控制台,然后来到 Severless 这一块。把我们需要的认证服务,云函数,云数据库,云缓存,云存储,统统打开。
我们回到 IDE 继续开发,点击 Finsh 就可。
出现这个,我们 ok 就可以。
然后大家可以看到工程配置完成
成功创建工程并关联云开发资源后,DevEco Studio 会为工程自动执行一些初始化配置,并开通云开发相关服务:认证服务、云函数、云数据库、云托管、API 网关、云存储。,所以上面我们在控制台的操作其实也可以不用操作。
端侧工程自动集成 AGC SDK,包括 AGC SDK 配置文件entry/src/main/resources/rawfile/agconnect-services.json 和在entry/oh-package.json5配置文件中引入的 AGC 相关云服务最新版本 SDK。
"dependencies": { "@hw-agconnect/crypto-ohos": "^1.0.10", "@hw-agconnect/function-ohos": "^1.0.10", "@hw-agconnect/auth-ohos": "^1.0.10", "@hw-agconnect/cloudstorage-ohos": "^1.0.10", "@hw-agconnect/api-ohos": "^1.0.10", "@hw-agconnect/base-ohos": "^1.0.10", "@hw-agconnect/core-ohos": "^1.0.10", "@hw-agconnect/credential-ohos": "^1.0.10", "@ohos/agconnect-auth-component": "^1.0.5", "long": "5.2.1" }
复制代码
云侧工程自动集成云数据库最新版本 Node.js Server SDK。
/Users/jianguo/Desktop/teaching/utilitybox/CloudProgram/cloudfunctions/idgenerator/package.json
"dependencies": { "@agconnect/database-server": "^1.0.7" }}
复制代码
四,端云一体化开发
大家都知道我这次用的是端云一体化开发,那那么我们就有必要来了解一下端云协同的目录结构
端云一体化开发工程目录分为三个子工程:如下图所示:
端开发工程(Application)、
云开发工程(CloudProgram)、
端侧公共库(External Libraries)。
4.1 端开发工程(Application)
端开发工程主要用于开发应用端侧的业务代码,端开发工程目录结构如下:
端侧的目录和之前大家看到的文件目录结构都差不多
- Application - AppScope app.json5 // 应用的全局配置信息 - entry // 应用/服务模块,编译构建生成一个HAP oh_modules // 用于存放三方库依赖信息 - src/main - ets // 用于存放ArkTS源码 - resources // 用于存放应用/服务所用到的资源文件 module.json5 // Stage模型配置文件 build-profile.json5 // 当前模块信息、编译信息配置项 hvigorfile.ts // 模块级编译构建任务脚本 oh-package.json5 // 配置三方包声明的入口及包名 build-profile.json5 // 应用配置信息,包括签名、产品配置等 hvigorfile.ts // 应用级编译构建任务脚本
复制代码
4.2 云开发工程(CloudProgram)
云开发工程中开发者可以为应用开发云函数和云数据库服务资源,云开发工程目录结构如下:
- CloudProgram - clouddb // 云数据库工程目录 dataentry // 用于存放数据条目文件 objecttype // 用于存放对象类型文件 db-config.json // 模块配置文件 - cloudfunctions // 云函数工程目录 node_modules // 包含所有三方依赖 cloud-config.json // 云开发工程配置文件 package.json // 定义了TypeScript公共依赖
复制代码
五,云函数开发指南
5.1 创建函数
在云端工程(CloudProgram)中可以创建函数、编写函数业务代码、为函数配置调用触发器。
1.单击“cloudfunctions”目录,选择“New > Cloud Function”创建云函数。
2.输入函数名称,单击“OK”按钮 DevEco Studio 自动生成函数目录。函数名称仅支持小写英文字母、数字、中划线(-),首字母必须为小写字母,结尾不能为中划线(-)。
比如 add。
3.云函数目录结构。
- add node_modules // 自动为该函数引入依赖包 function-config.json // 函数的配置文件,可配置触发器,通过触发器暴露的触发条件来实现函数调用。 package.json // 包含了当前函数的名称、版本等函数元数据。 add.ts // 函数入口文件
复制代码
4.云函数触发器
云函数触发器在function-config.json文件中triggers属性中配置,当前支持 HTTP 触发器、CLOUDDB 触发器、AUTH 触发器、CLOUDSTORAGE 触发器、CRON 触发器五种。
{ "handler": "IdGenerator.myHandler", "triggers": [ { "type": "http", "properties": { "enableUrlDecode": true,//通过HTTP触发器触发函数,对于contentType为“application/x-www-form-urlencoded”的触发请求,是否使用URLDecoder对请求body进行解码再转发到函数中。true:启用。false:不启用。 "authFlag": "true",//是否鉴权,默认为true。 "authAlgor": "HDA-SYSTEM",//鉴权算法,默认为HDA-SYSTEM。 "authType": "apigw-client",//HTTP触发器的认证类型。apigw-client:端侧网关认证,适用于来自APP客户端侧的函数调用。 cloudgw-client:云侧网关认证,适用于来自APP服务器侧的函数调用 "mode": "NO_PATH" } } ] }
复制代码
5.2 开发云函数
云函数的代码实现基于不同的语言运行环境可分为 Node.js、Java、Python,还有一种比较特别运行环境为 Custom Runtime(自定义运行环境)。本工程的语言运行环境为 Node.js。
1.云函数的入口方法:
module.exports.myHandler = function(event, context, callback, logger)
myHandler:入口方法名称。
event:调用方传递的事件对象,JSON 格式。
context:函数运行时上下文对象,封装了日志接口、回调接口、环境变量 env 对象等。
callback:事件处理结果。
logger:记录日志。开发者在代码中使用 logger 接口记录日志,当前支持四种级别。
logger.debug()
logger.error()
logger.warn()
logger.info()
函数必须通过显示调用 callback(object)将事件处理结果返回给 AGC,结果可以是任意对象,但必须与 JSON.stringify 兼容,AGC 会将结果转换成 JSON 字符串,返回给调用方。callback 执行完成后,函数即执行结束。
2.为云函数添加返回内容
let myHandler = async function (event, context, callback, logger) { logger.info(event);
// do something here
callback({ code: 0, desc: "Success." });};
export { myHandler };
复制代码
3.调试云函数 函数开发过程中,开发者可在本地进行调试,或者将函数部署到 AGC 云端后,在本地触发调用云端函数。当前本地调试支持 Run 和 Debug 两种模式,Debug 模式支持使用断点来追踪函数的运行情况。
查看 Run 面板,若出现“Cloud Functions loaded successfully”,标识云函数启动成功(云函数启动/调试将部署 cloudfunctions 中所有的云函数),并生成对应的 POST URL。
在菜单栏选择“Tools > CloudDev > Cloud Functions Requestor”触发云函数调用
在弹出的云函数调用界面填写触发事件参数。
Environment:选择函数调用环境,Local 表示本地调用,Remote 表示远程调用(需要先将函数部署到 AGC 云端)。
Cloud Function:选择需要触发的云函数。
Event:输入事件参数,内容为 JSON 格式请求体数据。
单击 Trigger 按钮,触发执行云函数,执行结果展示在 Result 框内,Run 面板同时打印运行日志。
部署云函数 完成函数代码开发后,开发者可将函数部署到 AGC 控制台,支持单个部署和批量部署。
登录 AGC 控制台,进入当前项目的云函数服务菜单,可查看开发者部署的函数。
远程函数测试 在“Cloud Functions Reuestor”面板中,更改 Environment 为 Remote 远程调用,单击“Trigger”按钮,在 Result 中显示返回结果。
当开发者创建的函数或函数别名中创建一个 HTTP 类型的触发器后,在应用客户端调用函数时需要传入 HTTP 触发器的标识,查询方法如下: 在函数的触发器页面点击“HTTPTrigger”触发器,查看“触发 URL”的后缀,获取触发器标识,格式为“函数名-版本号”。
5.3 调用云函数
应用集成云函数 SDK 后,可以在应用内直接通过 SDK API 调用 AGC 中的云函数,云函数 SDK 与 AGC 的函数调用基于 HTTPS 的安全访问。
Button('请求自定义云函数') .fontSize(16) .onClick(() => { getSudokuPuzzle(getContext(this)).then((ret) => { Log.info(Constants.LOG_TAG_NAME, `单击按钮调用云函数返回结果: ${JSON.stringify(ret)}`) }) })
复制代码
然后我们签名,在真机中
六,华为认证服务
当前 AGC 认证服务为 HarmonyOS 应用/服务提供的登录认证方式有手机、邮箱和关联账号三种方式。本工程使用“邮箱+验证码”的方式作为应用的登录入口。我们这次使用的是手机号码登录
需要在 AGC 控制台开通认证服务(工程创建时默认开通),并在“认证方式”页签中启用“邮箱地址”。
调用PhoneUserBuilder生成PhoneUser,然后调用PhoneAuthProvider.credentialWithVerifyCode注册用户。注册成功后,系统会自动登录,无需再次调用登录接口。也可以使用 signIn 登录接口,通过第三方认证来登录 AGConnect 平台,在entry/src/main/ets/services/Auth.ts认证工具类中添加邮箱账号注册用户方法。
public async login(countryCode: string, phoneNumber: string, verifyCode: string): Promise<AgUser> { return new Promise((resolve, reject) => { const credential = PhoneAuthProvider.credentialWithVerifyCode(countryCode, phoneNumber, verifyCode); this.agc.auth().signIn(credential).then(async (ret) => { Log.info(TAG, "User has signed in.."); // @ts-ignore let user = ret.getUser(); let userExtra = await ret.getUser().getUserExtra();
let loginRes = new AgUser( user.getUid(), user.getPhotoUrl(), user.getPhone(), user.getDisplayName(), userExtra.getCreateTime(), userExtra.getLastSignInTime())
resolve(loginRes); }).catch((error) => { Log.error(TAG, "Error: ", error); reject(error); }); }); }
复制代码
6.1 构建手机登录页面
通过容器组件 Flex、Row、Column 以及基础组件 Text、Image、Button、Navigation、TextInput,构建手机验证码登录页面。
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { Text($r('app.string.auth_dialog_title')) .fontSize($r('app.float.navigation_font_size')) .margin({ bottom: Constants.LENGTH_20_PX })
Flex({ direction: FlexDirection.Row }) {
Select(mockData) .font({ size: $r('app.float.body_font_size') }) .selectedOptionFont({ size: $r('app.float.body_font_size') }) .optionFont({ size: $r('app.float.body_font_size') }) .value(this.countryCode) .layoutWeight(Constants.LENGTH_1_PX) .backgroundColor($r('app.color.placeholder_background')) .borderRadius(Constants.BORDER_RADIUS_4_PX) .height(Constants.HEIGHT_40) .onSelect((_, val) => { this.countryCode = val.substring(0, val.length - 4); })
TextInput({ placeholder: $r('app.string.auth_dialog_number_placeholder') }) .layoutWeight(Constants.LENGTH_3_PX) .margin({ left: Constants.LENGTH_5_PX }) .borderRadius(Constants.BORDER_RADIUS_4_PX) .maxLength(Constants.LENGTH_20_PX) .height(Constants.HEIGHT_40) .enabled(this.timer === 60) .onChange((val) => { this.phoneNumber = val; })
} .margin({ bottom: Constants.LENGTH_20_PX })
Flex({ direction: FlexDirection.Row }) { TextInput({ placeholder: $r('app.string.auth_dialog_code_placeholder'), text: this.verificationCode }) .layoutWeight(Constants.LENGTH_3_PX) .margin({ right: Constants.LENGTH_5_PX }) .borderRadius(Constants.BORDER_RADIUS_4_PX) .maxLength(Constants.LENGTH_6_PX) .height(Constants.HEIGHT_40) .onChange((val) => { this.verificationCode = val; })
Button(this.timer === 60 ? $r('app.string.auth_dialog_get_code_button_text') : this.timer.toString(), { type: ButtonType.Normal }) .backgroundColor($r('app.color.start_window_background')) .layoutWeight(Constants.LENGTH_2_PX) .borderColor($r('app.color.action_button_background')) .borderWidth(Constants.LENGTH_1_PX) .fontColor($r('app.color.action_button_background')) .borderRadius(Constants.BORDER_RADIUS_4_PX) .margin({ left: Constants.LENGTH_5_PX }) .height(Constants.HEIGHT_40) .enabled(this.canGetCode() && this.timer === 60) .onClick(() => this.onGetCodeButtonClicked()) } .margin({ bottom: Constants.LENGTH_20_PX })
Button($r('app.string.auth_dialog_auth_button_text'), { type: ButtonType.Normal }) .width(Constants.PERCENT_100) .borderRadius(Constants.BORDER_RADIUS_4_PX) .backgroundColor($r('app.color.action_button_background')) .enabled(this.canAuthorize() && this.verificationCode.length > 5 && this.canLogin) .opacity(this.canLogin ? 1 : 0.5) .height(Constants.HEIGHT_40) .onClick(() => this.onAuthButtonClicked())
} .height(Constants.PERCENT_50) .padding({ right: Constants.LENGTH_20_PX, left: Constants.LENGTH_20_PX }) }
复制代码
6.2 用户登录成功信息写入缓存
调用自定义的登录接口实现登录,并使用首选项自定义工具接口将用户信息写入缓存。,这样下次进入之后,就可以进行对应的逻辑判断。执行对应的流程。
onAuthButtonClicked() { this.canLogin = false; this.agcAuth.login(this.countryCode, this.phoneNumber, this.verificationCode).then(user => { AppStorage.Set<AgUser>('user', user); PreferencesUtil.putPreference(getContext(this), Constants.USER_AUTH_INFO, JSON.stringify(user)); Log.info(TAG, "Logged in succesfully."); this.canLogin = true; router.replaceUrl({ url: "pages/MainPage" }) }).catch((err) => { this.canLogin = true; Log.error(TAG, "Logged in failed " + JSON.stringify(err)); AlertDialog.show({ title: $r('app.string.common_prompt'), message: $r('app.string.common_login_fail'), }); }); }
复制代码
七,元服务开发
元服务 (原为"原子化服务") 是一种基于 HarmonyOS API 的全新服务提供方式,以鸿蒙万能卡片等多种呈现形态, 向用户提供更轻量化的服务。具有即用即走、信息外显、服务直达的特性。
万能卡片(以下简称“卡片”)是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。卡片常用于嵌入到其他应用(当前卡片使用方只支持系统应用,如桌面)中作为其界面显示的一部分,并支持拉起页面、发送消息等基础的交互功能。
下面是我们开发卡片时候的实现原理
这里涉及的几个知识点给大家介绍一下
卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
卡片提供方:提供卡片显示内容的应用,控制卡片的显示内容、控件布局以及控件点击事件。
卡片管理服务:用于管理系统中所添加卡片的常驻代理服务。
卡片渲染服务:用于管理卡片渲染实例,渲染实例与卡片使用方上的卡片组件一一绑定。
ArkTS 卡片提供了postCardAction()接口用于卡片内部和提供方应用间交互,当前支持 router、message 和 call 三种类型的事件,仅在卡片中可以调用。这个我们在查询油价,以及处理 message 跳转的时候会用到。大家继续往后看就可以。
7.1 油价查询(2*2)
这里我们需要学习的知识点在于卡片的创建,以及卡片数据如何交互。
创建 ArkTS 卡片有两种方式:
使用第二种方式创建卡片,在”entry > src/main/ets > widget > pages“目录右键单击“New > ArkTS File”创建RubbishWidgetCard.ets文件,接着打开"entry > src/main/resources > base > profile"目录下的form_config.json文件,配置名称为rubbish的 2*2 卡片
这样的话,我们的卡片就算创建完成了,
Column() { Row() { Image($r("app.media.rubbish")).width(this.Image_WIDTH_PERCENT).margin({ right: 5 }) Text(this.TITLE) .fontSize($r('app.float.font_size')) .fontWeight(FontWeight.Bold) .fontColor($r('app.color.item_title_font')) } } .width(this.FULL_WIDTH_PERCENT) .backgroundColor($r("app.color.emuiLow4_alpha"))
.height(this.FULL_HEIGHT_PERCENT) .justifyContent(FlexAlign.Center) .onClick(() => { postCardAction(this, { "action": this.ACTION_TYPE, "abilityName": this.ABILITY_NAME, "params": { "targetPage": this.MESSAGE } }); }) }
复制代码
但是要实现卡片数据的更新
ArkTS 卡片框架提供了 updateForm()接口和 requestForm()接口主动触发卡片的页面刷新。
卡片中需要使用@LocalStorageProp装饰器接收。
@LocalStorageProp("puzzles") puzzles: Array<Array<number>> = [];@LocalStorageProp("answers") answers: Array<Array<Array<number>>> = [];
复制代码
然后,我们以更新深圳的天气为例,在/src/main/ets/entryformability/EntryFormAbility.ts 写写对应的请求事件。
upData(formId :string){ let formInfo = formBindingData.createFormBindingData({ 'text': '刷新中...' }) formProvider.updateForm(formId, formInfo) console.info('Result:的结果' + JSON.stringify(formId)); let httpRequest = http.createHttp(); httpRequest.request(Constants.OILSERVER, { method: http.RequestMethod.GET, readTimeout: Constants.HTTP_READ_TIMEOUT, connectTimeout: Constants.HTTP_READ_TIMEOUT , extraData: { 'province': "深圳", 'app_id': Constants.APPID, 'app_secret': Constants.APPSECRET
}, }, (err, data) => { if (!err) { // data.result为HTTP响应内容,可根据业务需要进行解析 console.info('Result:的结果' + JSON.stringify(data.result)); let OilModel: OilModel = JSON.parse(data.result.toString()) // 注意:FormExtensionAbility在触发生命周期回调时被拉起,仅能在后台存在5秒 // 建议下载能快速下载完成的小文件,如在5秒内未下载完成,则此次网络图片无法刷新至卡片页面上 let formData = { 'oil': OilModel.data, 'loaded': true, "time": getTime()
}; let formInfo = formBindingData.createFormBindingData(formData) formProvider.updateForm(formId, formInfo).then((data) => { console.info('FormAbility updateForm success.' + JSON.stringify(data)); }).catch((error) => { console.error('FormAbility updateForm failed: ' + JSON.stringify(error)); }) // 当该请求使用完毕时,调用destroy方法主动销毁 httpRequest.destroy(); } else { console.info('error:' + JSON.stringify(err)); // 取消订阅HTTP响应头事件 httpRequest.off('headersReceive'); // 当该请求使用完毕时,调用destroy方法主动销毁 httpRequest.destroy(); } }
); }
复制代码
然后我们在 onUpdateForm 中去调用就可以啦。
onUpdateForm(formId) { this.upData(formId)
// Called to notify the form provider to update a specified form. }
复制代码
这样我们就完成了对油价的查询。
这里我们主要了解的就是对卡片事件能力的处理。
接口定义:postCardAction(component: Object, action: Object): void
接口参数说明:
action 参数说明:
7.1.1 通过 message 事件刷新卡片内容
实现功能:在卡片上实现当前城市的油价查询,在页面里实现对各个城市油价的查询
主要技术,通过 message 事件刷新卡片内容,在卡片页面中可以通过 postCardAction 接口触发 message 事件拉起 FormExtensionAbility,然后由 FormExtensionAbility 刷新卡片内容,展示油价信息
.onClick(() => { postCardAction(this, { 'action': 'message', 'params': { 'info': 'refreshImage' } }); })
复制代码
7.2 垃圾分类(1*2)
这里实现的功能是点击卡片之后,进入垃圾分类查询页的详情。输入物品名称。就可以查看具体的垃圾分类信息。
7.2.1 使用 router 事件跳转到指定 UIAbility
.onClick(() => { postCardAction(this, { 'action': 'router', 'abilityName': 'EntryAbility', "params": { "targetPage": this.MESSAGE } });})
复制代码
然后我们在 src/main/ets/entryability/EntryAbility.ts 中处理对应的逻辑。
onCreate(want) {
console.info("onCreate want:" + JSON.stringify(want)); if (want.parameters.params !== undefined) { let params = JSON.parse(want.parameters.params); hilog.info(0x0000, 'tonCreate router targetPage', '%{public}s', params.targetPage) console.info("onCreate router targetPage:" + params.targetPage); selectPage = params.targetPage; }}
复制代码
如果 UIAbility 已在后台运行,在收到 Router 事件后会触发 onNewWant 生命周期回调
onNewWant(want, launchParam) { console.info("onNewWant want:" + JSON.stringify(want)); if (want.parameters.params !== undefined) { let params = JSON.parse(want.parameters.params); hilog.info(0x0000, 'tonCreate router targetPage', '%{public}s', params.targetPage)
console.info("onNewWant router targetPage:" + params.targetPage); console.info("获取的 params.targetPage是" + params.targetPage); if (params.dimension !== undefined && params.dimension != null) { AppStorage.Set<string>('message', params.dimension); console.info("保存的 params.targetPage是" + params.targetPage); }
selectPage = params.targetPage;
} if (currentWindowStage != null) { this.onWindowStageCreate(currentWindowStage); }}
复制代码
根据传递的 targetPage 不同,选择拉起不同的页面
onWindowStageCreate(windowStage: Window.WindowStage) { let targetPage;
switch (selectPage) { case "MainPage": targetPage = "pages/MainPage"; break; default: targetPage = 'pages/LoginPage'; } if (currentWindowStage === null) { currentWindowStage = windowStage; } windowStage.loadContent(targetPage, (err, data) => { if (err && err.code) { console.info('Failed to load the content. Cause: %{public}s', JSON.stringify(err)); return; } });}
复制代码
这样我们就完成了跳转。
7.3 当前定位(2*2)
点击卡片之后,进入定位展示的详情页,展示当前的详细定位。
主要技术,动态权限的获取。
7.3.1 动态权限获取
首先需要在 module.json 中声明对应权限
"requestPermissions": [ { "name": "ohos.permission.INTERNET" }, { "name": "ohos.permission.MEDIA_LOCATION", }, { "name": 'ohos.permission.APPROXIMATELY_LOCATION' }, { "name": 'ohos.permission.LOCATION' } ],
复制代码
然后在 ets/entryability/EntryAbility.ts 中添加动态权限的申请
let AtManager = abilityAccessCtrl.createAtManager(); AtManager.requestPermissionsFromUser(this.context, [ 'ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION']).then((data) => { hilog.info(0x0000, 'testTag', '%{public}s', 'request permissions from user success' + data); }).catch((err) => { hilog.error(0x0000, 'testTag', 'Failed to request permissions from user. Cause: %{public}s', JSON.stringify(err) ?? ''); }); hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
复制代码
然后我们编写卡片代码
Column() { Text("你想知道") .fontSize($r('app.float.title_immersive_font_size')) .textOverflow({ overflow: TextOverflow.Ellipsis }) .fontColor($r('app.color.text_font_color')) .maxLines(this.MAX_LINES) Text("你当前的位置吗") .fontSize("14fp") .textOverflow({ overflow: TextOverflow.Ellipsis }) .fontColor($r('app.color.text_font_color')) .maxLines(this.MAX_LINES) } .alignItems(HorizontalAlign.Center) .justifyContent(FlexAlign.Center) .padding($r('app.float.column_padding'))
.width(this.FULL_WIDTH_PERCENT) .height(this.FULL_HEIGHT_PERCENT) .backgroundColor($r('app.color.emuiLow2_alpha')) .onClick(() => { postCardAction(this, { "action": this.ACTION_TYPE, "abilityName": this.ABILITY_NAME, "params": { "targetPage": this.MESSAGE } }); }) }
复制代码
运行之后的效果
八,总结
本文我们从元服务的基本概念,到我们实用小工具的项目开发,技术剥析,完整的了解了端元一体化开发元服务,以及如何在元服务中集成华为认证服务,到最后完成油价查询功能在卡片上及时显示,定位在卡片上实时刷新。以及点击卡片分类,还可以进入详情页查询更多物品的垃圾属性,以及动态权限的获取。
你可以学到的有
1.了解元服务的基本概念
2.使用端云一体化开发、开发云函数、开发云数据库,集成华为认证服务。
3.使用 FormExtensionAbility 创建、更新、删除元服务卡片。
4.使用 router、message 和 call 三种类型的事件,处理对应逻辑。
也可点击元服务官网,了解更多信息。
ARTS 打卡计划
评论