会Vue?HarmonyOs还难吗?

48 阅读27分钟

特此声明

文章中的图片与资料,一部分来自B站黑马教育视频的截图,一部分来自HarmonyOs官网的教学视频截图

API地址

ArkTS接口参考

组件参考(基于ArkTS的声明式开发范式)

官网基础课堂

官网开发者认证

安装包

状态管理装饰器

组件同步

当父子组件之间需要数据同步时,可以使用@Prop和eLink装饰器:

需要注意的是,被 @Prop 和 @Link 装饰后,不能在本地(当前组件方法中)做初始化

@Prop 支持单向同步

  • @Prop只支持string、 number、 boolean、 enum类型

  • 变量类型不可以是数组、any

  • 如果父组件@Sate装饰的是对象类型,则子组件只要装饰的是对象的子属性,也会被允许

     class StateInfo {
         message: string = 'hello'
     }
     ​
     @Entry
     @Component
     struct StateTaskList {
         @State state: StateInfo = new StateInfo()
         
         TaskTotalInfoComponent({ message: this.state.message })
     }
     ​
     //  子组件
     @Component
     struct TaskTotalInfoComponent {
         @Prop message: string
         
         build () {
             Text(this.message)
         }
     }
    
  • @Prop的实现相当于将父组件的值,拷贝一份到子组件,每次父组件的值被修改,都会拷贝一个新的值传递到子组件

     @Entry
     @Component
     struct StateTaskList {
         @State message: number = 0
         
         ChildComponent({ message: this.message })
     }
     ​
     @Component
     struct ChildComponent {
         @Prop totalTask: number
     }
    

@Link 支持双向同步

  • 父子类型一致:string、 number、 boolean、 enum、 object、 class,以及他们的数组

     class StateInfo {
         message: string = 'hello'
     }
     ​
     @Entry
     @Component
     struct StateTaskList {
         @State state: StateInfo = new StateInfo()
         
         TaskTotalInfoComponent({ state: $state })
     }
     ​
     //  子组件
     @Component
     struct TaskTotalInfoComponent {
         @Lnik state: StateInfo
         
         build () {
             Text(this.state.message)
         }
     }
    
  • 数组中元素增、删、替换会引起刷新

  • 嵌套类型以及数组中的对象属性无法触发视图更新

  • 使用 @Link 传递变量时,传递的是变量的引用,父子组件访问的都是同一个变量

@Link 装饰的属性,必须在初始化时带上 $

 TaskList({ finishTask: $finishTask, totalTask: $totalTask })

@Provide 和 @Consume

@Provide 和 @Consume 可以跨组件提供类似于 @State 和 @Link 的双向同步

弊端就是因为不需要传参,就需要代码内部自动维护,从而会损耗资源

 class StateInfo {
     message: string = 'hello'
 }
 ​
 @Entry
 @Component
 struct StateTaskList {
     @Provide state: StateInfo = new StateInfo()
     
     //  使用 @Provide 时,无需传参
     TaskTotalInfoComponent()
 }
 ​
 //  子组件
 @Component
 struct TaskTotalInfoComponent {
     @Consume state: StateInfo
     
     build () {
         Text(this.state.message)
     }
 }

@Observed 和 @ObjectLink

@Observed 和 @ObjectLink 装饰器作用于在涉及 嵌套对象数组元素为对象 的场景中进行双向数据同步

页面路由

路由说明

页面路由是指在应用程序中实现不同页面之间的跳转和数据传递。

在 HarmonyOS 中,访问过的页面并没有被销毁,而是被保存在页面栈的中间中,页面栈是先进后出(层层叠加的),当前访问的页面就在栈顶,最先访问的页面就在栈底。

当页面要操作返回上一页时,只需要将栈顶的页面消除,即可实现返回上一页的功能。

页面栈的最大容量上限为 32 个页面,使用 router.clear() 方法可以清空页面栈,释放内存

Router有两种页面跳转模式,分别是:

  • router.pushUrl() 目标页面不会替换当前页面,而是压入页面栈,因此可以用 router.back() 返回当前页面;例如:搜索历史页面
  • router.replaceUrl() 目标页替换当前页,当前页会被销毁并释放资源,无法返回当前页;例如:用户登录页面

Router有两种创建页面实例的模式,分别是:

  • Standard:标准实例模式,每次跳转都会新建一个目标页并压入栈顶。默认就是这种模式
  • Single:单实例模式,如果目标页已经在栈中,则离栈顶最近的相同Url的页面,就会被移动到栈顶并重新加载

页面跳转

  • 导入 HarmonyOS 提供的 Router 模块
 import router from '@ohos.router'
  • 利用 router 实现跳转、返回等操作

    页面异常回调错误码:

    100001:內部错误,可能是渲染失败

    100002:路由地址错误

    100003:路由栈中的页面超过 32 个

 import router from '@ohos.router';
 ​
 class RouterInfo {
     url: string
     title: string
 ​
     constructor(url: string, title: string) {
         this.url = url
         this.title = title
     }
 }
 ​
 @Entry
 @Component
 struct RouterPage {
     @State page_title: string = '页面列表'
     private routers: RouterInfo[] = [
         new RouterInfo('pages/basic/List', '商品列表')
     ]
 ​
     build() {
         Column() {
             Text(this.page_title).lineHeight(50).padding({ left: 20 }).fontSize(20).fontWeight(FontWeight.Bold)
             List({ space: 12 }) {
                 ForEach(this.routers, (item, index) => {
                     ListItem() {
                         this.RouterListItem(item, index + 1)
                     }
                 })
             }.padding({ left: 20, right: 20 })
         }
     }
 ​
     @Builder RouterListItem (item: RouterInfo, index: number) {
         Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
             Text(item.title).fontSize(16).fontColor('#ffffff')
             Text((index).toString()).fontSize(16).fontColor('#ffffff')
         }
         .height(40)
         .padding({ left: 20, right: 20 })
         .backgroundColor('#278ff0')
         .borderRadius(15)
         .onClick(() => {
             router.pushUrl(
                 { url: item.url, params: { title: item.title } },
                 router.RouterMode.Single,
                 error => {
                     if (error) console.log(`路由失败,errCode: ${ error.code } message: ${ error.message }`)
                 }
             )
         })
     }
 }
 ​
 ​
  • 配置路由

     //  entry/src/main/resources/base/profile/main_pages.json
     {
         "src": [
             "page/router",
             "page/goods/detail"
         ]
     }
    

页面参数获取

 //  获取路由传递过来的参数
 parms: any = router.getParmas()
 ​
 //  返回上一页
 router.back()
 ​
 //  返回到指定页面,并携带参数
 router.back({ url: 'page/goods/detail', params: { id: 1 } })

动画

属性动画

属性动画 是通过设置组件的animation属性来给组件添加动画,当组件的widthheightOpacitybackgroundColorscalerotatetranslate等属性变更时,可以实现渐变过渡效果。

animation 需要放在样式属性之后,否则动画不会被生效

名称参数类型必填描述
durationnumber设置动画时长 默认值: 1000,单位:毫秒
temponumber动画播放速度,数值越大,速度越快 默认值:1
curvestring|Curve设置动画曲线 默认值:Curve.EaseInOut,平滑开始和结束
delaynumber设置动画延迟执行的时长 默认值:0,单位:毫秒
iterationsnumber设置播放次数 默认值:1,取值范围:(-1, +∞) 说明:设置为 -1 时表示无限次播放
playModePlayMode设置动画播放模式,默认播放完成后重新开始播放 默认值:PlayMode.Normal
onFinish()=>void状态回调,动画播放完成时触发
 Image(this.src)
     .width(40)
     .height(40)
     .position({ x: this.fishX - 20, y: this.fishY - 20 })
     .rotate({
     angle: this.angle,  //  旋转角度
         centerX: '50%',     //  旋转中心横坐标
         centerY: '50%'      //  旋转中心纵坐标
     }).animation({ duration: 500 })
 ​
 Button('←').backgroundColor('#20101010').onClick(() => {
     this.fishX -= 20
 })

显式动画

显式动画 是通过全局animateTo函数来修改组件属性,实现属性变化时的渐变过渡效果。

 Image(this.src)
     .width(40)
     .height(40)
     .position({ x: this.fishX - 20, y: this.fishY - 20 })
     .rotate({
         angle: this.angle,  //  旋转角度
         centerX: '50%',     //  旋转中心横坐标
         centerY: '50%'      //  旋转中心纵坐标
     })
 ​
 Button('←').backgroundColor('#20101010').onClick(() => {
     animateTo({ duration: 500 }, () => {
         this.fishX -= 20
     })
 })

组件转场动画

**组件转场动画**是在组件插入或移除时的过渡动画,通过组件的transition属性来配置。

参数名称参数类型参数描述
typeTransitionType类型,默认包括组件新增和删除。默认是ALL
opacitynumber不透明度,为插入时起点和删除时终点的值。 默认值:1,取值范围:[0, 1]
translate{ x?: numberstring, y?: number string z?: number string }平移效果,为插入时起点和删除时终点的值。 -X:横向的平移距离。 y: 纵向的平移距离。 -z:竖向的平移距离。
scale{ x?: number y?: number z?: number centerX?: number string centerY?: numbe stringr }缩放效果,为插入时起点和删除时终点的值。 -x:横向放大倍数(或缩小比例)。 -y:纵向放大倍数(或缩小比例) -z:当前为二维显示,该參数无效。 centerx、 centerY指缩放中心点,centerX和 centerY默认值是“50%“。 中心点为0时,默认的是组件的左上角。
rotate{ x?: number y?: number z?: number angle?: number string centerX?: number string centerY?: numbe stringr }旋转效果: angle是旋转角度,其它参数与scale类似
 if (this.is_game_start) {
     Image(this.src)
     .width(40)
     .height(40)
     .position({ x: this.fishX - 20, y: this.fishY - 20 })
     .rotate({
         angle: this.angle,  //  旋转角度
         centerX: '50%',     //  旋转中心横坐标
         centerY: '50%'      //  旋转中心纵坐标
     })
     .transition({
         type: TransitionType.Insert,
         opacity: 0,
         translate: { x: -250 }
     })
 }
 ​
 Button('开始游戏').backgroundColor('#278ff0').fontColor('#ffffff').onClick(() => {
     animateTo({ duration: 1000 }, () => {
         this.is_game_start = true
     })
 })

Stage模型

基本概念

Stage模型
HAP-Entry
HAP-Feature
HAP-Feature
HAP-Feature
HSP
打包编译
entry
Ability-Module
共享依赖模块
Library-Module
...
Bundle
APP

stagemodel-20240624085700284

  • 为了降低不同功能模块的耦合,每个模块都可以独立进行编译和运行

    • 所有的 Ability 类型模块都将编译成 HAP(HarmonyAbilityPackage-鸿蒙能力类型包) 文件
    • 所有的 Library 类型模块都将编译成 HSP(HarmonySharePackage-鸿蒙共享类型包) 文件

编译期

  • 一个应用内部,只有存在一个 Entry 类型的 HAP,可以有存在多个 Frature 类型的 HAP

  • HAP 打包后将会合并到一起,形成一个 Bundle

  • Bundle 有个自己的名字,成为 BundleNameBundleName 是整个应用的唯一标识,整个 Bundle 的打包合并后,最终会形成一个 APP

  • 之所以采用多 HAP 文件的打包模式:

    • 降低不同模块之前的耦合
    • 可以对应用进行选择性的安装,可以先安装 Entry 模式,其他的 Feature 可以进行选择性安装

image-20240711174030.png

运行期

  • 因为每个 HAP 都可以独立运行, HAP在运行时都会创建一个 AbilityStage 实例(应用组件‘舞台’)

  • AbilityStage有很多中类型,其中比较常见的有:

    • UIAbility UI能力组件
    • ExtensionAbility 拓展能力组件
  • UIAbility 包含UI界面的应用组件,是系统调度的基本单元,在 UIAbility 展示组件时,首先会持有一个 WindowStage 实例对象

    • WindowStage 组件窗口‘舞台’,组件窗口中含有 Window,而在 Window 窗口中就可以进行 ArkUI Page 的页面绘制

配置文件

Stage 模型的配置文件分为两类:

  • 针对整个应用的全局配置文件

     project-name/
     │
     ├── AppScope
     │   ├── app.json5
    
     {
       "app": {
         "bundleName": "com.example.harmonyapp", //  应用唯一标识,命名格式域名倒置
         "vendor": "example",
         "versionCode": 1000000,
         "versionName": "1.0.0",
         "icon": "$media:app_icon",  //  应用图标 AppScope/resources/base/media
         "label": "$string:app_name" //  应用名称 AppScope/resources/base/element
       }
     }
    
  • 每个模块中的 module.json5 文件就是当前模块的配置文件

     {
         "module": {
             "name": "entry",    //  当前模块名称
             "type": "entry",    //  当前模块类型 entry | feature | shared
             "description": "$string:module_desc",   //  当前模块描述
             "mainElement": "EntryAbility",  //  当前模块入口 entry/src/main/ets/entryability/EntryAbility
             "deviceTypes": [    //  当前模块设备类型
               "phone",
               "tablet"
             ],
             "deliveryWithInstall": true,    //  当前模块的Feature模块是否要跟随整个APP一起安装 
             "pages": "$profile:main_pages", //  当前模块内部所有的页面(entry/src/main/resources/base/profile/main_pages.json)
             /*
             * 声明访问权限 应用授权列表(https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/permission-list-0000001544464017-V2)
             * name  权限名称  必填,权限类型是用户授权(user_grant)时,才需要填写下面的信息
             * reason  申请原因
             * usedScene 使用场景
              */
             "requestPermissions": [
               //  申请网络权限,用于加载网络图片等
               {
                 "name": "ohos.permission.INTERNET",
               }
             ],
             //  一个模块下可以创建多个 Ability, 每创建一个 Ability 都需要在这里指定处理
             "abilities": [
                 {
                     "name": "EntryAbility", //  Ability 名称
                     "srcEntry": "./ets/entryability/EntryAbility.ts",   //  Ability源码地址
                     "description": "$string:EntryAbility_desc", //  Ability描述
                     "icon": "$media:icon",  //  当前 Ability 图标(入口应用的图标)
                     "label": "$string:EntryAbility_label",  //  当前 Ability 名称(入口应用的名称)
                     "startWindowIcon": "$media:icon",   //  应用启动时的图标
                     "startWindowBackground": "$color:start_window_background",  //  应用启动时的背景色
                     "exported": true,
                     "skills": [ //  当前 Ability 负责的功能,跟 Ability 之前的跳转有关系
                         {
                             //  表示项目入口
                             "entities": ["entity.system.home"],
                             "actions": ["action.system.home"]
                         }
                     ]
                 }
             ]
         }
     }
    

AbilityStage

  • onMemoryLevel 系统决定调整内存时的回调函数
  • onConfigurationUpdated 系统发生全局配置变化时的回调函数

UIAbility

UIAbility生命周期

/src/main/ets/entryability/EntryAbility.ts

  • create 创建 Ability

  • onWindowStageCreate 创建 WindowStage舞台

  • onForeground 应用被切换至前台

    • Visible 当前 WindowStage 可见
    • ActiveWindowStage 可见时,获取焦点
    • InActive 当前 WindowStage 失去焦点
    • InVisible 当前 WindowStage 不可见
  • onBackground 应用被切换至后台

    • onWindowStageDestroy WindowStage 被销毁
  • onDestroy 应用被销毁时

uiability-20240624093957691.png

页面组件生命周期

  • aboutToAppear 页面组件实例创建以后,build函数执行之前
  • aboutToDisappear 组件被销毁之前
  • onPageShow 页面展示之后执行
  • onPageHide 页面隐藏之前执行
  • onBackPress 返回上一页之前执行

onPageShowonPageHideonBackPress 都是属于页面生命周期函数,只能在 @Entry 下使用,普通组件则无法使用

aboutToAppearaboutToDisappear 属于组件生命周期函数,可以在任何页面或组件中使用

image-20240624100950530.png

UIAbility启动模式

修改启动模式

 //  entry/src/main/module.json5
 //  默认 singleton
 {
     "abilities": [
         {
             "launchType": "singleton",
             //  "launchType": "multion",
             //  "launchType": "standard",
             //  "launchType": "specified",
         }
     ]
 }
  • Singleton 【单实例模式】 每一个UIAbility只存在唯一实例。是默认启动模式。任务列表中只会存在一个相同的UIAbility

image-20240711175207.png

  • multion 每次启动UIAbility都会创建一个新的实例。在任务列表中可能存在一个或多个相同的UIAbility,但内存中只会存在一份

image-20240711175338.png

  • standardmultion相似,但不同之处是,standard 在内存中会存在多个(API10中好像被去除了)

  • specified 每个UIAbility实例可以设置Key标示,启动UIAbility时,需要指定key,存在key相同实例直接被拉起,不存在则创建新实例。实现在一个 Ability 中唤醒另一个 Ability

    • 当前UIAbility调用startAbility方法拉起目标UIAbility

      import common from '@ohos.app.ability.common'
      import Want from '@ohos.app.ability.Want'
      
      //	获取上下文
      private context = getContext(this) as common.UIAbilityContext
      
      //	指定要跳转到的 UIAbility 信息
      let want: Want = {
          deviceId: '',	//	设备信息,不传(空)表示本设备
          bundleName: 'com.example.harmonyapp',	//	包名(应用名) /AppScope/app.json5 的 bundleName
          abilityName: 'DocumentAbility',	//	要调用的 UIAbility 名称
          moduleName: 'entry',	//	要调用的 UIAbility 在哪个模块
          parameters: {	//	参数
          	// getInstancekey:自定义方法,生成目标UIAbility实例的key,要唤醒的 Ability 实例的 key
              instanceKey: this.getInstanceKey()
          }
      }
      
      //	尝试唤醒目标 UIAbility 实例
      this.context.startAbility(want)
      
    • AbilityStage 的生命周期回调中为目标 UIAbility 实例生成 key

      mkdir harmonyapp/entry/src/main/ets/myabilitystage/MyAbilityStage.ts
      
      import AbilityStage from '@ohos.app.ability.AbilityStage'
      import Want from '@ohos.app.ability.Want'
      
      export default class MyABilityStage extends AbilityStage {
          onAcceptWant(want: Want): string{
              //	判读当前要拉去的是否为 DocumentAbility
              if (want.abilityName === 'DocumentAbility') {
                  //	根据参数中的 instanceKey 参数拼接生成一个 key 值并返回
                  return `document_ability_${want.parameters.instanceKey}`
              }
              return ''
          }
      }
      
    • module.json5 配置文件中,通过 srcEntry 参数指定 AbilityStage 路径

      {
          "module": {
              "abilities": [
                  { ... },	//	默认的 EntryAbility
                  {
                   	"srcEntry": "./ets/myabilitystage.MyAbilityStage.ts",
              		"launchType": "specified"
                  }
              ]
          }
      }
      

image-20240711175536.png

ExtensionAbility组件

ExtensionAbility组件:是基于特定场景(例如服务卡片、输入法等)提供的应用组件。每一个具体场景对应一个ExtensionAbilityType,开发者只能使用系统已定义的类型。

各类型的ExtensionAlblity组件均由相应的系统服务统一管理,例如InputMethodExtensonAblity组件由输入法管理服务统一管理

目前EtensionAblity组件类型有用于输入法场景的InputMethodExtensionAlbilfty

用于闲时任务场景的WorkSchedullerExtensionAbillity

HTTP数据请求

网络管理模块主要分为一下功能:

  • HTTP数据请求 通过 HTTP 发起请求
  • WebScoket连接 使用 WebScoket 建立服务器与客户端的双向连接
  • Scoket连接 通过 Scoket 进行数据传输

HTTP数据请求

  • 导入 http 模块

    import http from '@ohos.net.http'
    
  • 使用 http 模块发送请求,单方法对象不可复用,只能发送一次请求,下次再请求时,需要再创建一个新的对象

    HttpRequestOptions

    名称类型描述
    methodRequestMethod请求方式,GET、 POST、 PUT、 DELETE等
    extraDatastringObject请求参数
    headerObject请求头字段
    connectTimeoutnumber连接超时时间,单位亳秒, 默认60000ms
    readTimeoutnumber读取超时间,单位亳秒, 默认60000ms

    HttpResponseOptions

    名称类型描述
    responseCodeResponseCode响应状态码
    headerObject响应头
    cookiesstring响应返回的cookies
    resultstring | Object响应体,默认是JSON字符串
    resultTypeHttpDataType返回值类型
    class dataInfo {
        id: number,
        name: string,
        ...
    }
    
    class httpModel {
     	baseUrl: string = '请求连接'
    
        getData(): Promise<dataInfo> {
            return new Promise(resolve, reject) => {
                //	创建一个 http 请求对象
                let httpRequest = http.createHttp()
    
                //	发起网络请求
                httpRequest.request(baseUrl+'接口地址', {
                    method: http.RequestMethod.POST,
                    extraData: { 'param1': 'value1', ... }
                })
                httpRequest.request(baseUrl+'接口地址', {
                    method: http.RequestMethod.GET,
                    extraData: { 'key1=value1&key2=value2...' }
                }).then((res: http.HttpResponse) => {	//	处理响应结果
                    if (res.responseCode === 200) {
                        //	请求成功
                        resolve(JSON.parse(res.data.toString()))
                    }
                }).catch((error: Error) => {
                    //	请求失败
                    reject()
                })
            }
        }   
    }
    

Axios使用

下载安装ohpm

  • 下载 ohpm 工具包,点击链接获取ohpm使用指导

  • 解压工具包,执行初始化命令

    #	Windows
    init.bat
    #	Mac/Linux
    ./init.sh
    
  • ohpm 配置到环境变量中

    # windows 环境,直接在我的电脑配置即可
    
    # Linux 或Mac环境,其中OHPM的路经请替换为ohpm的安装路经 
    export OHPM_HOME=/home/xx/Downloads/ohpm  #本处路径请替换为ohpm的安装路径
    export PATH=${OHPM_HOME}/bin:${PATH}
    
    ohpm -v
    

下载安装Axios

第三方库中心仓

ohpm install @ohos/axios

开放网络权限

//	entry/src/main/module.json5
"requestPermissions": [
   //  申请网络权限,用于加载网络图片等
   {
       "name": "ohos.permission.INTERNET",
   }
]

安装成功后,就会在 oh-package.json5 中的 description 选项中显示

{
  	"dependencies": {
    	"@ohos/axios": "^2.2.0"
  	}
}

导入使用

  • 导入

    import axios from '@ohos/axios'
    
  • 发送请求并处理响应

    baseUrl: string = '请求连接'
    
    axios.get(baseUrl+url, {
        params: {},	//	GET请求参数
        data: {}	//	POST请求参数
    }).then(res => {
        if (res.code === 200) {
            
        }
    }).catch(error => {
        console.log('请求失败')
    })
    
    class dataInfo {
        id: number,
        name: string,
        ...
    }
    
    class httpModel {
     	baseUrl: string = '请求连接'
    
        getData(): Promise<dataInfo> {
            return new Promise(resolve, reject) => {
                //	创建一个 http 请求对象
                let httpRequest = http.createHttp()
    
                //	发起网络请求
                axios.get(baseUrl+'接口地址', {
                    data: {}
                }).then((res: http.HttpResponse) => {	//	处理响应结果
                    if (res.status === 200) {
                        //	请求成功
                        resolve(res.data)
                    }
                }).catch((error: Error) => {
                    //	请求失败
                    reject()
                })
            }
        }   
    }
    

数据持久化

用户首选项(Preference)为应用提供Key-Va lue键值型的数据处理能力,支持应用持久化轻量级数据

image-20240627161738025.png

使用步骤

preferences 的 key 必须为 string 类型,同时要求非空,且长度不超过80字节

value 可以是 string、number、boolean等类型,但大小不能超过8192字节

preferences 的数据量建议不超过一万条

  • 导入首选项模块

     import dataPreference from '@ohos.data.preferences'
    
  • 获取首选项实例,读取指定文件

     //  MyAppPreferences ---> Preferences实例名称
     dataPreference.getPreferences(this.context, 'MyAppPreferences').then(preferences => {
         //  获取成功
     })
     .catch(error => {
         //  获取失败
     })
    
  • 数据操作

     //  写入数据,如果数据已经存在,则会覆盖之前的数据;可以用使用 .has() 方法进行判断数据是否存在
     preferences.put('key', value).then(() => preferences.flush()) //    刷到磁盘
     .catch(error => {}) //  异常处理
     ​
     //  删除数据
     preferences.delete('key').then(() => {}).catch(error => {})
     ​
     //  查询数据,defaultValue ---> 如果查不到,则返回 defaultValue
     preferences.get('key', 'defaultValue').then(() => {}).catch(error => {})
    

实战案例

  • 封装 Preferences 工具包实例,因为在写入数据时 value 支持的类型有很多,因此 Preferences 提供了 preferences.ValueType,用来声明 value 的字段类型

     import preferences from '@ohos.data.preferences';
     ​
     class PreferencesUtil {
         prefMap: Map<string, preferences.Preferences> = new Map()   //  存储 preferences
     ​
         /* 加载 preferences 实例 */
         loadPreferences (context, name: string) {
             preferences.getPreferences(context, name).then(pref => {
                 //  存储 preferences
                 this.prefMap.set(name, pref)
                 console.log('preferences=====>', `加载preferences${name}成功`)
             }).catch(error => {
                 console.log('preferences=====>', `加载preferences${name}失败`, JSON.stringify(error))
             })
         }
     ​
         /* 同步加载 preferences 实例 */
         async loadPreferencesSync (context, name: string) {
             try {
                 const result = await preferences.getPreferences(context, name)
                 this.prefMap.set(name, result)
             } catch (error) {
                 console.log('preferences=====>', `加载preferences${name}失败`, JSON.stringify(error))
             }
         }
     ​
         /* 新增 preferences 数据 */
         async putPreferencesValue (name: string, key: string, value: preferences.ValueType) {
             //  判断当前数据是否存在
             if (this.prefMap.has(name)) {
                 try {
                     const currentPref = this.prefMap.get(name)
                     //  写入数据
                     await currentPref.put(key, value)
                     //  刷数据
                     await currentPref.flush()
                     console.error('preferences=====>', `保存preferences数据${name}${key}=${value}成功`)
                 } catch (error) {
                     console.error('preferences=====>', `保存preferences数据${name}${key}=${value}失败`, JSON.stringify(error))
                 }
             } else {
                 console.error('preferences=====>', `当前preferences${name}尚未初始化`)
             }
         }
     ​
         /* 获取 preferences 数据 */
         async getPreferencesValue (name: string, key: string, defaultValue: preferences.ValueType) {
             //  判断当前数据是否存在
             if (this.prefMap.has(name)) {
                 const currentPref = this.prefMap.get(name)
                 //  读取数据
                 const result = await currentPref.get(key, defaultValue)
                 console.error('preferences=====>', `保存preferences数据${name}${key}=${result}成功`)
                 return result
             } else {
                 console.error('preferences=====>', `获取preferences${name}失败`)
             }
         }
     ​
         /* 删除 preferences 数据 */
         async deletePreferencesValue (name: string, key: string) {
             //  判断当前数据是否存在
             if (this.prefMap.has(name)) {
                 const currentPref = this.prefMap.get(name)
                 //  写入数据
                 await currentPref.delete(key)
                 console.error('preferences=====>', `删除preferences数据${name}${key}=${value}成功`)
             } else {
                 console.error('preferences=====>', `删除preferences${name}失败`)
             }
         }
     }
     ​
     const preferencesUtil = new PreferencesUtil()
     ​
     export default preferencesUtil as PreferencesUtil
    
  • 在应用启动时,创建 Preferences 实例

     //  entry/src/main/ets/entryability/EntryAbility.ts
     import UIAbility from '@ohos.app.ability.UIAbility';
     import PreferencesUtil from '../common/util/PreferencesUtil'
     ​
     export default class EntryAbility extends UIAbility {
           onCreate(want, launchParam) {
                 //  加载 Preferences 实例
                 PreferencesUtil.loadPreferencesSync(this.context, 'MyPreferences')
           }
     }
    
  • 写入 Preferences 数据

     import PreferencesUtil from '../../common/util/PreferencesUtil'
     ​
     Text('设置大字号').onClick(() => {
         PreferencesUtil.putPreferencesValue('MyPreferences', 'font_size', 20)  
     })
    
  • 获取 Preferences 数据,在获取数据时,因为要接收的数据是 number 类型,而 Preferences 返回的是 preferences.ValueType 多类型,因此要使用 as number 来指定接收的字段类型

    import PreferencesUtil from '../../common/util/PreferencesUtil'
    
    //	推荐在页面生命周期创建时获取
    async aboutToAppear() {
            this.font_size = await PreferencesUtil.getPreferencesValue('MyPreferences', 'font_size', 16) as number
    }
    

关系型数据库

关系型数据库(RDB)是基于SQLite组件提供的本地数据库,用于管理应用中的结构化数据。例如:记账本、备忘录、任务管理等。

  • 不需要通过网络进行请求,直接在应用内部实现
  • 还具备关系型数据库的所有高级特性,例如:事务、存储过程等
  • 因为不通过网络,所以响应性能好

image-20240628130122.png

初始化数据库

  • 导入关系型数据库模块

     import relationalStore from '@ohos.data.relationalStore';
    
  • 初始化数据库表

     //  rdb配置
     const config = {
         name: 'HarmonyApp.db',   //  数据库文件名
         securityLevel: relationalStore.SecurityLevel.S1    //  数据库安全级别
     }
     //  初始化表的SQL
     const sql = `CREATE TABLE IF NOT EXISTS TASK (
     ID INTEGER PRIMARY KEY,
     NAME TEXT NOT NULL,
     FINISHED bit)`
     //  获取rdb
     relationalStore.getRdbStore(this.context, config, (err, rdbStore) => {
         //  执行Sql,后续的所有增册改查都是使用rdbStore对象
         rdbStore.executeSql(sql)
     })
    
  • 增删改

    在 HarmonyOS 中 rdbStore 的API中也提供了比较常见的 增、删、改、查的方法简化了对数据的ADBC操作

    表格实例

    IDNAMEFINISHED行号 -1
    1任务1false0
    2任务2false1
    3任务3true2
    • 新增

       //  准备数据
       const task = { id: 0, name: '任务1', is_finished: false }
       //  新增数据
       this.rdnStore.insert(this.tableName, task)
      
    • 修改

       //  准备数据
       const task = { is_finished: true }
       //  查间条件, RabPredicates就是条件谓词,相当于判断查询
       const predicates = new relationalStore.RdbPredicates(this.tableName)
       //  判断ID是否对应
       predicates.equalTo('ID', id)
       //  执行更新
       this.rdbstore.update(task, predicates)
      
    • 删除

       //  查询条件
       const predicates = new relationalStore.RdbPredicates(this.tableName)
       //  判断ID是否对应
       predicates.equalTo('ID', id)
       //  执行更新
       this.rdbstore.delete(predicates)
      
    • 查询数据

       //  查询条件
       const predicates = new relationalStore.RdbPredicates(this.tableName)
       //  执行查询 ['ID', 'NAME', 'FINISHED'] 要查询的字段列表,返回的是 结果集数据
       const result = await this.rdnStore.query(predicates, ['ID', 'NAME', 'FINISHED'])
      
    • 解析结果集数据,使用 result.getColumnIndex 获取列的编号

       //  保存解析后的数据
       let taskList: ang[] = []
       //  循环遍历结果集,判断是否结果是否遍历到最后一行
       while (!result.isAtLastRow) {
           //  指针移动到下一行数据
           result.goToNextRow()
           //  根据字段名称获取字段index,从而获取字段的值
           const id = result.getLong(result.getColumnIndex('ID'))
           const name = result.getString(result.getColumnIndex('NAME'))
           taskList.push(id, name)
       }
      

示例代码

  • 封装数据库工具类

     //  entry/src/main/ets/common/util/TaskModel.ets
     import relationalStore from '@ohos.data.relationalStore';
     //  任务类型
     @Observed
     class TaskInfo {
         static id: number = 1
         //  任务名称
         name: string = `任务${Task.id++}`
         //  任务状态:是否完成
         finished: boolean = false
     }
     ​
     class TaskModel {
         //  设置成员变量
         private rdbStore: relationalStore.RdbStore
         private table_name: string = 'Task'
     ​
         /* 初始化数据库 */
         initTaskDB (context) {
             //  rdb配置
             const config = {
                 name: 'HarmonyApp.db',   //  数据库文件名
                 securityLevel: relationalStore.SecurityLevel.S1    //  数据库安全级别
             }
             //  初始化表的SQL
             const sql = `CREATE TABLE IF NOT EXISTS Task (
                 ID INTEGER PRIMARY KEY,
                 NAME TEXT NOT NULL,
                 FINISHED bit)`
             //  获取rdb
             relationalStore.getRdbStore(context, config, (error, rdbStore) => {
                 if (error) {
                     console.log('获取rdb失败')
                     return false
                 }
                 //  执行Sql,后续的所有增册改查都是使用rdbStore对象
                 rdbStore.executeSql(sql)
                 console.log('HarmonyAppDB', '创建Task表成功')
                 //  保存到成员变量
                 this.rdbStore = rdbStore
             })
         }
     ​
         /* 查询数据库数据 */
         getTaskDB () {
             //  构建查询条件
             const predicates = new relationalStore.RdbPredicates(this.table_name)
             //  执行查询 ['ID', 'NAME', 'FINISHED'] 要查询的字段列表
             const result = await this.rdnStore.query(predicates, ['ID', 'NAME', 'FINISHED'])
             //  查询的结果返回的是结果集,因此需要定义一个数组,组装最终的查询结果
             const taskList: TaskInfo[] = []
             //  判断数据是否为最后一行
             while (!result.isAtLastRow) {
                 //  指针移动到下一行
                 result.goToNextRow()
                 //  获取数据
                 let id = result.getLong(result.getColumnIndex('ID'))
                 let name = result.getString(result.getColumnIndex('NAME'))
                 //  因为在数据库中,true 和 false 被存储为 0 和 1,因此也需要以number类型获取 Boolean 数据
                 let finished = result.getLong(result.getColumnIndex('FINISHED'))
                 //  将数据存储到数组中
                 taskList.push({ id, name, finished: !!finished })
             }
             return taskList
         }
     ​
         /* 添加数据,数据添加后,会以 Promise 的方式返回一个新增后的 id 值 */
         addTaskDB (name: string) {
             this.rdbStore.insert(this.table_name, { name, finished: false })
         }
     ​
         /* 更新Task数据状态 */
         updateTaskStatus (id: number, finished: boolean) {
             const data = { finished }
             //  构建查询条件
             const predicates = new relationalStore.RdbPredicates(this.table_name)
             //  判断数据是否一致,或数据是否存在
             predicates.equalTo('ID', id)
             //  执行更新操作,返回 Promise 对象
             return this.rdbStore.update(data, predicates)
         }
     ​
         /** 根据 id 删除数据 */
         deleteTaskData (id: number) {
             //  构建删除条件
             const predicates = new relationalStore.RdbPredicates(this.table_name)
             predicates.equalTo('ID', id)
             //  执行删除操作,返回 Promise 对象
             return this.rdbStore.delete(predicates)
         }
     }
     ​
     const taskModel = new TaskModel()
     ​
     export default taskModel as TaskModel;
    

    在项目启动时,创建初始化数据库操作

    需要注意的是

    EntryAbility 是 TS文件,而 TaskModel 工具类是 ETS文件

    ETS文件中可以引用 TS文件,但 TS文件中是不可以引用 ETS文件的

    因此要将 EntryAbility 的文件后缀修改为 ETS 后缀

     //  entry/src/main/ets/entryability/EntryAbility.ts
     import UIAbility from '@ohos.app.ability.UIAbility';
     import TaskModel from '../common/util/TaskModel'
     ​
     export default class EntryAbility extends UIAbility {
           onCreate(want, launchParam) {
                 //  初始化任务表数据库
                 TaskModel.initTaskDB(this.context)
           }
     }
    

基础通知

应用可以通过通知接口发送通知消息

注意

  • API version 11开始不再维护contentType,建议使用 notificationContentType 代替。
  • API version 11开始不再维护slotType,建议使用 notificationSlotType 代替。
 let request: notificationManager.NotificationRequest = {
     id: 10,
     content: {
         notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
         normal: {
             title: '通知标题',
             text: '通知内容详情',
             additionalText: '通知附加内容'
         }
     },
     deliveryTime: new Date().getTime(),
     showDeliveryTime: true,
     groupName: 'app',
     notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION
 }
  • notificationManager.publish 的错误回调也需要注明
import Base from '@ohos.base';
notificationManager.publish(request).then(() => {
    console.log('发送通知成功')
}).catch((reason: Base.BusinessError) => {	//	不加上 Base.BusinessError 就会报错
    console.log('发送通知失败:失败原因', JSON.stringify(reason))
})

使用步骤

  • 导入 notification 模块

    import notificationManager from '@ohos.notificationManager'
    
  • 发布通知

    //	构建通知请求对象
    let request: notificationManager.NotificationRequest = {
        id: 10,	//	当前通知的唯一标识,多次通知采用相同的 id 时,后面的通知内容将会覆盖原来的通知内容
        content: {
            //	通知内容
        }
    }
    
    //	发布通知
    notificationManager.publish(request).then(() => {
        console.log('发送通知成功')
    }).catch(reason => {
        console.log('发送通知失败:失败原因', JSON.stringify(reason))
    })
    
  • 取消通知

    //	取消指定 id 的通知
    notificationManager.cancel(10)
    //	取消当前应用的的所有通知
    notificationManager.cancelAll()
    

通知请求参数

类型说明
NOTIFICATION_CONTENT_BASIC_TEXT普通文本型
NOTIFICATION_CONTENT_LONG_TEXT长文本型
NOTIFICATION_CONTENT_MULTILINE多行文本型
NOTIFICATION_CONTENT_PICTURE图片型
  • 普通文本类型

    let request: notificationManager.NotificationRequest = {
        id: 10,
        content: {
            contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
            normal: {
                title: '通知标题',
                text: '通知内容详情',
                additionalText: '通知附加内容'
            }
        }
    }
    
  • 长文本类型

     let request: notificationManager.NotificationRequest = {
         id: 10,
         content: {
             contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_LONG_TEXT,
             longText: {
                 title: '通知标题',
                 text: '通知内容详情',
                 additionalText: '通知附加内容',
                 longText: '通知中的长文本内容',
                 briefText: '通知概要总结',
                 expandedTitle: '通知展开时的标题'
             }
         }
     }
    
  • 多行文本类型

     let request: notificationManager.NotificationRequest = {
         id: 10,
         content: {
             contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_MULTILINE,
             multiLine: {
                 title: '通知标题',
                 text: '通知内容详情',
                 additionalText: '通知附加内容',
                 longText: '通知中的长文本内容',
                 briefText: '通知概要总结',
                 longTitle: '通知展开时的标题',
                 lines: [
                     '第一行',
                     '第二行',
                     '第三行'
                 ]
             }
         }
     }
    
  • 图片类型

     let request: notificationManager.NotificationRequest = {
         id: 10,
         content: {
             contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_PICTURE,
             picture: {
                 title: '通知标题',
                 text: '通知内容详情',
                 additionalText: '通知附加内容',
                 briefText: '通知概要总结',
                 expandedTitle: '通知展开后的标题',
                 picture: this.pixel
             }
         }
     }
    

    这里的图片需要使用像素图,需要先将图片加载出来,再将图片创建为一个 PixelMap,因为 PixelMap 是基于像素级别,可以对图像进行任意修改操作,PixelMap构建方式相对固定

    import { image } from '@kit.ImageKit';
    
    async aboutToAppear() {
        //  获取资源管理器
        const rm = getContext(this).resourceManager;
        //  读取图片
        const file = await rm.getMediaContent($r('app.media.header_back'))
        //  创建 PixelMap
        image.createImageSource(file.buffer).createPixelMap().then(value => this.pixel = value).catch(reason => {
            console.log('图片加载异常', JSON.stringify(reason))
        })
    }
    

NotificationRequest常用参数

  • deliveryTime 发送通知的时间

  • showDeliveryTime 是否展示发送通知的时间

  • groupName 分组名称,默认每个通知是各自不同的分组,如果设置 groupName 可以实现,如微信app一样,将通知集中到一个通知分组中

  • slotType 通道类型,通知展示的方式类型

    类型说明状态栏图标提示音横幅
    SOCIAL_COMMUNICATION社交类型
    SERVICE_INFORMATION服务类型×
    CONTENT_INFORMATION内容类型××
    OTHER_TYPES其他×××
let request: notificationManager.NotificationRequest = {
    id: 10,	//	当前通知的唯一标识,多次通知采用相同的 id 时,后面的通知内容将会覆盖原来的通知内容
    content: {
        //	通知内容
    },
    deliveryTime: new Date().getTime(),
    showDeliveryTime: true,
    groupName: 'app',
    slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION
}

进度条通知

进度条通知会展示一个动态的进度条,主要用于文件下载、长任务处理的进度显示。

使用步骤

  • 判断当前系统是否支持进度条模版

    this.isSupport = await notificationManage.isSupportTemplate('downloadTemplate')
    if (!this.isSupport) {
        return false
    }
    
  • 定义通知请求

     //  通知模版
     let template = {
         name: 'downloadTemplate',
         data: {
             progressValue: this.progressValue,  // 进度条当前进度 
             progressMaxValue: 100 // 进度条最大值
         }
     }
     //  通知请求
     let request: notificationManage.NotificationRequest = {
         id: 999,
         template: template,
         content: {
             notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
             normal: {
                 title: `${this.filename: this.state}`,
                 text: '',
                 additionalText: `${this.progressValue}`
             }
         }
     }
    

实例代码

 import notificationManager from '@ohos.notificationManager'
 import promptAction from '@ohos.promptAction'
 import Base from '@ohos.base';
 ​
 enum DownloadState {
     NOT_BEGIN = '未开始',
     DOWNLOADING = '下载中',
     PAUSE = '已暂停',
     FINISHED = '已完成',
 }
 ​
 ​
 @Entry
 @Component
 struct DownloadProgressPage {
     //  下载进度
     @State progressValue: number = 0
     progressMaxValue: number = 100
     //  任务状态
     @State state: DownloadState = DownloadState.NOT_BEGIN
     //  下载的文件名
     filename: string = '圣诞星.mp4'
     //  模拟下载的任务的id,存储计时器
     taskId: number = -1
     //  通知id
     notificationId: number = 999
     isSupport: boolean = false
 ​
     async aboutToAppear(){
         //  判断当前系统是否支持进度条模板
         this.isSupport = await notificationManager.isSupportTemplate('downloadTemplate')
     }
 ​
     build() {
         Row() {
             Column() {
                 Progress({
                     value: this.progressValue,
                     total: 100
                 })
 ​
                 Row({ space: 5 }) {
                     Text(`${(this.progressValue * 0.43).toFixed(2)}MB`).fontSize(14).fontColor('#c1c2c1')
                     Blank()
                     if (this.state === DownloadState.NOT_BEGIN) {
                         Button('开始').downloadButton().onClick(() => this.handleClickDownload())
                     } else if (this.state === DownloadState.DOWNLOADING) {
                         Button('取消').downloadButton().backgroundColor('#d1d2d3').onClick(() => this.handleClickCancelDown())
                         Button('暂停').downloadButton().onClick(() => this.handleClickPauseDown())
                     } else if (this.state === DownloadState.PAUSE) {
                         Button('取消').downloadButton().backgroundColor('#d1d2d3').onClick(() => this.handleClickCancelDown())
                         Button('继续').downloadButton().onClick(() => this.handleClickDownload())
                     } else {
                         Button('打开').downloadButton().onClick(() => this.handleClickOpenDown())
                     }
                 }.width('100%')
             }
             .width('100%')
             .padding(20)
         }
         .height('100%')
     }
 ​
 ​
     /** 点击开始下载 */
     handleClickDownload () {
         //  清理旧任务
         this.taskId && clearInterval(this.taskId);
         //  开启定时任务,模拟下载
         this.taskId = setInterval(() => {
             //  判断任务进度是否达到100
             if(this.progressValue >= 100){
                 //  任务完成了,应该取消定时任务
                 clearInterval(this.taskId)
                 this.taskId = -1
                 //  并且标记任务状态为已完成
                 this.state = DownloadState.FINISHED
                 //  发送通知
                 this.publishDownloadNotification()
                 return
             }
             //  模拟任务进度变更
             this.progressValue += 2
             //  发送通知
             this.publishDownloadNotification()
         }, 500)
         //  标记任务状态:下载中
         this.state = DownloadState.DOWNLOADING
     }
 ​
     /** 点击暂停下载 */
     handleClickPauseDown () {
         //  取消定时任务
         if(this.taskId > 0){
             clearInterval(this.taskId);
             this.taskId = -1
         }
         //  标记任务状态:已暂停
         this.state = DownloadState.PAUSE
         //  发送通知
         this.publishDownloadNotification()
     }
 ​
     /** 点击取消下载 */
     handleClickCancelDown () {
         //  取消定时任务
         if(this.taskId > 0){
             clearInterval(this.taskId);
             this.taskId = -1
         }
         //  清理下载任务进度
         this.progressValue = 0
         //  标记任务状态:未开始
         this.state = DownloadState.NOT_BEGIN
         //  取消通知
         notificationManager.cancel(this.notificationId)
     }
 ​
     /** 点击打开下载文件 */
     handleClickOpenDown () {
         promptAction.showToast({
             message: '功能未实现'
         })
     }
 ​
     /** 发送进度条通知消息 */
     publishDownloadNotification () {
         //  判断当前系统是否支持进度条模板
         if (!this.isSupport) return
 ​
         let request: notificationManager.NotificationRequest = {
             id: this.notificationId,
             template: {
                 name: 'downloadTemplate',
                 data: {
                     progressValue: this.progressValue,
                     progressMaxValue: this.progressMaxValue
                 }
             },
             content: {
                 notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
                 normal: {
                     title: `${this.filename}: ${this.state}`,
                     text: '',
                     additionalText: this.progressValue + '%'
                 }
             }
         }
         //  发送通知
         notificationManager.publish(request)
             .then(() => console.log('test', '通知发送成功'))
             .catch((reason: Base.BusinessError) => console.log('test', '通知发送失败!', JSON.stringify(reason)))
     }
 }
 ​
 //  @Extend 设置继承自哪个组件
 @Extend(Button) function downloadButton() {
     .width(75).height(28).fontSize(14)
 }

通知意图

我们可以给通知或其中的按钮设置的行为意图want,从而实现拉起应用组件或发布公共事件等能力。

说白了,就是当手机收到通知消息时,通过点击当前通知进入到对应的应用

就像微信通知那样,当我们收到一条微信消息时,通过点击微信消息就可以直接进入到微信聊天页面。

使用说明

 import wantAgent, { WantAgent } from '@ohos.app.ability.wantAgent'
 ​
 //  创建意图行为信息
 let wantInfo: wantAgent.WantAgentInfo = {
     wants: [
         {
             deviceId: '',   //  设备ID,为空代表时,代表是当前设备
             bundleName: 'com.example.harmonyapp',   //  应用唯一标识
             abilityName: 'EntryAbility',    //  拉起应用中的哪一个 Ability 页面
             action: '',
             entities: []
         }
     ],
     requestCode: 0, //  请求码
     operationType: wantAgent.OperationType.START_ABILITY,   //  操作类型,接下来要做什么,START_ABILITY 启动一个页面
     wantAgentFlags: [wantAgent.WantAgentFlags.CONSTANT_FLAG]    //  标识当前行为意图信息
 }
 ​
 //  创建意图行为实例
 this.wantAgentInstance = await wantAgent.getWantAgent(wantInfo)
 ​
 //  通知请求
 let request: notificationManager.NotificationRequest = {
     id: this.notificationId,
     template: {},
     wantAgent: this.wantAgentInstance,
     content: {
         notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
         normal: {
             title: '下载',
             text: '',
             additionalText: this.progressValue + '%'
         }
     }
 }

实例案例

 import notificationManager from '@ohos.notificationManager';
 import wantAgent, { WantAgent } from '@ohos.app.ability.wantAgent';
 import promptAction from '@ohos.promptAction';
 import Base from '@ohos.base';
 ​
 enum DownloadState {
     NOT_BEGIN = '未开始',
     DOWNLOADING = '下载中',
     PAUSE = '已暂停',
     FINISHED = '已完成',
 }
 ​
 ​
 @Entry
 @Component
 struct DownloadProgressPage {
     //  下载进度
     @State progressValue: number = 0
     progressMaxValue: number = 100
     //  任务状态
     @State state: DownloadState = DownloadState.NOT_BEGIN
     //  下载的文件名
     filename: string = '圣诞星.mp4'
     //  模拟下载的任务的id,存储计时器
     taskId: number = -1
     //  通知id
     notificationId: number = 999
     isSupport: boolean = false
     wantAgentInstance?: WantAgent
 ​
     async aboutToAppear(){
         //  判断当前系统是否支持进度条模板
         this.isSupport = await notificationManager.isSupportTemplate('downloadTemplate')
         //  创建拉取当前应用的行为意图
         //  创建wantInfo信息
         let wantInfo: wantAgent.WantAgentInfo = {
             //  设置意图
             wants: [
                 {
                     bundleName: 'com.example.harmonyapp',
                     abilityName: 'EntryAbility',
                 }
             ],
             requestCode: 0,
             operationType: wantAgent.OperationType.START_ABILITY,
             wantAgentFlags: [wantAgent.WantAgentFlags.CONSTANT_FLAG]
         }
         //  创建wantAgent实例
         this.wantAgentInstance = await wantAgent.getWantAgent(wantInfo)
     }
 ​
     build() {
         Row() {
             Column() {
                 Progress({
                     value: this.progressValue,
                     total: 100
                 })
 ​
                 Row({ space: 5 }) {
                     Text(`${(this.progressValue * 0.43).toFixed(2)}MB`).fontSize(14).fontColor('#c1c2c1')
                     Blank()
                     if (this.state === DownloadState.NOT_BEGIN) {
                         Button('开始').downloadButton().onClick(() => this.handleClickDownload())
                     } else if (this.state === DownloadState.DOWNLOADING) {
                         Button('取消').downloadButton().backgroundColor('#d1d2d3').onClick(() => this.handleClickCancelDown())
                         Button('暂停').downloadButton().onClick(() => this.handleClickPauseDown())
                     } else if (this.state === DownloadState.PAUSE) {
                         Button('取消').downloadButton().backgroundColor('#d1d2d3').onClick(() => this.handleClickCancelDown())
                         Button('继续').downloadButton().onClick(() => this.handleClickDownload())
                     } else {
                         Button('打开').downloadButton().onClick(() => this.handleClickOpenDown())
                     }
                 }.width('100%')
             }
             .width('100%')
             .padding(20)
         }
         .height('100%')
     }
 ​
 ​
     /** 点击开始下载 */
     handleClickDownload () {
         //  清理旧任务
         this.taskId && clearInterval(this.taskId);
         //  开启定时任务,模拟下载
         this.taskId = setInterval(() => {
             //  判断任务进度是否达到100
             if(this.progressValue >= 100){
                 //  任务完成了,应该取消定时任务
                 clearInterval(this.taskId)
                 this.taskId = -1
                 //  并且标记任务状态为已完成
                 this.state = DownloadState.FINISHED
                 //  发送通知
                 this.publishDownloadNotification()
                 return
             }
             //  模拟任务进度变更
             this.progressValue += 2
             //  发送通知
             this.publishDownloadNotification()
         }, 500)
         //  标记任务状态:下载中
         this.state = DownloadState.DOWNLOADING
     }
 ​
     /** 点击暂停下载 */
     handleClickPauseDown () {
         //  取消定时任务
         if(this.taskId > 0){
             clearInterval(this.taskId);
             this.taskId = -1
         }
         //  标记任务状态:已暂停
         this.state = DownloadState.PAUSE
         //  发送通知
         this.publishDownloadNotification()
     }
 ​
     /** 点击取消下载 */
     handleClickCancelDown () {
         //  取消定时任务
         if(this.taskId > 0){
             clearInterval(this.taskId);
             this.taskId = -1
         }
         //  清理下载任务进度
         this.progressValue = 0
         //  标记任务状态:未开始
         this.state = DownloadState.NOT_BEGIN
         //  取消通知
         notificationManager.cancel(this.notificationId)
     }
 ​
     /** 点击打开下载文件 */
     handleClickOpenDown () {
         promptAction.showToast({
             message: '功能未实现'
         })
     }
 ​
     /** 发送进度条通知消息 */
     publishDownloadNotification () {
         //  判断当前系统是否支持进度条模板
         if (!this.isSupport) return
 ​
         let request: notificationManager.NotificationRequest = {
             id: this.notificationId,
             template: {
                 name: 'downloadTemplate',
                 data: {
                     progressValue: this.progressValue,
                     progressMaxValue: this.progressMaxValue
                 }
             },
             wantAgent: this.wantAgentInstance,
             content: {
                 notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
                 normal: {
                     title: `${this.filename}: ${this.state}`,
                     text: '',
                     additionalText: this.progressValue + '%'
                 }
             }
         }
         //  发送通知
         notificationManager.publish(request)
             .then(() => console.log('test', '通知发送成功'))
             .catch((reason: Base.BusinessError) => console.log('test', '通知发送失败!', JSON.stringify(reason)))
     }
 }
 ​
 @Extend(Button) function downloadButton() {
     .width(75).height(28).fontSize(14)
 }

欢迎页面

自定义弹窗

使用@CustomDialog装饰器声明弹窗组件,在使用 @CustomDialog 时必须声明 controller 成员变量

 /**
  * 用户隐私政策弹窗
  */
 @CustomDialog
 struct UserPrivacyDialog {
     //  设置 controller 类型为自定义弹窗控制器类型,用来控制弹窗状态
     controller: CustomDialogController
     build() {
         Column() {
             Text('隐私政策弹窗').fontSize(20)
         }
     }
 }
  • 在页面中使用弹窗控制器组件

     /**
      * 用户隐私政策弹窗
      */
     @CustomDialog
     export default struct UserPrivacyDialog {
         controller: CustomDialogController  //  设置 controller 类型为自定义弹窗控制器类型,用来控制弹窗状态
     ​
         cancel: () => void = () => {}
         confirm: () => void = () => {}
     ​
         build() {
             Column({ space: 10 }) {
                 Text('隐私政策弹窗').lineHeight(30).fontSize(20).fontWeight(FontWeight.Bold)
                 Text('隐私政策内容隐私政策内容隐私政策内容隐私政策内容隐私政策内容隐私政策内容隐私政策内容隐私政策内容').lineHeight(28).fontSize(20).margin(10)
                 Row() {
                     Button('同意').onClick(() => {
                         this.confirm()
                         this.controller.close()
                     })
                     .margin(10)
                     Button('不同意').onClick(() => {
                         this.cancel()
                         this.controller.close()
                     })
                         .margin(10)
                         .backgroundColor('#ffffff')
                         .fontColor('#333333')
                         .border({ style: BorderStyle.Solid, width: 1, radius: 30 })
                 }
             }
             .width('100%')
             .padding(10)
         }
     }
    

欢迎页面

EntryAbility 页面的入口改为欢迎页面的路由地址

 import UserPrivacyDialog from '../components/UserPrivacyDialog';
 import { common } from '@kit.AbilityKit';
 import PreferencesUtil from '../../common/util/PreferencesUtil';
 import router from '@ohos.router';
 ​
 //  声明首选项KEY
 const USER_PREF_KEY = 'userPrivacyKey'
 ​
 @Entry
 @Component
 struct WelcomePage {
     @State message: string = 'Welcome';
     context = getContext(this) as common.UIAbilityContext
     dialogController: CustomDialogController = new CustomDialogController({
         // 自定义弹窗内容构造器
         builder: UserPrivacyDialog({
             confirm: (): void => this.handleClickConfirm(),
             cancel: (): void => this.exitApp(),
         }),
         autoCancel: false,  //  是否点击遮障层退出
         alignment: DialogAlignment.Center   //  弹窗在竖直方向上的对齐方式
     })
 ​
     handleClickConfirm () {
         //  用户同意后,将同意存储首选项
         PreferencesUtil.putPreferencesValue('MyPreferences', USER_PREF_KEY, true)
         //  跳转首页
         this.handleRouterToHome()
     }
     /** 退出应用 */
     exitApp () {
         this.context.terminateSelf()
     }
 ​
     //  默认开启弹窗
     async aboutToAppear(): Promise<void> {
         //  加载首选项数据
         const is_agree = await PreferencesUtil.getPreferencesValue('MyPreferences', USER_PREF_KEY, false)
         //  判断用户是否同意隐私政策
         if (is_agree) {
             //  用户同意隐私政策后,跳转首页
             this.handleRouterToHome()
         } else {
             //  用户没有同意隐私政策,则直接弹窗
             this.dialogController.open()
         }
     }
 ​
     //  跳转到首页
     handleRouterToHome () {
         setTimeout(() => router.replaceUrl({ url: 'pages/basic/DownloadProgressPage' }), 1000)
     }
 ​
     build() {
         Row() {
             Column() {
                 Text(this.message)
                     .padding({ top: 10, right: 30, bottom: 10, left: 30 })
                     .fontSize(30)
                     .fontWeight(FontWeight.Bold)
                     .border({ style: BorderStyle.Solid, width: 2, color: '#3D3D3D', radius: 60 })
                     .onClick(() => {
                         //  利用弹窗控制器 open() 方法打开弹窗,使用 close() 方法关闭
                         this.dialogController.open()
                     })
             }
             .width('100%')
         }
         .height('100%')
         .backgroundColor('#ffffff')
         .backgroundImage($r('app.media.welcome'))
         .backgroundImageSize({ width: '100%', height: 'auto' })
         .layoutWeight(1)
     }
 }

ArkUI组件

List列表组件

ForEach循环渲染组件

参数

  • arr 数据源
  • itemGeneratow (必填)为子组件生成函数,为数组中的每个元素创建对应的组件
  • keyGenerator (选填)是数组项唯一键值生成函数,为数据源arr的每个数组项生成唯一且持久的键值。

Swiper轮播组件

基本用法

 Swiper() {                                                                                        Image('https://images.pexels.com/photos/15804651/pexels-photo-15804651.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500').border({ radius: 20 })
           Image('https://images.pexels.com/photos/26051261/pexels-photo-26051261.jpeg?auto=compress&cs=tinysrgb&w=300&lazy=load').border({ radius: 20 })
          }
 .width('100%')
     .height(100)

常见属性

属性方法参数作用默认值
loopboolean是否开启循环轮播true
autoPlayboolean是否自动播放false
intervalnumber自动播放时间间隔3000
verticalboolean纵向滑动轮播false
Swiper() {
    Image('https://images.pexels.com/photos/15804651/pexels-photo-15804651.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500').border({ radius: 10 })
    Image('https://images.pexels.com/photos/26051261/pexels-photo-26051261.jpeg?auto=compress&cs=tinysrgb&w=300&lazy=load').border({ radius: 10 })
}
.width(355)
    .height(100)
    .loop(true)
    .autoPlay(true)
    .interval(3000)

样式自定义

Swiper() {
    Image('https://images.pexels.com/photos/15804651/pexels-photo-15804651.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500').border({ radius: 10 })
    Image('https://images.pexels.com/photos/26051261/pexels-photo-26051261.jpeg?auto=compress&cs=tinysrgb&w=300&lazy=load').border({ radius: 10 })
}
.width(355)
.height(100)
.loop(true)
.autoPlay(true)
.interval(3000)
//	.indicator(false)
.indicator(
	Indicator.dot()   //  小圆点
    .itemWidth(6)    //  圆点默认宽度
    .itemHeight(6)    //  圆点默认高度
    .color(Color.White)    //  圆点颜色
    .selectedItemWidth(14)    //  圆点选中宽度
    .selectedItemHeight(6)    //  圆点选中高度
    .selectedColor('#278ff0')    //  圆点选中颜色
)

Scroll滚动容器

WaterFlow瀑布流容器

瀑布流容器,由“行”和“列”分割的单元格所组成,通过容器自身的排列规则,将不同大小的“项目”自上而下, 如瀑布般紧密布局。

实例

WaterFlow () {
    ForEach(this.dataSource, (item.number) => {
        FlowItem () {
            Column () {
                Text('N' + item)
                Image('res/waterFlowTest(' + item % 5 + ').jpg')
            }
        }
    }), (item: string => item)
}
.columnsTemplate('1fr 1fr')
.columnGap(10)
.rowGap(5)

常用组件

image-20240709124555.png

image-20240709124808.png

布局

自适应布局

元素可以根据相对关系自动变化以适应外部容器变化的布局能力。当前开发框架提炼了七种自适应布局能力,这些布局可以独立使用,也可多种布局叠加使用。

拉伸能力

拉伸能力是指容器组件尺寸发生变化时,增加或减小的空间全部分配给容器组件内指定区域。本例中,页面由中间的内容区(包含一张图片)以及两侧的留白区组成。

拉伸能力可以使用Flex组件的 flexGrow()flexShrink属性

Row(){
    Row ().width(150).flexGrow(0).flexShrink(1)
    Image ($r('app.media.photo')).width(400).flexGrow(1).flexShrink(0)
    Row ().width(150).flexGrow(0).flexShrink(1)
}

均分能力

均分能力是指容器组件尺寸发生变化时,增加或减小的空间均匀分配给容器组件内所有空白区域。本例中, 父容器尺寸变化过程中,图标及文字的尺寸不变,图标间的间距及图标离左右边缘的距离同时均等改变。

均分能力主要使用Row组件的 ColumnjustifyContent(FlexAlign.SpaceEvenly)属性进行设置

Column () {
    Row () {
        ForEach(this.list, (item: number) => {...})
    }
    .width('100%')
    //	均匀分配父容器主轴方向的剩余空间
    .justifyContent(FlexAlign.SpaceEvenly)
    
    Row () {
        ForEach(this.list, (item: number) => {...})
    }
    .width('100%')
    //	均匀分配父容器主轴方向的剩余空间
    .justifyContent(FlexAlign.SpaceEvenly)
    
    ...
}.width('100%')

占比能力

占比能力是指子组件的宽高按照预设的比例,随父容器组件发生变化。

Row () {
	Column () {...}.layoutWeight(1)	//	设置子组件在父容器主轴方向的布局权重
	Column () {...}.layoutWeight(1)	//	设置子组件在父容器主轴方向的布局权重
	Column () {...}.layoutWeight(1)	//	设置子组件在父容器主轴方向的布局权重
}.width('100%')

缩放能力

缩放能力是指子组件的宽高按照预设的比例,随容器组件发生变化,且变化过程中子组件的宽高比不变

Column () {
    Column () {
        Image($r('app.media.photo')).width('100%').height('100%')
    }.aspectRatio(1)	//	固定宽高比
}
.width('100%')
.height('100%')

延伸能力

容器组件内的子组件,按照其在列表中的先后顺序,随容器组件尺寸变化显示或隐藏

Row () {
   //	通过List组件实现超出隐藏
    List ({ space: 10 }) {...}
    .listDirection(Axis.Horizontal)
	.width('100%')
}.width('100%')

隐藏能力

容器组件内的子组件,按照其预设的显示优先级,随容器组件尺寸变化显示或隐藏。相同显示优先级的子组件同时显示或隐藏。通过 displayPriority 实现

Row() {
    Image($r("app.media.photo")).displayPriority(1// 布局优先级
	Image($r("app.media.photo")).displayPriority(2// 布局优先级
	Image($r("app.media.photo")).displayPriority(3// 布局优先级
	Image($r("app.media.photo")).displayPriority(2// 布局优先级
	Image($r("app.media.photo")).displayPriority(1// 布局优先级
}.width('100%')

image-20240711092404.png

image-20240711092440.png

折行能力

容器组件尺寸发生变化时,如果布局方向尺寸不足以显示完整内容,自动换行。通过 FlexWrap.Wrap 实现

Column() {
    //	通过 Flex组件wrap参数实现自适应折行
    Flex({
        wrap: FlexWrap.Wrap,
        direction: FlexDirection.Row
    }) {
        ForEact(this.list, (item: Resource) => {
            Image(item).width(183).height(183)
        })
    }.width('100%')
}

image-20240711093158

响应式布局

元素可以根据特定的特征(如窗口宽度、屏幕方向等)触发变化以适应外部容器变化的布局能力。响应式布局基于断点媒体查询栅格等能力实现。

断点

将窗口宽度划分为不同的范围(即断点),监听窗口尺寸变化,当断点改变时同步调整页面布局。

注意,断点支持自定义,取值范围可修改扩展

断点名称取值范围(vp)
xs[0, 320)
sm[320, 600)
md[600, 840)
lg[840, +∞]

媒体查询

媒体查询:媒体查询提供了丰富的媒体特征监听能力,可以监听应用显示区域变化、横竖屏、深浅色、设备类型等等,即借助媒体查询能力,监听断点的变化。

栅格布局

根据设备的水平宽度,将不同的屏幕尺寸划分为不同数量的栅格,来实现屏幕的自适应。

  • 通过 GridRow 栅格布局可以调节布局占栅格的数量(设置参数span)偏移量(设置参数offset),来实现栅格的适配。
  • 可以修改断点的取值范围,支持启用最多6个断点(设置breakpoints的value参数)
@Entry
@Component
struct LoginPage {
    @State message: string = 'Hello World';

    build() {
        GridRow({
            //  初始化设置对应栅格个数
            columns: { sm: 4, md: 8, lg: 12 },
            gutter: { x: '12vp' }
        }) {
            GridCol({
                /**
                 * 小屏幕目:占 4 个栅格,偏移量为 0
                 * 中屏幕目(折叠屏):占 6 个栅格,偏移量为 1
                 * 大屏幕目(平板):占 8 个栅格,偏移量为 2
                 */
                span: { sm: 4, md: 6, lg: 8 },
                offset: { sm: 0, md: 1, lg: 0 }
            }) {
                Column() {
                    Image($r('app.media.icon')).width(56).height(56)
                    Text('买好货上涂多多').margin({ top: 10 })
                }
                .width('100%')
                .height('100%')
                .padding({ top: 20, bottom: 15 })
            }
        }
        .backgroundColor('#f2f2f2')
    }
}

image-20240711152404.png

image-20240711152903.png

image-20240711153157.png

缩进场景
  • 通过设置GridColspan属性分配组件所占栅格列数
  • 通过设置GridColoffsetGridRowgutter 等属性改变间距,实现最佳效果
GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: 24 }) {
    GridCol({ 
    	span: { sm: 4, md: 6, lg: 8 },
       	offset: {md: 1, lg: 0 }	//	0 可以进行省略
    }) {
        Column() {...}.width('100%')
    }
}
挪移布局场景

通过设置GridColspan属性分配组件所占栅格列数

GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: 24 }) {
    GridCol({ span: { sm: 4, md: 6, lg: 8 } }) {...}
    GridCol({ span: { sm: 4, md: 6, lg: 8 } }) {...}
}
.onBreakpointChange((breakpoint: string) => {
    this.currentBreakpoint = breakpoint
})

image-20240711161818.png

重复布局

通过设置GridColspan属性分配组件所占栅格列数

 GridRow({ columns: { sm: 4, md: 8, lg: 12 }, gutter: 24 }) {
     ForEach(this.list, () => {
         //  通过配置元素在不同断点下占的列数,实现不同的布局效果
         GridCol({ span: { sm: 4, md: 6, lg: 6 } }) {...}
     })
 }

image-20240711162235.png

image-20240711162423.png

一次开发,多端部署

视觉风格

分层参数

为了保证各组件有相同风格的默认样式,或者为了保证HarmonyoS系统应用有统一的风格。UX定义了一套系统资源,预置在系统中,开发者可以直接使用,称为分层参数。

使用了分层参数后,当系统切换深色模式时,字体和背景也可以自适应。

image-20240711162845.png

 Column() {
     Text('分层参数')
         .fontColor($r('sys.color.ohos_id_color_text_primary'))
         .fontSize($r('sys.float.ohos_id_text_size_headline3'))
 }
 .backgroundColor($r('sys.color.ohos_id_color_background'))
资源正常模式深色模式
ohos_id_color_text_primary黑色白色
ohos_id_text_size_headline360vp60vp
ohos_id_color_background白色黑色

SysCap机制介绍

Harmonyos使用SysCap机制(即SystemCapability),可以帮助开发者仅关注设备的系统能力,而不用考虑成百上千种具体的设备类型,降低多设备应用开发难度。

canlUse接口

在编码阶段,开发者可以通过canIUse接口,判断目标设备是否支持某系统能力,进而执行不同的业务逻辑。

通常当设备不支持某种能力时,运行到这部分代码后,给出友好弹窗,避免应用crash崩溃。

 aboutToAppear(): void {
     if (canIUse("SystemCapability.Communication.NFC.Core")) {
         console.log("该设备支持SystemCapability.Communication.NFC.Core");
     } else {
         console.log("该设备不支持SystemCapability.Communication.NFC.Core");
     }
 }

三层架构规范

推荐在应用开发过程中使用如下的“三层工程结构”,以方便代码复用及提升开发效率。

  • common(公共能力层):用于存放公共基础能力集合(如公共配置等)。其只可以被productfeatures依赖,不可以反向依赖。
  • features(基础特性层):用于存放相对独立的UI及逻辑实现等。其可以横向调用及依赖common层,但不能反向依赖 product层。
  • product(产品定制层):用于针对不同设备形态进行功能和特性集成,作为应用主入口。product层不可以横向调用。
 /application
 ├── common                  # 可选。公共能力层, 编译为HAR包或HSP包
 ├── features                # 可选。基础特性层
 │   ├── feature1            # 子功能1,例如:手机蓝牙 编译为HAR包或HSP包或Feature类型的HAP包
 │   ├── feature2            # 子功能2,例如:手机WI-FI 编译为HAR包或HSP包或Feature类型的HAP包
 │   └── ...
 └── products                # 必选。产品定制层
     ├── wearable            # 智能穿戴泛类目录, 编译为Entry类型的HAP包
     ├── default             # 默认设备泛类目录, 编译为Entry类型的HAP包
     └── ...

image-20240711170435.png

image-20240711170729.png

自由流转

image-20240711231737.png

基本概念

多端协同

多端协同的情况有并发、协作、互补,并发主要就是镜像相同的内容到不同的显示屏幕

跨端迁移应用开发示例

申请权限并启用接续能力

  • 如果仅使用want迁移数据,无需申请权限。
  • 如果需要使用分布式文件或分布式对象迁移数据,需要申请权限
  • 如果未申请ohos.permission.DISTRIBUTED_DATASYNC权限,虽然会在组网内设备上出现接续应用图标,但是由于数据无法传输,会导致接续失败。
  • 如果未将continuable配置为true,则在用户应用程序在组网内设备的docker栏上不会出现图标,无法触发应用迁移。
  1. 数据迁移需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,详见声明权限
  2. 由于数据迁移使用权限需要用户授权,所以在应用首次启动时弹窗向用户申请授权,详见向用户申请授权
 ​

module.json5文件的abilities中,将continuable标签配置为true,表示该UIAbility可被迁移。配置为falseUIAbility将被系统识别为无法迁移且该配置默认值为false

 {
     "module": {
         // module其他属性已省略
         ... 
         "abilities": [
             {
                 // 接续能力是否可用配置开关
                 "continuable": true,        //  配置系统迁移能力
                 "launchType": "singleton"   //  设置系统启动模式
                 // ability其他属性已省略
                 ... 
             }
         ],
         "requestPermissions": [
             {
                 // 申请权限
                 "name": "ohos.permission.DISTRIBUTED_DATASYNC",
                 "reason": "$string:distributed_data_sync",
                 "usedScene": {
                     "abilities": [
                         "EntryAbility"
                     ],
                     "when": "inuse"
                 }
             }
         ]
     }
 }
 import UIAbility from '@ohos.app.ability.UIAbility';
 import { abilityAccessCtrl, bundleManager, Permissions, Want, AbilityConstant } from '@kit.AbilityKit';
 ​
 ​
 export default class EntryAbility extends UIAbility {
     onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
         this.checkPermissions()
     }
     
     /**
      * 获取用户授权
      */
     async checkPermissions(): Promise<void> {
         const permissions: Array<Permissions> = ['ohos.permission.DISTRIBUTED_DATASYNC']
         const accessManager = abilityAccessCtrl.createAtManager()
         try {
             const bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
             const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleFlags)
             const tokenId = bundleInfo.appInfo.accessTokenId
             //  获取用户授权状态,检查用户是否授权
             const grantStatus = await accessManager.checkAccessToken(tokenId, permissions[0])
             //  未获得授权,则需要向用户申请授权
             if (abilityAccessCtrl.GrantStatus.PERMISSION_DENIED === grantStatus) {
                 accessManager.requestPermissionsFromUser(this.context, permissions)
             }
         } catch (error) {
             console.log(`用户授权失败,失败信息为:${error}`)
         }
     }
 }
 //  源端:就是当前正在使用的设备,实现源端回调
 import UIAbility from '@ohos.app.ability.UIAbility';
 import { AbilityConstant } from '@kit.AbilityKit';
 ​
 ​
 export default class EntryAbility extends UIAbility {
     onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
         return AbilityConstant.OnContinueResult.AGREE
     }
 }
 //  对端:实现对端回调
 import UIAbility from '@ohos.app.ability.UIAbility';
 import { Want, AbilityConstant } from '@kit.AbilityKit';
 ​
 ​
 export default class EntryAbility extends UIAbility {
     onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
         this.checkPermissions()
         //  判断是否被迁移
         if (AbilityConstant.LaunchReason.CONTINUATION === launchParam.launchReason) {
             //  如果是迁移,则需要手动出发页面刷新操作
             this.context.restoreWindowStage(new LocalStorage())
         }
     }
 }

分布式数据传输

组件迁移数据

image-20240712001147.png

部分组件支持分布式数据迁移,如:ListGridScrollWaterFlow

同时还需要设置迁移组件的restoreId,这样就能完成自动迁移

 @Entry
 @Component
 struct Index {
     @State message: string = 'Hello World';
 ​
     build() {
         Column() {
             List({ space: 20 }) {
                 ForEach(this.list, (item: number) => {
                     ListItem() {...}
                                }, (item: number) => (item.toString()))
                 }.restoreId(1)
             }
         }
     }
 }

页面栈迁移

系统默认迁移页面栈,如果不希望迁移页面栈,可以将 wantParam["ohos.extra.param.key.supportContinuePageStack"] 设置为false

无论是否需要迁移页面栈,在对端的onCreate 函数中,只要是迁移的场景就要主动触发页面恢复

 import UIAbility from '@ohos.app.ability.UIAbility';
 import { abilityAccessCtrl, bundleManager, Permissions, Want, AbilityConstant } from '@kit.AbilityKit';
 ​
 ​
 export default class EntryAbility extends UIAbility {
     onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
         this.checkPermissions()
     }
     
     onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
         //  配置迁移页面栈和页面状态
         wantParam["ohos.extra.param.key.supportContinuePageStack"] = false
         return AbilityConstant.OnContinueResult.AGREE
     }
 }

少量状态数据迁移

少量状态数据迁移一般是小于100KB的状态数据

 //  源端:实现源端回调
 onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
     let sessionId: string = AppStorage.get('sessionId') as string
     wantParam['sessionId'] = AppStorage.Get<string>('sessionId')
 }
 //  对端:恢复数据
 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
     //  判断是否被迁移
     if (AbilityConstant.LaunchReason.CONTINUATION === launchParam.launchReason) {
         AppStorage.setOrCreate<string>('sessionId', want.parameters?.sessionId)
         //  如果是迁移,则需要手动出发页面刷新操作
         this.context.restoreWindowStage(new LocalStorage())
     }
 }

内存数据迁移

适用于临时数据,生命周期较短,通常保存在内存数据中的,可以封装成一个对象,使用分布式数据对象的方式进行迁移

 onWindowStageCreate(windowStage: window.WindowStage) {
     ...
     if (!this.localObject) {
         //  创建分布式数据对象
         let mailInfo: MailInfo = new MailInfo(undefined, undefined, undefined, undefined)
         this.localObject = distributedDataObject.create(this.context, mailInfo)
     }
 ​
     ...
 }
 onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
 ​
     try {
         let sessionId: string = AppStorage.get('sessionId') as string
         if (!sessionId) {
             sessionId = distributedDataObject.genSessionId()
             AppStorage.setOrCreate('sessionId', sessionId)
         }
         if (this.localObject) {
             //  设置 sessionId,将 sessionId 与对象绑定
             this.localObject.setSessionId(sessionId)
             //  将数据保存到分布书
             this.localObject['recipient'] = AppStorage.get('recipient');
             this.localObject['sender'] = AppStorage.get('sender');
             this.localobject['subject'] = AppStorage.get('subject');
             this.localObject['emailContent'] = AppStorage.get('emaiLContent');
             //  触发对象同步
             this.localobject.save(wantParam.targetDevice as string);
             wantParam.distributedSessionId = sessionId:
         }
     } catch(error) {
         
     }
 }
 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
     //  判断是否被迁移
     if (AbilityConstant.LaunchReason.CONTINUATION === launchParam.launchReason) {
         //  从 want 中获取 sessionId
         let sessionId: string = AppStorage.get('sessionId') as string
         if (!this.localObject) {
             //  创建分布式数据对象
             let mailInfo: MailInfo = new MailInfo(undefined, undefined, undefined, undefined)
             this.localObject = distributedDataObject.create(this.context, mailInfo)
             //  监听分布式数据对象的数据变更
             this.localObject.on('change', this.changeCall)
         }
         if (sessionId && this.localObject) {
             //  设置 sessionId,将 sessionId 与对象绑定
             this.localObject.setSessionId(sessionId)
             //  从分布式数据对象中恢复数据
             AppStorage.setOrCreate('recipient', this.localObject['recipient']);
             AppStorage.setOrCreate('sender', this.localObject['sender']);
             AppStorage.setOrCreate('subject', this.localObject['subject']);
             AppStorage.setOrCreate('emaiLContent', this.localObject['emaiLContent']);
         }
         this.context.restoreWindowStage(new LocalStorage())
     }
 }

image-20240712095141.png