Harmony应用开发笔记-全套笔记(更新中)

Harmony应用开发笔记-全套笔记(更新中)

[TOC]

鸿蒙开发分为应用开发和设备开发。

基础知识

*config.json细节问题

config.json中,主要分为三大部分

app

整个应用的配置信息。同一个应用的不同HAP包的“app”配置必须保持一致。

  • bundleName:表示应用的包名,标识应用的唯一性
  • vendor:标识对应用开发厂商的描述
  • version:应用的版本。
    • code:应用的版本号。仅用于HarmonyOS管理该应用,对用户不可空间,取值为大于0的整数。
    • name:表示应用的版本号。向用户呈现,取值自定义。
  • apiVersion:表示依赖的HarmonyOS的API版本。
    • compatible:表示应用运行需要的API最小版本。取值为大于0的整数。
    • target:表示应用运行需要的API目标版本。取值为大于0的整数。

deviceConfig

表示应用在具体设备上的配置

module

标识HAP的配置信息,表示该标签下的配置只对当前HAP包生效。

资源文件

资源文件的分类

resources目录

应用的资源文件(字符串、图片、音频等)统一存放于resources目录下,便于开发者使用和维护。resources目录包括两大类目录。

  • base目录 与 限定词目录

    • base目录:当应用中没有与设备相匹配的限定词目录的时候,会自动引用目录中的资源文件。
    • 限定词目录:目录名称由一个或多个特征限定词组合而成,与设备或者应用适配,子目录和base目录相同。
    • 没有找到限定词目录,就去找base目录
  • rawfile 目录

    rawfile目录中的资源文件会被直接打包进应用,不经过编译,不可以用ResourcesTable引用,下面的子目录和文件的名字自定义。

资源目录示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resources
|---base // 默认存在的目录
| |---element // 元素资源
| | |---string.json
| |---graphic // 绘制资源
| | |---btn_graphic.xml
|---en_GB-vertical-car-mdpi // 限定词目录示例,需要开发者自行创建
| |---layout // 布局资源
| | |---main.xml
| |---profile // 其他类型资源,原始文件形式保存
| | |---icon.png
| |---animation // 动画资源
| | |---demo.xml
|---rawfile // 默认存在的目录

限定词目录

限定词目录可以由一个或多个表征应用场景或设备特征的限定词组合而成。

包括移动国家码和移动网络码、语言、文字、国家或地区、横竖屏、设备类型、颜色模式和屏幕密度等维度,限定词之间通过下划线(_)或者中划线(-)连接。

需要遵守的顺序和格式:

语言-文字-国家或地区-横竖屏-设备类型-屏幕密度

资源组目录

base目录与限定词目录下面可以创建资源组目录(包括element、media、animation、layout、graphic、profile),用于存放特定类型的资源文件

资源组目录 目录说明 资源文件
element 表示元素资源,以下每一类数据都采用相应的JSON文件来表征。
boolean,布尔型
color,颜色
float,浮点型
intarray,整型数组
integer,整型
pattern,样式
plural,复数形式
strarray,字符串数组
string,字符串
element目录中的文件名称建议与下面的文件名保持一致。每个文件中只能包含同一类型的数据。
boolean.json
color.json
float.json
intarray.json
integer.json
pattern.json
plural.json
strarray.json
string.json
media 表示媒体资源,包括图片、音频、视频等非文本格式的文件。 文件名可自定义,例如:icon.png。
animation 表示动画资源,采用XML文件格式。 文件名可自定义,例如:zoom_in.xml。
layout 表示布局资源,采用XML文件格式。 文件名可自定义,例如:home_layout.xml。
graphic 表示可绘制资源,采用XML文件格式。 文件名可自定义,例如:notifications_dark.xml。
profile 表示其他类型文件,以原始文件形式保存。 文件名可自定义。

资源文件的使用

base目录与限定词目录中的资源文件:通过指定资源类型(type)和资源名称(name)来引用。

  1. Java文件引用资源文件的格式:
    1. 普通资源:ResourceTable.type_name。
    2. 系统资源,则采用:ohos.global.systemres.ResourceTable.type_name
    3. 目前系统所支持的系统资源
      1. ic_app:默认应用图标,类型为:媒体
      2. request_location_reminder_title:表示“请求使用设备定位功能”的提示标题,类型为字符串
      3. request_location_reminder_content:表示“请求使用设备定位功能”的提示内容,类型为字符串

Ability框架

Ability

Ability是应用所具备能力的抽象,也是应用程序的重要组成部分。Ability可以分为FA(Feature Ability)和PA(Particle Ability)两种类型,每种类型为开发者提供了不同的模板,以便实现不同的业务功能。

FA支持Page Ability,PA支持Service Ability和Data Ability。

Page Ability

Page模板(以下简称“Page”)是FA唯一支持的模板,用于提供与用户交互的能力。一个Page可以由一个或多个AbilitySlice构成,AbilitySlice是指应用的单个页面及其控制逻辑的总和。

AbilitySlice路由配置

可以通过**addActionRoute()**方法为此AbilitySlice配置一条路由规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在 MyAbility 中
public class MyAbility extends Ability {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
// set the main route
setMainRoute(MainSlice.class.getName());

// set the action route
addActionRoute("action.pay", PaySlice.class.getName());
addActionRoute("action.scan", ScanSlice.class.getName());
}
}

在 config.json 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"module": {
"abilities": [
{
"skills":[
{
"actions":[
"action.pay",
"action.scan"
]
}
]
...
}
]
...
}
...
}
  • 同一Page内导航

当发起导航的AbilitySlice和导航目标的AbilitySlice处于同一个Page时,可以通过present()方法实现导航。

如果开发者希望在用户从导航目标AbilitySlice返回时,能够获得其返回结果,则应当使用presentForResult()实现导航。用户从导航目标AbilitySlice返回时,系统将回调onResult()来接收和处理返回结果,开发者需要重写该方法。返回结果由导航目标AbilitySlice在其生命周期内通过setResult()进行设置。

  • 不同Page间导航

AbilitySlice作为Page的内部单元,以Action的形式对外暴露,因此可以通过配置Intent的Action导航到目标AbilitySlice。Page间的导航可以使用startAbility()或startAbilityForResult()方法,获得返回结果的回调为onAbilityResult()。在Ability中调用setResult()可以设置返回结果。

*跨设备迁移

在 HarmonyOS 中,分布式任务调度平台对搭载 HarmonysOS 的多设备构筑的“超级虚拟终端”提供统一的组件管理能力,为应用定义统一的能力极限、接口形式、数据结构、服务描述语言,屏蔽硬件差异;支持远程启动、远程调用、业务无缝迁移等分布式任务。

实现调度的约束与限制
  1. 远程调用 PA/FA,开发者需要在 Intent 中设置分布式的标记(例如:Intent.FLAG_ABILITYSLICE_DEVICE 表示该应用支持分布式调度),否则将无法获得分布式能力。

  2. 开发者可以通过 config.josn 中的 reqPermissions 字段里添加权限申请:

    1. 以获取跨设备链接的能力和分布式数据传输的权限。

      分布式数据传输的权限:

      {"name":"ohos.permission.servicebus.ACCESS_SERVICE"}
      三方应用使用权限:

      {"name":"ohos.permission.servicebus.DISTRIBUTED_DATASYNC"}
      系统应用使用权限:

      {"name":"ohos.huawei.hwddmp.servicebus.BIND_SERVICE"}

    2. 另外还有三个获取分布式设备信息需要的权限:

      "name":"ohos.permission.DISTURIBUTED_DEIVCE_STATE_CHANGE"
      "name":"ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"

      "name":"ohos.permission.GET_BUNEDLE_INFO"

      注意:还需要在Ability里主动声明需要用到的权限。

    3. FA(Feature Ability,Page模板的Ability)的调用支持启动和迁移行为,在进行调度时:

      1. 当启动FA时,需要开发者在Intent中指定对端设备的deviceIdbundleNameabilityName
      2. FA的迁移实现相同bundleNameabilityName的FA跨设备迁移,因此需要指定迁移设备的deviceId
    三类场景

    设备A(本地)、设备B(远端)。

    1. A 启动 B 的 FA
    2. A 的 FA 迁移至设备 B
    3. A 的 FA 迁移至设备 B,可主动撤回迁移。
    需要用到的接口
    1. 启动远程 FA

      startAbility(Intent intent) 接口提供启动指定设备上 FA 和 PA 的能力,Intent 指定带启动 FA 的设备 deviceIdbundleNameabilityName

    2. 迁移 FA

      continueAbility(String deviceId) 接口提供将本地 FA 迁移到指定设备上的能力。

      continueAbilityReversibly(String deviceId) 接口提供将本地 FA 迁移到指定设备上的能力,这种迁移可撤回,reverseContinueAbility() 接口提供撤回迁移的能力。

Service Ability

基于Service模板的Ability(以下简称“Service”)主要用于后台运行任务(如执行音乐播放、文件下载等),但不提供用户交互界面。Service可由其他应用或Ability启动,即使用户切换到其他应用,Service仍将在后台继续运行。

Service是单实例的。在一个设备上,相同的Service只会存在一个实例。

如果多个Ability共用这个实例,只有当与Service绑定的所有Ability都退出后,Service才能够退出。

由于Service是在主线程里执行的,因此,如果在Service里面的操作时间过长,开发者必须在Service里创建新的线程来处理(详见线程间通信),防止造成主线程阻塞,应用程序无响应。

创建 Serice

创建 service 很简单,只需要继承 Ability,然后在 config.json 中注册 service 即可。

  • onStart()

    该方法在创建Service的时候调用,用于Service的初始化。在Service的整个生命周期只会调用一次,调用时传入的Intent应为空。

  • onCommand()

    在Service创建完成之后调用,该方法在客户端每次启动该Service时都会调用,开发者可以在该方法中做一些调用统计、初始化类的操作。

  • onConnect()

    在Ability和Service连接时调用,该方法返回IRemoteObject对象,开发者可以在该回调函数中生成对应Service的IPC通信通道,以便Ability与Service交互。Ability可以多次连接同一个Service,系统会缓存该Service的IPC通信对象,只有第一个客户端连接Service时,系统才会调用Service的onConnect方法来生成IRemoteObject对象,而后系统会将同一个RemoteObject对象传递至其他连接同一个Service的所有客户端,而无需再次调用onConnect方法。

  • onDisconnect()

    在Ability与绑定的Service断开连接时调用。

  • onStop()

    在Service销毁时调用。Service应通过实现此方法来清理任何资源,如关闭线程、注册的侦听器等。

启动/停止 Service

Ability为开发者提供了startAbility()方法来启动另外一个Ability。因为Service也是Ability的一种,开发者同样可以通过将Intent传递给该方法来启动Service。不仅支持启动本地Service,还支持启动远程Service。启动 service 主要用于启动一个服务后台任务或者远程启动一个功能,不进行通信,不需要返回数据。 例如,设备A启动设备B,播放音乐。

开发者可以通过构造包含DeviceId、BundleName与AbilityName的Operation对象来设置目标Service信息。这三个参数的含义如下:

  • DeviceId:表示设备ID。如果是本地设备,则可以直接留空;如果是远程设备,可以通过ohos.distributedschedule.interwork.DeviceManager提供的getDeviceList()获取设备列表,详见Java API参考
  • BundleName:表示包名称。
  • AbilityName:表示待启动的Ability名称。

停止Service

Service一旦创建就会一直保持在后台运行,除非必须回收内存资源,否则系统不会停止或销毁Service。开发者可以在Service中通过terminateAbility()停止本Service或在其他Ability调用stopAbility()来停止Service。

停止Service同样支持停止本地设备Service和停止远程设备Service,使用方法与启动Service一样。一旦调用停止Service的方法,系统便会尽快销毁Service。

生命周期

根据调用方法的不同,其生命周期有以下两种路径:

  • 启动Service

    该Service在其他Ability调用startAbility()时创建,然后保持运行。其他Ability通过调用stopAbility()来停止Service,Service停止后,系统会将其销毁。

  • 连接Service

    该Service在其他Ability调用connectAbility()时创建,客户端可通过调用disconnectAbility()断开连接。多个客户端可以绑定到相同Service,而且当所有绑定全部取消后,系统即会销毁该Service。

    connectAbility()也可以连接通过startAbility()创建的Service。

*连接/停止 Service

启动和链接的差异

启动是无需数据传输的,链接是有数据传输的。

连接 Service 后,可以进行通信,需要连接后的 Service 执行任务之后返回数据,要用连接,就不能使用启动

Data Ability

使用Data模板的Ability(以下简称“Data”)有助于应用管理其自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。Data既可用于同设备不同应用的数据共享,也支持跨设备不同应用的数据共享。

数据的存放形式多样,可以是数据库,也可以是磁盘上的文件。Data对外提供对数据的增、删、改、查,以及打开文件等接口,这些接口的具体实现由开发者提供。

Intent

Intent是对象之间传递信息的载体。例如,当一个Ability需要启动另一个Ability时,或者一个AbilitySlice需要导航到另一个AbilitySlice时,可以通过Intent指定启动的目标同时携带相关数据。Intent的构成元素包括Operation与Parameters。

属性 子属性 描述
Operation Action 表示动作,通常使用系统预置Action,应用也可以自定义Action。例如IntentConstants.ACTION_HOME表示返回桌面动作。
Entity 表示类别,通常使用系统预置Entity,应用也可以自定义Entity。例如Intent.ENTITY_HOME表示在桌面显示图标。
Uri 表示Uri描述。如果在Intent中指定了Uri,则Intent将匹配指定的Uri信息,包括scheme, schemeSpecificPart, authority和path信息。
Flags 表示处理Intent的方式。例如Intent.FLAG_ABILITY_CONTINUATION标记在本地的一个Ability是否可以迁移到远端设备继续运行。
BundleName 表示包描述。如果在Intent中同时指定了BundleName和AbilityName,则Intent可以直接匹配到指定的Ability。
AbilityName 表示待启动的Ability名称。如果在Intent中同时指定了BundleName和AbilityName,则Intent可以直接匹配到指定的Ability。
DeviceId 表示运行指定Ability的设备ID。
Parameters - Parameters是一种支持自定义的数据结构,开发者可以通过Parameters传递某些请求所需的额外信息。

当Intent用于发起请求时,根据指定元素的不同,分为两种类型:

  • 如果同时指定了BundleName与AbilityName,则根据Ability的全称(例如“com.demoapp.FooAbility”)来直接启动应用。
  • 如果未同时指定BundleName和AbilityName,则根据Operation中的其他属性来启动应用。

说明

Intent设置属性时,必须先使用Operation来设置属性。如果需要新增或修改属性,必须在设置Operation后再执行操作。

公共事件和通知

*公共事件

CES(Common Event Service,公共事件服务):为应用程序提供订阅、发布、退订公共事件的能力,通过 ANS(Advanced Notification Service,通知增强服务)系统为应用程序提供发布通知的能力。

公共事件可分为

  • 系统公共事件
  • 自定义公共事件

通知

HarmonyOS提供了通知功能,即在一个应用的UI界面之外显示的消息,主要用来提醒用户有来自该应用中的信息。当应用向系统发出通知时,它将先以图标的形式显示在通知栏中,用户可以下拉通知栏查看通知的详细信息。常见的使用场景:

  • 显示接收到短消息、即时消息等。
  • 显示应用的推送消息,如广告、版本更新等。
  • 显示当前正在进行的事件,如播放音乐、导航、下载等。

NotificationSlot

NotificationSlot可以设置公共通知的震动,重要级别等,并通过调用NotificationHelper.addNotificationSlot()发布NotificationSlot对象。

一个应用可以创建一个或多个NotificationSlot,在发布通知时,通过绑定不同的NotificationSlot,实现不同用途。

  • 说明

NotificationSlot需要先通过NotificationHelper的addNotificationSlot(NotificationSlot)方法发布后,通知才能绑定使用;所有绑定该NotificationSlot的通知在发布后都具备相应的特性,对象在创建后,将无法更改这些设置,对于是否启动相应设置,用户有最终控制权。

不指定NotificationSlot时,当前通知会使用默认的NotificationSlot,默认的NotificationSlot优先级为LEVEL_DEFAULT。

*IntentAgent开发

IntentAgent封装了一个指定行为的Intent,可以通过triggerIntentAgent接口主动触发,也可以与通知绑定被动触发。具体的行为包括:启动Ability和发布公共事件。例如:收到通知后,在点击通知后跳转到一个新的Ability,不点击则不会触发。

*后台代理定时提醒开发

在应用开发时,可以调用后台代理提醒类ReminderRequest去创建定时提醒,包括倒计时、日历、闹钟三种提醒类型。使用后台代理提醒能力后,应用可以被冻结或退出,计时和弹出提醒的功能将被后台系统服务代理。

后台任务调度和管控

HarmonyOS将应用的资源使用生命周期划分为前台、后台和挂起三个阶段。

  • 前台:不受资源调度的约束
  • 后台:根据应用业务的具体任务情况进行资源使用管理
  • 挂起:会对应用的资源使用进行调度和控制约束,以保障其他体验类业务对资源的竞争使用。

后台任务类型

  1. 无后台业务。
  2. 短时任务。退出后有短时间需要完成的任务,例如数据压缩等。使用时 申请延迟进入挂起状态(Suspend)
  3. 长驻任务:例如后台下载,后台播放音乐等,进入常驻任务避免挂起。

短时任务

遵循如下规则:

  • 申请时机:允许应用在前台或在被挂起之间申请。默认退出再后台有 6~12秒运行时长
  • 超时(TimeOut)系统通过回调通知应用,应用需要 取消对应的延迟 或者 再次申请延迟挂起 。超过时间期限或不处理,将会被强制取消延迟挂起。
  • 取消时机:任务完成后应用应 主动取消延时申请
  • 配额机制:每个应用每天根据用户习惯会有一定的配额,根据用户的习惯动态调整。

长驻任务

长驻任务,可以通过使用长驻任务对应的后台模式保障业务在后台的运行,支撑应用在完成后台的业务。可以参考下一小节——后台模式分类。

后台模式分类

HarmonyOS提供了十种后台模式,供需要在后台做长驻任务的业务使用,具体的后台模式类型如下:

长驻任务后台模式 英文名 描述
数据传输 data-transfer 通过网络/对端设备进行数据下载、备份、分享、传输等业务
播音 audio-playback 音频输出业务
录音 audio-recording 音频输入业务
画中画 picture-in-picture 画中画、小窗口播放视频业务
音视频通话 voip 音视频电话,VoIP业务
导航/位置更新 location 定位、导航业务
蓝牙设备连接及传输 bluetooth-interaction 蓝牙扫描、连接、传输业务
WLAN设备连接及传输 wifi-interaction WLAN扫描、连接、传输业务
屏幕抓取 screen-fetch 录屏、截屏业务
多设备互联 multiDeviceConnection 多设备互联,分布式调度和迁移等业务

托管长驻任务

托管任务

托管任务是系统提供的一种后台代理机制。通过系统提供的代理API接口,用户可以把任务(如后台下载、定时提醒、后台非持续定位)交由系统托管。

类型

  1. 后台非持续性定位(non-sustained Location):如果应用未申请location常驻模式,且在后台依然尝试获取位置信息,此时应用行为被视为使用非持续定位能力,后台非持续定位限制每30分钟提供一次位置信息。应用不需要高频次定位时,建议优先使用非持续定位。
  2. 后台提醒代理(Reminder):后台提醒代理主要提供了一种机制,使开发者在应用开发时,可以调用这些接口去创建定时提醒,包括倒计时、日历、闹钟三种提醒类型。使用后台代理提醒能力后,应用可以被冻结或退出,计时和弹出提醒的功能将被后台系统服务代理。参考:Reminder接口使用说明
  3. 后台下载代理:系统提供DownloadSession接口实现下载任务代理功能,应用提交下载任务后,应用被退出,下载任务仍然可以继续执行,且支持下载任务断点续传。参考:DownloadSession接口使用说明

使用约束

  1. 后台下载代理,系统会根据用户场景和设备状态,对不同的下载任务进行相应的管控,避免影响功耗和性能。
  2. 后台非持续定位和后台提醒代理需要 申请对应的权限 。后台提醒需要申请 ohos.permission.PUBLISH_AGENT_REMINDER 权限,后台非持续定位需要申请 ohos.permission.LOCATION和ohos.permission.LOCATION_IN_BACKGROUND权限。
  3. 资源滥用会影响系统性能和功耗,托管任务类型要与应用类型匹配;匹配规则

FAQ

Frequently Asked Questions

  • 什么样的应用不受后台任务调度和管控的约束,不被挂起(Suspend)?

    原则上所有应用都会受后台任务调度和管控的约束,仅仅是约束的时机和状态不同而已。系统提供了选择设置的方式供用户从使用需求维度干预后台任务调度和管控,以华为手机举例,设置路径:手机管家->应用启动管理->选择手动管理->允许后台活动。

  • 应用后台任务调度和管控与那些因素有关?

    应用后台任务调度和管控以支撑用户使用体验为第一优先级,结合用户使用习惯、用户使用情景状态、设备模式状态、应用分类、应用资源占用统计等多个维度综合判断应用后台任务调度和管控的优先级。

  • 后台任务调度和管控机制的适用范围?

    后台任务调度和管控机制仅对HarmonyOS应用进行调度和管控。

线程管理

不同应用在各自独立的进程中运行。当应用以任何形式启动时,系统为其创建 进程,该进程将持续运行。当进程完成当前任务处于等待状态,且系统资源不足时,系统自动回收。

在启动应用时,系统会为该应用创建一个称为“主线程”的执行线程。该线程随着应用创建或消失,是应用的核心线程。UI界面的显示和更新等操作,都是在主线程上进行。主线程又称UI线程,默认情况下,所有的操作都是在主线程上执行。如果需要执行比较耗时的任务(如下载文件、查询数据库),可创建其他线程来处理。

TaskDispatcher

可以使用“TaskDispatcher”来分发不同的任务。TaskDispatcher是一个任务分发器,它是Ability分发任务的基本接口,隐藏任务所在线程的实现细节。为保证应用有更好的响应性,我们需要设计任务的优先级。在UI线程上运行的任务默认以高优先级运行,如果某个任务无需等待结果,则可以用低优先级。

优先级 详细描述
HIGH 最高任务优先级,比默认优先级、低优先级的任务有更高的几率得到执行。
DEFAULT 默认任务优先级, 比低优先级的任务有更高的几率得到执行。
LOW 低任务优先级,比高优先级、默认优先级的任务有更低的几率得到执行。

TaskDispatcher具有多种实现,每种实现对应不同的任务分发器。在分发任务时可以指定任务的优先级,由同一个任务分发器分发出的任务具有相同的优先级。

  • GlobalTaskDispatcher

    全局并发任务分发器,由Ability执行getGlobalTaskDispatcher()获取。适用于任务之间没有联系的情况。一个应用只有一个GlobalTaskDispatcher,它在程序结束时才被销毁。

    1
    TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
  • ParallelTaskDispatcher

    并发任务分发器,由Ability执行createParallelTaskDispatcher()创建并返回。与GlobalTaskDispatcher不同的是,ParallelTaskDispatcher不具有全局唯一性,可以创建多个。开发者在创建或销毁dispatcher时,需要持有对应的对象引用 。

    1
    2
    String dispatcherName = "parallelTaskDispatcher";
    TaskDispatcher parallelTaskDispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
  • SerialTaskDispatcher

    串行任务分发器,由Ability执行createSerialTaskDispatcher()创建并返回。由该分发器分发的所有的任务都是按顺序执行,但是执行这些任务的线程并不是固定的。如果要执行并行任务,应使用ParallelTaskDispatcher或者GlobalTaskDispatcher,而不是创建多个SerialTaskDispatcher。如果任务之间没有依赖,应使用GlobalTaskDispatcher来实现。它的创建和销毁由开发者自己管理,开发者在使用期间需要持有该对象引用。

    1
    2
    String dispatcherName = "serialTaskDispatcher";
    TaskDispatcher serialTaskDispatcher = createSerialTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
  • SpecTaskDispatcher

    专有任务分发器,绑定到专有线程上的任务分发器。目前已有的专有线程为UI线程,通过UITaskDispatcher进行任务分发。

    UITaskDispatcher:绑定到应用主线程的专有任务分发器, 由Ability执行getUITaskDispatcher()创建并返回。 由该分发器分发的所有的任务都是在主线程上按顺序执行,它在应用程序结束时被销毁。

    1
    TaskDispatcher uiTaskDispatcher = getUITaskDispatcher();

*创建八种任务

*线程间通信开发

EventHandler是HarmonyOS用于处理线程间通信的一种机制,可以通过EventRunner创建新线程,将耗时的操作放到新线程上执行。这样既不阻塞原来的线程,任务又可以得到合理的处理。比如:主线程使用EventHandler创建子线程,子线程做耗时的下载图片操作,下载完成后,子线程通过EventHandler通知主线程,主线程再更新UI。

剪切板

SystemPasteboard提供系统剪贴板操作的相关接口,比如复制、粘贴、配置回调等。PasteData是剪贴板服务操作的数据对象,一个PasteData由若干个内容节点(PasteData.Record)和一个属性集合对象(PasteData.DataProperty)组成。Record是存放剪贴板数据内容信息的最小单位,每个Record都有其特定的MIME类型,如纯文本、HTML、URI、Intent。剪贴板数据的属性信息存在放PasteData.DataProperty中,包括标签、时间戳等。

UI

  • padding 是 内边距,margin 是 外边距

像素单位

vp:是针对应用而言的虚拟尺寸(区别于屏幕硬件本身的像素单位)。

vp = ( px * 160) / PPI ,PPI(Pixels per Inch),每英寸有多少个像素点。

代表的分辨率 屏幕分辨率 换算(px/vp)
240 * 320 120 1 vp = 0.75 vp
320 160 1 vp = 1 vp
480 * 800 240 1 vp = 1.5 vp
720 * 1280 320 1 vp = 2 vp
1920 * 1080 480 1 vp = 3 vp

fp:是大小单位,字体大小。

色彩

美学中的三原色:红黄蓝,但是在计算机中,采用光学三原色是:红、绿、蓝 ,用 0 ~ 255 来表示这三个颜色深浅。

  • 色彩取值:例如:**#BF2568**,再这值中,BF,是红色,25,是绿色,68,是蓝色。为16进制数字。
  • 透明度:在色彩取值中,可以加上透明度:**#00BF2568** ,00,代表全透明,范围是 00 ~ FF
  • 省略问题:三组全相同则可以省略。如:**#778899**,可省略为 #789,而 #778896,不可省略。

权重

权重越大,占比越大。

细节问题

1
2
3
4
5
6
7
<Text
...
ohos:layout_alignment="right"
ohos:right_margin="20vp"/>
<!-- 在这里面 布局默认是从左向右排布的,
需要先设置 layout_alignment 为 right
之后,right_margin 才会生效。-->

常用组件

Text

长和宽的默认单位是:像素(px) ,但是可以自己指定单位(vp/px)。

自动换行

开启自动换行

1
ohos:multiple_line="true"

设置最大行数

1
ohos:max_text_lines="1"

省略

可选值有以下几种

  • auto_scrolling:滚动输出。

使用上述选值,需要配置以下属性,表示滚动的次数,值为:limited、数字。

1
ohos:auto_scrolling_count="unlimited"

滚动速度:单位毫秒

1
ohos:autoscrolling_duration="2000"

最后,还需要在Java代码中开启滚动效果(MainAbilityScilice),

1
2
3
4
public void onClick(Component component) {
//...
text.startAutoScrolling();
}
  • none:同理
  • ellipsis_at_middle:同理
  • ellipsis_at_start:前面的内容省略
  • ellipsis_at_end:同理
1
ohos:truncation_mode="ellipsis_at_start"

时钟组件-Clock

Clock 组件是 Text 组件的子类。

常用属性:

属性名称 功能说明
time 设置开始时间(值为毫秒值) 如果写0,表示从1970年1月1日 0:0:0开始计时 该属性不写。默认是从当前时间开始计时
time_zoom 时区 包括: GMT(格林威治标准时间)
UTC(世界标准时间)
CST(美国、澳大利亚、古巴或中国的标准时间)
DST(夏令时)、 PDT(太平洋夏季时间)
mode_24_hour 按照24小时显示的格式。值为指定的格式。
mode_24_hour 按照24小时显示的格式。值为指定的格式。
mode_24_hour 按照24小时显示的格式。值为指定的格式。如:yyyy年MM月dd日 HH:mm:ss
显示12小时制的:
1
2
ohos:mode_24_hour="false"
ohos:mode_12_hour="yyyy年MM月dd日 HH:mm:ss"

注:在这里面的 y,M,d等形式,需要查阅 JavaAPI

常见方法:

方法名 功能说明
setTime(long time) 传入时间的毫秒值
setTime(long time) 传入时区
set24HourModeEnabled(boolean format24Hour) 参数:false:不按24小时 true:按24小时 默认:true

基本用法:

  • xml 文件布局:
1
2
3
4
<Clock
ohos:height="match_content"
ohos:width="match_content"
ohos:text_size="30fp" />
  • java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//将字符串表示的时间(2021-01-01 11:11:11)转成毫秒值
public static String dateToTimeStamp(String s) throws ParseException{
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(s);
long ts = date.getTime();
String res = String.valueOf(ts);
return res;
}
//将时间的毫秒值转换为时间
public static String timeStampToDate(String s){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long lt = new Long(s);
Date date = new Date(lt);
String res = simpleDateFormat.format(date);
return res;
}

计时器组件-TickTimer

是Text的子类,所以可以使用Text的一些属性。

常见属性

属性名 功能说明
format 设置显示的格式
count_down true倒着计时 false正着计时

常见方法

方法名 功能说明
start() 启动计时器
stop() 暂停计时器
setBaseTime(long base) 设置基准时间,有bug
setCountDown(boolean countDown) true:倒着计时,false:顺着计时
setFormat(String format) 设置显示格式。默认格式为:分钟::秒钟
setTickListener 计时监听

基本用法:

  • xml 文件:
1
2
3
4
5
6
7
8
9
10
11
12
<TickTimer
ohos:id="$+id:my_tt"
ohos:height="60vp"
ohos:width="250vp"
ohos:padding="10vp"
ohos:text_size="20fp"
ohos:text_color="#ffffff"
ohos:background_element="#0000ff"
ohos:text_alignment="center"
ohos:layout_alignment="horizontal_center"
ohos:top_margin="50vp" />
//没有设置时间,默认是从1970年1月1日开始。
  • Java 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
TickTimer tickTimer = (TickTimer)findComponentById(ResourceTable.Id_my_tt);
//可能有bug,里边的事件,时间戳,绝对时间值,测试都不对
//没有设置时间,默认是从1970年1月1日开始。
//设置为0,是从当前时间开始。正数减时间,负数加时间,实际写代码测试一下,是否修改了这个bug
//tickTimer.setBaseTime(时间的毫秒值);

//设置是正着计时还是倒着计时。
//tickTimer.setCountDown(false);

//设置格式
tickTimer.setFormat("mm:ss");
//对时间进行监听
tickTimer.setTickListener(监听回调);
//开始计时
tickTimer.start();
//可能有bug,执行后,后台没停止
tickTimer.stop();
//纯Java实现
//每隔1秒就执行run里面的代码
//只不过没有页面显示而已。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
//...在这里写定时任务
});
}},0,1000);
timer.cancel(); //停止计时

TextField

是Text的子类,用来进行用户输入数据的。

常见属性

属性名称 功能说明
hint 提示文字
basement 输入框基线的颜色
element_cursor_bubble 设置提示气泡
selection_color 选中文字的颜色
element_selection_left_bubble 设置选中之后左边的气泡
element_selection_right_bubble 设置选中之后右边的气泡
text_input_type 输入框中的输入类型(pattern_password密文展示)

案例:

按下按钮查看密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MainAbilitySlice extends AbilitySlice implements Component.TouchEventListener {
TextField tf;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
tf = (TextField) findComponentById(ResourceTable.Id_text);
Button but = (Button) findComponentById(ResourceTable.Id_but);
but.setTouchEventListener(this);
}

//...
@Override
//参数一:现在触摸的按钮。
//参数二:动作对象。
public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
int action = touchEvent.getAction();
if(action == TouchEvent.PRIMARY_POINT_DOWN){
//当按下不松的时候,将文本框中的密码变成明文。
tf.setTextInputType(InputAttribute.PATTERN_NULL);
}else if(action == TouchEvent.PRIMARY_POINT_UP){
//当松开的时候,将文本框中的密码变回密文。
tf.setTextInputType(InputAttribute.PATTERN_PASSWORD);
}
//true:表示触摸事件的后续动作还会进行触发
//false:表示触摸事件只触发第一个按下不松的动作。
return true;
}
}

Button

响应点击事件

方法一:自己编写类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void MainAbilitySlice extends AbilitySlice {

@Override
public void onStart(Intent intent) {
//...
button.setClickedListener(new MyListener());
}
}

class MyListener implements Component.ClickedListener{
@Override
piblic void onClick(Component component) {
//...
}
}

方法二:实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainAbilitySlice extends AbilitySlice implement Component.ClickedListener{

@Overrride
public void onStart(Intent, intent) {
//...
button.setClickedListener(this);
}
//...

@Override
piblic void onClick(Component component) {
//...
}
}

方法三:匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainAbilitySlice extends AbilitySlice {
@Overrride
public void onStart(Intent, intent) {
//...
button.setClickedListener(new Component.ClickListener(){
@Override
piblic void onClick(Component component) {
//...
}
});
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}

方法四:方法引用

1
2
3
4
5
6
7
8
9
10
11
public class MainAbilitySlice extends AbilitySlice {
@Overrride
public void onStart(Intent, intent) {
//...
button.setClickedListener(this::onClick);
}

public void onClick(Component component) {
//...
}
}

禁用点击事件

1
button.setClickable(false);

设置位置

1
button.setTranslation();

双击事件

1
2
3
4
5
6
7
8
9
10
11
12
public class MainAbilitySlice extends AbilitySlice implements Component.DoubleClickedListener {
@Override
public void onStart(Intent intent) {
//...
button.setDoubleClickedListener(this);
}

@Override
public void onDoubleClicked(Component component) {
//...
}
}

长按事件

1
2
3
4
5
6
7
8
9
10
11
12
public class MainAbilitySlice extends AbilitySlice implements Component.LongClickedListener {
@Override
public void onStart(Intent intent) {
//...
button.setLongClickedListener(this);
}

@Override
public void onLongClicked(Component component) {
//...
}
}

滑动 / 触摸 事件

分为三个事件

  • PRIMARY_POINT_DOWN:按下不松开
  • POINT_MOVE:滑动
  • PRIMARY_POINT_UP:抬起
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MainAbilitySlice extends AbilitySlice implements Component.TouchEvenListener {

@Override
public void onStart(Intent intent) {
directionLayout.setTouchEvenListener(this);
}

//记录按下时手指的位置
float startX = 0;
float startY = 0;

/**
* @touchEvent 表示动作是 按下,滑动,抬起。
* @return 返回值:true表示继续执行后面的动作,false表示不会继续执行后面的动作。
*/
@Override
public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
count++;
// 1: 按下,2: 松开,3: 滑动
int action = touchEvent.getAction();
if(action == touchEvent.PRIMARY_POINT_DOWN) {
MmiPoint point = touchEvent.getPointerPosition(0);
startX = point.getX();
startY = point.getY();
//...
}else if(action == touchEvent.POINT_MOVE) {
//...
} else if(action == touchEvent.PRIMARY_POINT_UP) {
//...
}
return true;
}
}

Checkbox

父类是AbsButton,而AbsButton的父类是Button。

常见属性

属性名称 功能说明
marked 多选框的选中状态。true为选中,false为没有选中。
check_element 自定义选择框的样式。样式需要跟marked的值对应。

常见方法

方法名称 功能说明
setChecked 设置多选框的选中状态。true为选中,false为没有选中。
isChecked 判断多选框的选中状态。true为选中,false为没有选中。
setCheckedStateChangedListener 添加一个状态监听事件

基本用法

  • xml 文件
1

  • Java 文件
1

Image

背景:background_element

内容(前景):image_src

基本用法

1
2
3
4
5
<Image
ohos:height="100vp"
ohos:width="100vp"
ohos:image_src="$media:all"
ohos:background_element="#00ff00"/>

裁切缩放

相关方法

方法名 功能说明
setClipGravity 设置剪切对齐模式
setScaleMode 当图像和组件的大小不同时,此方法可以缩放或者剪切图像

图片剪切显示:

  • 代码中:可以用setClipGravity方法

  • xml文件中:可以用clip_alignment属性

  • 上、下、左、右、居中

  • 表示分别按照上、下、左、右、中间部位进行剪切。

图片缩放显示:

  • 代码中:可以用setScaleMode方法
  • xml文件中:可以用scale_mode属性
    • inside:表示将原图按比例缩放到与Image相同或更小的尺寸,并居中显示。 有可能不会填充组件
    • center:表示不缩放,按Image大小显示原图中间部分。
    • stretch:表示将原图缩放到与Image大小一致。 拉伸。将组件填充。
    • clip_center:表示将原图按比例缩放到与Image相同或更大的尺寸,并居中显示。超过组件的部分被剪 切掉。
    • zoom_center:表示原图按照比例缩放到与Image最窄边一致,并居中显示。
    • zoom_end:表示原图按照比例缩放到与Image最窄边一致,并靠结束端显示。
    • zoom_start:表示原图按照比例缩放到与Image最窄边一致,并靠起始端显示。

注意:一般来讲在设置的时候会跟图片保持一致,否则图片会失真。

1
2
3
4
5
6
<Image
ohos:background_element="#00ff00"
缩放
ohos:scale_mode="zoom_center"
剪切
ohos:clip_alignment="top" />

左上角:left|top 单词之间得写在一起。

Dialog

CommonDialog

普通弹窗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    //...
CommonDialog cd = new CommonDialog(this);
//2.因为弹框里面是有默认布局的
cd.setTitleText("系统定位服务已关闭");
cd.setContentText("请打开定位服务,以便司机师傅能够准确接您上车");

// 自动关闭,如果加上,则会造成点击弹窗以外的区域一样可以关闭这个弹窗。
cd.setAutoClosable(true);

//设置按钮
//参数一:按钮的索引 0 1 2
//参数二:按钮上的文字
//参数三:点击了按钮之后能做什么
cd.setButton(0, "设置", new IDialog.ClickedListener() {
@Override
public void onClick(IDialog iDialog, int i) {
//...
//如果点击之后我不需要做任何事情,在第三个参数中传递null就可以了。
}
});

cd.setButton(1, "取消", new IDialog.ClickedListener() {
@Override
public void onClick(IDialog iDialog, int i) {
//销毁弹框
cd.destroy();
}
});

//把弹框显示出来
cd.show();
}

自定义弹窗

  1. 创建弹窗布局,xml布局文件
  2. 在Java类中配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
CommonDialog commonDialog= new CommonDialog(this);
//大小是默认包裹内容的。
//弹框默认是居中放置、透明的、直角,可以把直角设置为圆角
cd.setCornerRadius(15);

//xml文件加载到内存当中。交给弹框并展示出来。
//加载xml文件并获得一个布局对象
//parse方法:加载一个xml文件,返回一个布局对象。
//参数一:要加载的xml文件
//参数二:该xml文件是否跟其他xml文件有关。如果无关是独立的,就写null就可以了
//参数三:如果文件是独立的,那么直接写false
DirectionalLayout directionalLayout =
(DirectionalLayout) LayoutScatter.getInstance(this).parse(ResourceTable.Layout_messagedialog, null, false);

//...
buttom_submit.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
title.setText("点击了确定按钮");
}
});

//取消按钮也要添加点击事件
cancel.setClickedListener(new Component.ClickedListener() {
public void onClick(Component component) {
cd.destroy();
}
});

//此时布局对象跟弹框还没有任何关系,还需要把布局对象交给弹框才可以
cd.setContentCustomComponent(directionalLayout);
//让弹框展示出来
cd.show();

如果需要更复杂的弹框,只要丰富xml文件中的组件即可。

抽取工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MyDialog {
public static void showDialog(Context context,String msg){
CommonDialog cd = new CommonDialog(context);
cd.setCornerRadius(15);
DirectionalLayout dl = (DirectionalLayout) LayoutScatter.getInstance(context).parse(ResourceTable.Layout_messagedialog, null, false);

Text title = (Text) dl.findComponentById(ResourceTable.Id_message);
Button submit = (Button) dl.findComponentById(ResourceTable.Id_submit);
Button cancel = (Button) dl.findComponentById(ResourceTable.Id_cancel);

title.setText(msg);

submit.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
title.setText("点击了确定按钮");
}
});

cancel.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
cd.destroy();
}
});

cd.setContentCustomComponent(dl);
cd.show();
}
}

ToastDialog

吐司弹框。是 CommonDialog 的子类,用法基本相似,但是有自己特性。

也拥有标题,内容和按钮,但是我们只使用按钮。

1
2
3
4
ToastDialog t = new ToastDialog(this);
t.setText("要显示的内容")
t.setAlignment(LayoutAlignment.CENTER);
t.show();

相关设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ToastDialog toastDialog = new ToastDialog(this);
//设置的大小,如果不写,默认包裹内容
toastDialog.setSize(DirectionalLayout.LayoutConfig.MATCH_CONTENT,
DirectionalLayout.LayoutConfig.MATCH_CONTENT);
//设置持续时间,如果不写,默认2秒
toastDialog.setDuration(2000);
//设置自动关闭,如果不写,就是自动关闭
toastDialog.setAutoClosable(true);
//设置位置,如果不写,默认居中
toastDialog.setAlignment(LayoutAlignment.CENTER);
//设置提示信息内容
toastDialog.setText("要显示的内容");
//让吐司展示出来
toastDialog.show();

自定义布局和抽取工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyToastUtils {
public static void showDialog(Context context,String msg){
//1.加载xml布局文件
DirectionalLayout dl = (DirectionalLayout)
LayoutScatter.getInstance(context).parse(ResourceTable.Layout_mytoast, null, false);
//创建吐司弹框的对象
ToastDialog td = new ToastDialog(context);
//设置吐司的大小,宽和高。
td.setSize(DirectionalLayout.LayoutConfig.MATCH_CONTENT,
DirectionalLayout.LayoutConfig.MATCH_CONTENT);
//设置出现的时间
td.setDuration(2000);
//设置自动关闭
td.setAutoClosable(true);
//设置对齐方式
td.setAlignment(LayoutAlignment.CENTER);
//给吐司弹框设置要展示的文本内容
td.setText(msg);
//让吐司弹框出现
td.show();
}
}

偏移

接上面的代码,如果想换个位置,加上偏移即可。

1
td.setOffset(0,200); // x 轴偏移0,y 轴 向上偏移200

进度条

ProgressBar

常见属性

属性名称 功能说明
orientation 进度条的摆放
horizontal:水平
vertical:垂直
progress_color 进度条颜色
progress_width 进度条粗细
progress 当前的进度值
max 进度最大值
min 进度最小值
progress_hint_text 进度条文字
progress_hint_text_size 进度条文字大小
progress_hint_text_color 进度条文字颜色
progress_hint_text_alignment 进度条文字对齐方式

常见方法

方法名 功能说明
setOrientation(int orientation) 方向
setProgressWidth(int progressWidth) 进度条的粗细
setMaxValue 最大进度值
setMinValue 最小进度值
setProgressValue(int progress) 当前的进度值
setViceProgress(int progress) 次一级进度值
(看电影时有个进度,电影的提前缓冲也有个进度)

基本用法

  • xml 文件
1
2
3
4
5
6
7
8
9
<ProgressBar
ohos:height="50vp"
ohos:width="300vp"
ohos:progress_width="8vp"
ohos:progress_color="#ff00ff"
ohos:max="100"
ohos:min="0"
ohos:progress="20"
ohos:top_margin="100vp" />
  • Java 代码
1
2
3
4
5
6
7
8
9
//用点击模拟如接收文件的进度动态改变进度值
ProgressBar progressBar = (ProgressBar) findComponentById(ResourceTable.Id_my_pgb);
progressBar.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
ProgressBar pgb = (ProgressBar) component;
pgb.setProgressValue(pgb.getProgress()+5);
}
});

RoundProgressBar

是ProgressBar的子类,用法跟ProgressBar一模一样,只是显示的方式不一样。圆形进度条。

基本用法:

1
2
3
4
5
6
7
8
9
10
11
<RoundProgressBar
ohos:height="300vp"
ohos:width="300vp"
ohos:progress_hint_text="80%"
ohos:progress_hint_text_size="50vp"
ohos:progress_hint_text_color="#000000"
ohos:progress="80"
ohos:progress_width="20vp"
ohos:progress_color="#FF0000"
ohos:max="100"
ohos:min="0"/>

ListContainer

ListContainer是用来呈现连续、多行数据的组件,包含一系列相同类型的列表项。

三个自有属性

  1. rebound_effect 开启/关闭回弹效果
  2. shader_color 着色器颜色
  3. orientation 列表项排列方向

ListContainer 的使用方法

  1. 在布局中创建 ListContainer
  2. 再创建 ListContainer 的子布局
  3. 创建数据包装类
  4. 创建 Provider 类,继承自 BaseItemProvider ,重写方法
    1. int getCount() 方法,返回填充的表项个数。
    2. Object getItem(int position) 方法, 返回某一项的id。
    3. Component getComponent(int position, Component covertComponent,ComponentContainer componentContainer) 方法,根据 position 返回对应的界面组件。
  5. 在Java代码(MainAbilitySlice)中添加 ListContainer 的数据,并适配其数据结构。
  6. listContainer 在 sampleItemProvider 初始化后修改数据。

示例,前三步略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 数据包装类
package com.felixcjy.mylistdemo;

public class SampleItem {
private String name;

public SampleItem(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 适配器类
public class SampleItemProvider extends BaseItemProvider {

private List<SampleItem> list;
private AbilitySlice slice;

public SampleItemProvider(List<SampleItem> list, AbilitySlice slice) {
this.list = list;
this.slice = slice;
}

@Override
public int getCount() {
return list == null ? 0 : list.size();
}

@Override
public Object getItem(int position) {
if (list != null && position >= 0 && position < list.size()) {
return list.get(position);
}
return null;
}

@Override
public long getItemId(int position) {
//可添加具体处理逻辑
return position;
}

@Override
public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer) {
final Component cpt;
if (convertComponent == null) {
cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_sample, null, false);
} else {
cpt = convertComponent;
}
SampleItem sampleItem = list.get(position);
Text text = (Text) cpt.findComponentById(ResourceTable.Id_item_index);
text.setText(sampleItem.getName());
return cpt;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 添加 ListContainer 的数据
public class MainAbilitySlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
initListContainer();
}

private void initListContainer() {
ListContainer listContainer = (ListContainer) findComponentById(ResourceTable.Id_list_container);
List<SampleItem> list = getData();
SampleItemProvider sampleItemProvider = new SampleItemProvider(list, this);
listContainer.setItemProvider(sampleItemProvider);

list.add(new SampleItem("Item" + sampleItemProvider.getCount()));
listContainer.setBindStateChangedListener(new Component.BindStateChangedListener() {
@Override
public void onComponentBoundToWindow(Component component) {
// ListContainer初始化时数据统一在provider中创建,不直接调用这个接口;
// 建议在onComponentBoundToWindow监听或者其他事件监听中调用。
sampleItemProvider.notifyDataChanged();
}

@Override
public void onComponentUnboundFromWindow(Component component) {}
});

// 点击监听事件
listContainer.setItemClickedListener((container, component, position, id) -> {
SampleItem item = (SampleItem) listContainer.getItemProvider().getItem(position);
new ToastDialog(this)
.setText("you clicked:" + item.getName())
// Toast显示在界面中间
.setAlignment(LayoutAlignment.CENTER)
.show();
});

// 长按监听事件
listContainer.setItemLongClickedListener((container, component, position, id) -> {
SampleItem item = (SampleItem) listContainer.getItemProvider().getItem(position);
new ToastDialog(this)
.setText("you long clicked:" + item.getName())
.setAlignment(LayoutAlignment.CENTER)
.show();
return false;
});
}

private ArrayList<SampleItem> getData() {
ArrayList<SampleItem> list = new ArrayList<>();
for (int i = 0; i <= 8; i++) {
list.add(new SampleItem("Item" + i));
}
return list;
}

@Override
public void onActive() {
super.onActive();
}

@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}

美化组件

美化的文件写在 graphic 的包中。

形状美化

xmlns:ohos="http://schemas.huawei.com/res/ohos" 是命名空间。

根标签:属性 shape 的值:

  • rectangle:长方形
  • oval:圆形

子标签:

  • stroke:边框设置
    • color:颜色
    • width:宽度
  • bounds:边框设置
    • top、right、left、bottom:上下左右单独设置。
  • gradient:渐变
    • shader_tyoe:有三个值
      • sweep_gradient:暂时没有效果。
      • radial_gradient:辐射渐变,solid中需要使用多个颜色。
      • linear_gradient:线性渐变
  • soild:背景色
    • color:颜色
    • colors:多个颜色,需要逗号隔开,ohos:colors="#009911,#889977"
  • corners:设置圆角
    • radius:四个角同时设置,同理:left_top_y;left_top_x;left_bottom_y;left_bottom_x;right_bottom_y;right_bottom_x;right_top_y;right_top_x;
1
2
3
4
5
6
7
8
<shape
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:shape="rectangle">
<stroke
ohos:color="#21ABCD"
ohos:width="2vp"/>
<!-- ... -->
</shape>

状态美化

根标签:state-container

子标签:

  • item
    • state
      • component_state_checked:开启状态
      • component_state_pressed:按下不松开的状态
      • component_state_empty:默认状态(必须要写在最下面
    • element:可写颜色;可添加图片资源

*动画

Java UI框架提供了帧动画、数值动画和属性动画,并提供了将多个动画同时操作的动画集合。

*安全

*权限

相关名词解释

  • 沙盒

    sandbox,为运行中的程序提供的隔离环境。通常作为一些来源不明破坏力无法判断程序意图的程序以供实验使用。

  • 应用沙盒

    鸿蒙采用的是应用沙盒。系统利用内核保护机制来识别和隔离应用资源,可将不同的应用隔离开,保护应用自身和系统免受恶意应用的攻击。默认应用间不能交互调用,对系统的访问会受到限制。

  • 应用权限

    系统通过沙盒机制管理各个应用,默认只允许访问优先的应用资源。但为了扩展功能的需要,需要访问自身沙盒之外的系统或其他应用的数据,包括用户个人数据或能力;系统或应用也必须以明确的方式对外提供接口来共享其数据或能力。应用权限是程序访问操作某种对象的许可。权限在应用层面要求明确定义且经用户授权,以便系统化地规范各类应用程序的行为准则与权限许可。

  • 权限保护的对象

    权限保护的对象可分为数据和能力。数据包含了个人数据(如照片、通讯录)、设备数据(如标识、相机)、应用数据;能力包括了设备能力(如打电话、发信息)、应用能力(如弹出悬浮窗、创建快捷方式等)。

  • 权限开放范

    权限开放范围指一个权限能被哪些应用申请。按可信程度从高到低的顺序,不同权限开放范围对应的应用可分为:系统服务、系统应用、系统预置特权应用、同签名应用、系统预置普通应用、持有权限证书的后装应用、其他普通应用,开放范围依次扩大。

  • 敏感权限

    涉及访问个人数据(如:照片、通讯录、日历、本机号码、短信等)和操作敏感能力(如:相机、麦克风等)的权限。

  • 应用核心功能

    一个应用可能提供了多种功能,其中应用为满足用户的关键需求而提供的功能,称为应用的核心功能。这是一个相对宽泛的概念,本规范用来辅助描述用户权限授权的预期。用户选择安装一个应用,通常是被应用的核心功能所吸引。比如导航类应用,定位导航就是这种应用的核心功能;比如媒体类应用,播放以及媒体资源管理就是核心功能,这些功能所需要的权限,用户在安装时内心已经倾向于授予(否则就不会去安装)。与核心功能相对应的是辅助功能,这些功能所需要的权限,需要向用户清晰说明目的、场景等信息,由用户授权。既不属于核心功能,也不是支撑核心功能的辅助功能,就是多余功能。不少应用存在并非为用户服务的功能,这些功能所需要的权限通常被用户禁止。

  • 最小必要权限

    保障应用某一服务类型正常运行所需要的应用权限的最小集,一旦缺少将导致该类型服务无法实现或无法正常运行的应用权限。

官方示例中,未为找到的权限

权限名称(声明) 说明
ohos.permission.servicebus.ACCESS_SERVICE 分布式软总线
ohos.permission.R 未知

以上两个权限是必须得有的。

权限声明示例

config.json 中声明,声明不一定会获得权限,例如需要用户授权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:permreason_camera",
"usedScene":
{
"ability": ["com.mycamera.Ability", "com.mycamera.AbilityBackground"],
"when": "always"
}
},{
...
}
]
}
}

接口方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// void requestPermissionFromUser(String[] permissions, int requestCode)
// 在 MainAbility 中
// 参数说明
// permissions:权限名列表。
// requestCode:请求应答会带回此编码以匹配本次申请的权限请求。
public class MainAbility extends Ability{
//...
public void onStart(Intent intent) {
//...
String[] permissions = {"ohos.permission.CAMERA"};
requestPermissionsFromUser(permissions,1);
}
}

记住两点

  1. 声明权限
  2. 如果碰到敏感权限,需要通过 requestPermissionFromUser(String[] permissions, int requestCode) 从用户哪里获得权限。

细节参考官网即可。官网:权限开发指导-权限-安全-开发-HarmonyOS应用开发

*生物识别

*网络与链接

HarmonyOS网络管理模块主要提供以下功能:

  • 数据连接管理:网卡绑定,打开URL,数据链路参数查询。
  • 数据网络管理:指定数据网络传输,获取数据网络状态变更,数据网络状态查询。
  • 流量统计:获取蜂窝网络、所有网卡、指定应用或指定网卡的数据流量统计值。
  • HTTP缓存:有效管理HTTP缓存,减少数据流量。
  • 创建本地套接字:实现本机不同进程间的通信,目前只支持流式套接字。

约束与限制

使用网络管理模块的相关功能时,需要请求相应的权限。

权限名 权限描述
ohos.permission.GET_NETWORK_INFO 获取网络连接信息。
ohos.permission.SET_NETWORK_INFO 修改网络连接状态。
ohos.permission.INTERNET 允许程序打开网络套接字,进行网络连接。

使用当前网络打开一个 URL 链接

应用使用当前网络打开一个URL链接,所使用的接口说明如下。

类名 接口名 功能描述
NetManager getInstance(Context context) 获取网络管理的实例对象。
hasDefaultNet() 查询当前是否有默认可用的数据网络。
getDefaultNet() 获取当前默认的数据网络句柄。
addDefaultNetStatusCallback(NetStatusCallback callback) 获取当前默认的数据网络状态变化。
setAppNet(NetHandle netHandle) 应用绑定该数据网络。
NetHandle openConnection(URL url, java.net.Proxy proxy) throws IOException 使用该网络打开一个URL链接。

使用当前网络进行 Socket 数据传输

使用指定网络进行数据访问

流量统计

管理 HTTP 缓存

数据管理

关系型数据库

基本概念

  • 关系型数据库

    基于关系模型来管理数据的数据库,以行和列的形式存储数据。

  • 谓词

    数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。

  • 结果集

    指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便的拿到用户想要的数据。

  • SQLite数据库

    一款轻型的数据库,是遵守ACID的关系型数据库管理系统。它是一个开源的项目。

约束与限制

  • 数据库中连接池的最大数量是4个,用以管理用户的读写操作。

  • 为保证数据的准确性,数据库同一时间只能支持一个写操作。

基本操作见API,这部分较为容易:关系型数据库概述-关系型数据库-数据管理-开发-HarmonyOS应用开发

关系型数据库是在SQLite基础上实现的本地数据操作机制,提供给用户无需编写原生SQL语句就能进行数据增删改查的方法,同时也支持原生SQL语句操作。

接口说明详见API:关系型数据库开发指导-关系型数据库-数据管理-开发-HarmonyOS应用开发

创建前置/手动配置

在数据库开始配置的时候,如下开发步骤中的第一步示例代码中:StoreConfig config = StoreConfig.newDefaultConfig("RdbStoreTest.db");,这里面有七个默认值。

1
2
3
4
5
6
7
8
9
//...
StoreConfig config = StoreConfig.newDefaultConfig("RdbStoreTest.db");
System.out.println(config.getName()); // 数据库名
System.out.println(config.isReadOnly()); // 数据库是否只读
System.out.println(config.getDatabaseFileType()); // 数据库类型,如:NORMAL...
System.out.println(config.getJournalModle()); // 数据库日志模式,默认 null
System.out.println(config.getStoreMode()); // 数据库存储模式,默认:MODE_DISK...
System.out.println(config.getSyncMode()); // 数据库同步模式
System.out.println(config.getEncryptKey); // 数据库密钥,默认数据库是开放的,不加密。

同步模式,在API中实际上是一种枚举

  • MODE_OFF:关闭同步

  • MODE_NORMAL:正常同步

  • MODE_FULL:完全同步
    假设A节点和B节点数据不同,则把A节点所有数据复制给B

  • MODE_EXTRA:增量同步

    原本有一些数据,又增加了一些数据,把增加的作为副本同步给其他的节点

日志模式:鸿蒙默认是:WAL模式

sqlite 目录包含了三个文件:xxx.sqlitexxx.sqlite-shmxxx.sqlite-wal和日志模式:

原生是上述三个文件,此处做了封装,即xxx.dbxxx.db-shmxxx.db-wal

  • DELETE:SQLite默认的日志模式,一般在事务开始时创建回滚日志,事务结束时删除回滚日志。

  • PERSIST:事务结束时不删除回滚日志,而是在日志文件头部覆写0,这样也可以达到删除文件同样的效果,却减少了反复创建删除回滚日志的磁盘开销。

  • MEMORY:将回滚日志存储在内存而不是磁盘中,但因为在磁盘中没有用于恢复的文件,如果在事务中间发生崩溃或者掉电,那么整个数据库就有可能损坏。

  • OFF:禁用 SQLite 原子提交或回滚功能。

  • WAL:预写日志,当数据库连接首次打开时,wal 文件就会被创建。
    当最后一次数据库连接被正常关闭时,wal 文件就会被删除。

    如果最后一次数据库连接没有正常关闭,wal 文件会被保存下来,直到下一次打开数据库时才会被自动删除。

    shm 共享内存文件

    共享内存文件用于提供一块共享内存给多个进程在 WAL 模式中访问相同的数据。

自定义的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String str = "123456"; // 数据库密钥
StoreConfig.Builder builder = new StroeConfig.Builder();
builder.setName("mydb.db")
.getSyncMode(null)
.getJournalMode(StoreConfig.JournalMode.MODE_WAL)
.setStroageMode(StroeConfig.StroageMode.MODE_DISK)
.setReadOnly(false)
.setDatabaseFileType(DatabaseFileType.NORMAL)
.setEncryptKey(str.getBytes());
StroeConfig config = builer.build();
// 创建数据库
DatabaseHelper helper = new DatabaseHelper(this);
// 第二次打开的 callback 里面 OnCreate方法就不需要重新创建数据库了,需要修改。
RdbStore rdbStore = helper.getRdbStroe(config,1,callback,null);

使用带密钥的数据库

注意:细节问题,在数据打开之后,不允许再对数据库进行设置(Config)。

再次使用还是相同的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
String str = "123456"; // 数据库密钥
StoreConfig.Builder builder = new StroeConfig.Builder();
builder.setName("mydb.db")
.getSyncMode(null)
.getJournalMode(StoreConfig.JournalMode.MODE_WAL)
.setStroageMode(StroeConfig.StroageMode.MODE_DISK)
.setReadOnly(false)
.setDatabaseFileType(DatabaseFileType.NORMAL)
.setEncryptKey(str.getBytes());
StroeConfig config = builer.build();
// 需要修改这里的 this 为 MainAbility.this,参数为 Content 类型。
DatabaseHelper helper = new DatabaseHelper(MainAbility.this);
RdbStore rdbStore = helper.getRdbStroe(config,1,callback,null);

关闭数据库:

1
rdbStroe.close();

注意:关闭数据库以后,使用不同的密钥打开同一个数据库是会报错的:file is not a database.

开发步骤

  1. 创建数据库。

    1. 配置数据库相关信息,包括数据库的名称、存储模式、是否为只读模式等。
    2. 初始化数据库表结构和相关数据。
    3. 创建数据库。

    示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    DatabaseHelper helper = new DatabaseHelper(context);
    StoreConfig config = StoreConfig.newDefaultConfig("RdbStoreTest.db");
    RdbOpenCallback callback = new RdbOpenCallback() {
    @Override
    public void onCreate(RdbStore store) {
    // 见下面的解释,一般在此作初始化操作。下文中的 REAL/BLOB,见后续解释。
    store.executeSql("CREATE TABLE IF NOT EXISTS test ("+
    "id INTEGER PRIMARY KEY AUTOINCREMENT,"+
    " name TEXT NOT NULL, age INTEGER, salary REAL, blobType BLOB)");
    }
    @Override
    public void onUpgrade(RdbStore store, int oldVersion, int newVersion) {}
    };
    RdbStore store = helper.getRdbStore(config, 1, callback, null);

RdbOpenCallback 是抽象类,所以创建的时候会自动实现抽象方法。

  • onCreate(RdbStore var1) 方法,值:创建数据库的时候会回调,并创建好数据库。
  • onUpgrade(RdbStore var1, int var2, int var3):数据库升级的时候会回调。
  • onDowngrade(RdbStore store, int currentVersion, int targetVersion):数据库降级的时候会回调。
  • onOpen(RdbStore store):打开数据库的时候回调。
  • onCorruption(File databaseFile):数据库出现问题的时候会回调此方法。

REAL 数据类型

real型数据的存储大小为4个字节,可精确到小数点后第7位数字。

这种数据类型的数据存储范围为从-3.40E+38~-1.18E-38,0和1.18E-38~3.40E+38。

在MYSQL中real的同义词是float,用法几乎一样。


BLOB 数据类型

在MySQL中Blob是一个二进制的对象,它是一个可以存储大量数据的容器(如图片,音乐等等),且能容纳不同大小的数据,在MySQL中有四种Blob类型,他们的区别就是可以容纳的信息量不容分别是以下四种:
①TinyBlob类型 最大能容纳255B的数据
②Blob类型 最大能容纳65KB的
③MediumBlob类型 最大能容纳16MB的数据
④LongBlob类型 最大能容纳4GB的数据
而在我们实际使用的时候,可以根据自己的需求选择这几种类型,但是如果Blob中存储的文件的大小过大的话,会导致数据库的性能很差。下面具体介绍一下插入Blob类型的数据以及读取Blob类型的数据的方式:
1、插入Blob类型的数据
插入Blob类型的数据时,需要注意必须要用PreparedStatement,因为Blob类型的数据是不能够用字符串来拼的,在传入了SQL语句后,就需要去调用PreparedStatement对象中的setBlob(int index , InputStream in)方法来设置传入的的参数,其中index表示Blob类型的数据所对应的占位符(?)的位置,而InputStream类型的in表示被插入文件的节点流。
2、读取Blob类型的数据
读取Blob类型相对来说比较容易,当获取了查询的结果集之后,使用getBlob()方法读取到Blob对象,然后调用Blob的getBinaryStream()方法得到输入流,再使用IO操作进行文件的写入操作即可。
————————————————
版权声明:本文为CSDN博主「鲸鱼programmer」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_38785658/article/details/75009700

  1. 插入数据。

    关系型数据库提供了插入数据的接口,通过ValuesBucket输入要存储的数据,通过返回值判断是否插入成功,插入成功时返回最新插入数据所在的行号,失败时则返回-1。

    1. 构造要插入的数据,以ValuesBucket形式存储。
    2. 调用关系型数据库提供的插入接口。

    示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    ValuesBucket values = new ValuesBucket();
    values.putInteger("id", 1);
    values.putString("name", "zhangsan");
    values.putInteger("age", 18);
    values.putDouble("salary", 100.5);
    values.putByteArray("blobType", new byte[] {1, 2, 3});
    //插入成功时返回最新插入数据所在的行号,失败时则返回-1。
    long id = store.insert("test", values);

    插入多条数据,放入List中,循环即可。

  2. 查询数据。

    1. 构造用于查询的谓词对象,设置查询条件。
    2. 指定查询返回的数据列。
    3. 调用查询接口查询数据。
    4. 调用结果集接口,遍历返回结果。

    示例代码如下:

    1
    2
    3
    4
    String[] columns = new String[] {"id", "name", "age", "salary"};
    RdbPredicates rdbPredicates = new RdbPredicates("test").equalTo("age", 25).orderByAsc("salary");
    ResultSet resultSet = store.query(rdbPredicates, columns);
    resultSet.goToNextRow();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 谓词方式查询,降序排列
    RdbPredicates predicates = new RdbPredicates("test").equalTo("age","19").orderByDesc("salary");
    String[] columns = new String[] {"id", "name", "age", "salary"};
    ResultSet resultSet = rdbStore.query(predicates, columns);
    // 原生语句查询
    // ResultSet resultSet = rdbStore.querySQL(
    // "select id,name,age,salary from test where age=? order by salary desc",new String[] {"19});
    for (int i = 0; i < resultSet.getRowCount(); i++) {
    resultSet.goToRow(i);
    HiLog.info(hiloglabel,log_format,"=======+resId: "+resultSet.getInt(resultSet.getColumnIndexForName("id")));
    HiLog.info(hiloglabel,log_format,"=======+resName: "+resultSet.getString(resultSet.getColumnIndexForName("name")));
    HiLog.info(hiloglabel,log_format,"=======+resAge: "+resultSet.getInt(resultSet.getColumnIndexForName("age")));
    HiLog.info(hiloglabel,log_format,"=======+resSalary: "+resultSet.getDouble(resultSet.getColumnIndexForName("salary")));
    }
  3. 更新数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 用谓词更新
    ValuesBucket replaceVB = new ValuesBucket();
    replaceVB.putString("name","wangwu2");
    RdbPredicates replace_rdbPredicates = new RdbPredicates("test").equalTo("id",3);
    int update = rdbStore.update(replaceVB,replace_rdbPredicates);
    // 原生SQL更新
    // rdbStore.executeSql("update test set name='wangwu2' where id=?",new Object[]{3});
    // 查看更新后的结果
    ResultSet resultSet2 = rdbStore.querySql("select id,name,age,salary from test where id=?",new String[] {"3"});
    for (int i = 0; i < resultSet2.getRowCount(); i++) {
    resultSet2.goToRow(i);
    HiLog.info(hiloglabel,log_format,"=======+resId: "+resultSet2.getInt(resultSet.getColumnIndexForName("id")));
    HiLog.info(hiloglabel,log_format,"=======+resName: "+resultSet2.getString(resultSet.getColumnIndexForName("name")));
    HiLog.info(hiloglabel,log_format,"=======+resAge: "+resultSet2.getInt(resultSet.getColumnIndexForName("age")));
    HiLog.info(hiloglabel,log_format,"=======+resSalary: "+resultSet2.getDouble(resultSet.getColumnIndexForName("salary")));
    }
  4. 删除数据
    注意:在数据库中,rowid,行ID,从1开始,查询之后的数据集,从0开始,叫索引。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 删除数据
    RdbPredicates del_rdbPredicates = new RdbPredicates("test").equalTo("id",3);
    rdbStore.delete(del_rdbPredicates);
    // 原生SQL
    // rdbStore.executeSql("delete from test where id=?",new Object[]{3});
    // 查看删除后结果
    RdbPredicates predicates3 = new RdbPredicates("test").equalTo("age","19");
    ResultSet resultSet3 = rdbStore.query(predicates3, columns);
    for (int i = 0; i < resultSet3.getRowCount(); i++) {
    resultSet3.goToRow(i);
    HiLog.info(hiloglabel,log_format,".......+resId: "+resultSet3.getInt(resultSet.getColumnIndexForName("id")));
    HiLog.info(hiloglabel,log_format,".......+resName: "+resultSet3.getString(resultSet.getColumnIndexForName("name")));
    HiLog.info(hiloglabel,log_format,".......+resAge: "+resultSet3.getInt(resultSet.getColumnIndexForName("age")));
    HiLog.info(hiloglabel,log_format,".......+resSalary: "+resultSet3.getDouble(resultSet.getColumnIndexForName("salary")));
    }
  5. 注册结果集观察者。

    1. 注册观察者,类型为DataObserverAsyncWrapper。
    2. 设置受影响的URI

    示例代码如下:

    1
    2
    3
    4
    resultSet.registerObserver(dataObserverAsyncWrapper);
    List<Uri> uris = new ArrayList<>();
    uris.add(Uri.parse(String.format((Const.BASE_URI + Const.DATA_PATH), "")));
    resultSet.setAffectedByUris(this, uris);

对象关系映射数据库-ORM

HarmonyOS对象关系映射(Object Relational Mapping,ORM)数据库是一款基于SQLite的数据库框架,屏蔽了底层SQLite数据库的SQL操作,针对实体和关系提供了增删改查等一系列的面向对象接口。应用开发者不必再去编写复杂的SQL语句, 以操作对象的形式来操作数据库,提升效率的同时也能聚焦于业务开发。

基本概念

  • 对象关系映射数据库的三个主要组件:

    • 数据库:被开发者用@Database注解,且继承了OrmDatabase的类,对应关系型数据库。
    • 实体对象:被开发者用@Entity注解,且继承了OrmObject的类,对应关系型数据库中的表。
    • 对象数据操作接口:包括数据库操作的入口OrmContext类和谓词接口(OrmPredicate)等。
  • 谓词

    数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。对象关系映射数据库将SQLite数据库中的谓词封装成了接口方法供开发者调用。开发者通过对象数据操作接口,可以访问到应用持久化的关系型数据。

  • 对象关系映射数据库

    通过将实例对象映射到关系上,实现操作实例对象的语法,来操作关系型数据库。它是在SQLite数据库的基础上提供的一个抽象层。

  • SQLite数据库

    一款轻型的数据库,是遵守ACID的关系型数据库管理系统。

开发步骤

  1. 配置“build.gradle”文件。

    • 如果使用注解处理器的模块为“com.huawei.ohos.hap”模块,则需要在模块的“build.gradle”文件的ohos节点中添加以下配置:

      1
      2
      3
      compileOptions{            
      annotationEnabled true
      }
    • 如果使用注解处理器的模块为“com.huawei.ohos.library”模块,则需要在模块的“build.gradle”文件的“dependencies”节点中配置注解处理器。

      查看“orm_annotations_java.jar”、“orm_annotations_processor_java.jar” 、“javapoet_java.jar”这3个jar包在HUAWEI SDK中的Sdk/java/x.x.x.xx/build-tools/lib/目录,并将目录的这三个jar包导进来。

      1
      2
      3
      4
      5
      6
      dependencies {    
      compile files("orm_annotations_java.jar的路径",
      "orm_annotations_processor_java.jar的路径", "javapoet_java.jar的路径")
      annotationProcessor files("orm_annotations_java.jar的路径",
      "orm_annotations_processor_java.jar的路径", "javapoet_java.jar的路径")
      }
    • 如果使用注解处理器的模块为“java-library”模块,则需要在模块的“build.gradle”文件的dependencies节点中配置注解处理器,并导入“ohos.jar”。

      1
      2
      3
      4
      5
      6
      dependencies {
      compile files("ohos.jar的路径","orm_annotations_java.jar的路径",
      "orm_annotations_processor_java.jar的路径","javapoet_java.jar的路径")
      annotationProcessor files("orm_annotations_java.jar的路径",
      "orm_annotations_processor_java.jar的路径","javapoet_java.jar的路径")
      }
  2. 构造数据库,即创建数据库类并配置对应的属性。

    例如,定义了一个数据库类BookStore.java,数据库包含了“User”,”Book”,”AllDataType”三个表,版本号为“1”。数据库类的getVersion方法和getHelper方法不需要实现,直接将数据库类设为虚类即可。

    1
    2
    @Database(entities = {User.class, Book.class, AllDataType.class}, version = 1) 
    public abstract class BookStore extends OrmDatabase { }
  3. 构造数据表,即创建数据库实体类并配置对应的属性(如对应表的主键,外键等)。数据表必须与其所在的数据库在同一个模块中。

    例如,定义了一个实体类User.java,对应数据库内的表名为“user”;indices 为“firstName”和“lastName”两个字段建立了复合索引“name_index”,并且索引值是唯一的;“ignoredColumns”表示该字段不需要添加到“user”表的属性中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Entity(tableName = "user", ignoredColumns = {"ignoredColumn1", "ignoredColumn2"},
    indices = {@Index(value = {"firstName", "lastName"}, name = "name_index", unique = true)})
    public class User extends OrmObject {
    // 此处将userId设为了自增的主键。注意只有在数据类型为包装类型时,自增主键才能生效。
    @PrimaryKey(autoGenerate = true)
    private Integer userId;
    private String firstName;
    private String lastName;
    private int age;
    private double balance;
    private int ignoredColumn1;
    private int ignoredColumn2;

    // 需添加各字段的getter和setter方法。
    }

    说明

    示例中的getter & setter 的方法名为小驼峰格式,除了手写方法,IDE中包含自动生成getter和setter方法的Generate插件。

    • 当变量名的格式类似“firstName”时,getter和setter方法名应为“getFirstName”和“setFirstName”。
    • 当量名的格式类似“mAge”,即第一个字母小写,第二个字母大写的格式时,getter和setter方法名应为“getmAge”和“setmAge”。
    • 当变量名格式类似“x”,即只有一个字母时,getter和setter方法名应为“getX”和“setX”。

    变量为boolean类型时,上述规则仍然成立,即“isFirstName”,“ismAge”,“isX”。

  4. 使用对象数据操作接口OrmContext创建数据库。

    例如,通过对象数据操作接口OrmContext,创建一个别名为“BookStore”,数据库文件名为“BookStore.db”的数据库。如果数据库已经存在,执行以下代码不会重复创建。通过context.getDatabaseDir()可以获取创建的数据库文件所在的目录。

    1
    2
    3
    4
    // context入参类型为ohos.app.Context,注意不要使用slice.getContext()来获取context,请直接传入slice,否则会出现找不到类的报错。
    DatabaseHelper helper = new DatabaseHelper(this);

    OrmContext context = helper.getOrmContext("BookStore", "BookStore.db", BookStore.class);
  5. (可选)数据库升降级。如果开发者有多个版本的数据库,通过设置数据库版本迁移类可以实现数据库版本升降级。

    数据库版本升降级的调用示例如下。其中BookStoreUpgrade类也是一个继承了OrmDatabase的数据库类,与BookStore类的区别在于配置的版本号不同。

    1
    OrmContext context = helper.getOrmContext("BookStore","BookStore.db",BookStoreUpgrade.class,new TestOrmMigration32(),new TestOrmMigration23(),new TestOrmMigration12(),new TestOrmMigration21());

    TestOrmMigration12的实现示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private static class TestOrmMigration12 extends OrmMigration {    
    // 此处用于配置数据库版本迁移的开始版本和结束版本,super(startVersion, endVersion)即数据库版本号从1升到2。
    public TestOrmMigration12() {
    super(1, 2);
    }

    @Override
    public void onMigrate(RdbStore store) {
    store.executeSql("ALTER TABLE `Book` ADD COLUMN `addColumn12` INTEGER");
    }
    }

    说明

    数据库版本迁移类的开始版本和结束版本必须是连续的。

    • 如果BookStore.db的版本号为“1”,BookStoreUpgrade.class的版本号为“2”时,TestOrmMigration12类的onMigrate方法会被自动回调。
    • 如果BookStore.db的版本号为“1”,BookStoreUpgrade.class版本号为“3”时,TestOrmMigration12类和TestOrmMigration23类的onMigrate方法会依次被回调。
  6. 使用对象数据操作接口OrmContext对数据库进行增删改查、注册观察者、备份数据库等。

    • 更新或删除数据,分为两种情况:

      • 通过直接传入OrmObject对象的接口来更新数据,需要先从表中查到需要更新的User对象列表,然后修改对象的值,再调用更新接口持久化到数据库中。删除数据与更新数据的方法类似,只是不需要更新对象的值。

        例如,更新“user”表中age为“29”的行,需要先查找“user”表中对应数据,得到一个User的列表。然后选择列表中需要更新的User对象(如第0个对象),设置需要更新的值,并调用update接口传入被更新的User对象。最后调用flush接口持久化到数据库中。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        // 更新数据
        OrmPredicates predicates = context.where(User.class);
        predicates.equalTo("age", 29);
        List<User> users = context.query(predicates);
        User user = users.get(0);
        user.setFirstName("Li");
        context.update(user);
        context.flush();
        // 删除数据
        OrmPredicates predicates = context.where(User.class);
        predicates.equalTo("age", 29);
        List<User> users = context.query(predicates);
        User user = users.get(0);
        context.delete(user);
        context.flush();
      • 通过传入谓词的接口来更新和删除数据,方法与OrmObject对象的接口类似,只是无需flush就可以持久化到数据库中。

        1
        2
        3
        4
        5
        6
        7
        ValuesBucket valuesBucket = new ValuesBucket();
        valuesBucket.putInteger("age", 31);
        valuesBucket.putString("firstName", "ZhangU");
        valuesBucket.putString("lastName", "SanU");
        valuesBucket.putDouble("balance", 300.51);
        OrmPredicates update = context.where(User.class).equalTo("userId", 1);
        context.update(update, valuesBucket);
    • 查询数据。在数据库的“user”表中查询lastName为“San”的User对象列表,示例如下

      1
      2
      OrmPredicates query = context.where(User.class).equalTo("lastName", "San");
      List<User> users = context.query(query);
    • 注册观察者。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 定义一个观察者类。
      private class CustomedOrmObjectObserver implements OrmObjectObserver {
      @Override
      public void onChange(OrmContext changeContext, AllChangeToTarget subAllChange) {
      // 用户可以在此处定义观察者行为
      }}
      // 调用registerEntityObserver方法注册一个观察者observer。
      CustomedOrmObjectObserver observer = new CustomedOrmObjectObserver();
      context.registerEntityObserver("user", observer);
      // 当以下方法被调用,并flush成功时,观察者observer的onChange方法会被触发。其中,方法的入参必须为User类的对象。
      public <T extends OrmObject> boolean insert(T object)public <T extends OrmObject> boolean update(T object)
      public <T extends OrmObject> boolean delete(T object)
    • 备份数据库。其中原数据库名为

      “OrmTest.db”

      ,备份数据库名为

      “OrmBackup.db”

      1
      OrmContext context = helper.getObjectContext("OrmTest", "OrmTest.db", BookStore.class);context.backup("OrmBackup.db");context.close();
  7. 删除数据库,例如删除OrmTest.db。

    1
    helper.deleteRdbStore("OrmTest.db");

Codelabs

Page内和Page间导航的实现(Java)

Page内可以使用present()或presentForResult()实现导航,使用presentForResult()时可以获得从导航目标AbilitySlice返回时的返回结果。

  • 返回结果由导航目标AbilitySlice在其 生命周期内 通过setResult()进行设置。
  • 当用户从导航目标AbilitySlice返回时,系统将回调onResult()来接收和处理返回结果,您需要重写该方法。
1
2
3
//start entering the NewAbilitySlice 
Component enterNewAbilitySliceButton = findComponentById(ResourceTable.Id_enter_newAbilitySlice);
enterNewAbilitySliceButton.setClickedListener(listener -> presentForResult(new NewAbilitySlice() , new Intent(), 0));

新建的”NewAbilitySlice.Java”文件,通过setResult()完成返回结果的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.huawei.abilityintent.slice; 

import com.huawei.abilityintent.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Component;

public class NewAbilitySlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main_new);

Component newToMainButton = findComponentById(ResourceTable.Id_new_to_main);
// terminate() 指的是关掉当前的 AbilitySlice terminateAbility() 关掉整个 Ability。
newToMainButton.setClickedListener(component -> terminate());
}

@Override
public void onActive() {
super.onActive();

Intent intent = new Intent();
intent.setParam("key", "我从NewAbilitySlice跳回来咯");
setResult(intent);
}

@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}

通过onResult完成返回结果的接收

1
2
3
4
5
6
7
8
9
10
11
/** 
* Result of presentForResult()
*/
@Override
protected void onResult(int requestCode, Intent resultIntent) {
if (requestCode != 0 || resultIntent == null) {
return;
}
String result = resultIntent.getStringParam("key");
backValueText.setText(result);
}

Page间的导航可以使用startAbility()或startAbilityForResult()方法,使用startAbilityForResult()方法时可以获得导航目标Ability返回时的返回结果。

  • 在导航目标Ability中调用setResult()来设置返回结果。
  • 获得返回结果的回调为onAbilityResult()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//start entering the SecondAbility page 
Component enterSecondAbilityButton = findComponentById(ResourceTable.Id_enter_second);
enterSecondAbilityButton.setClickedListener(component -> startEnterSecondAbility());
/**
* Explicit Startup
*/
private void startEnterSecondAbility() {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder().withDeviceId("")
.withBundleName(getBundleName())
.withAbilityName("com.huawei.abilityintent.SecondAbility")
.build();
intent.setOperation(operation);
intent.setParam("key", "我从MainAbility进到了SecondAbility");
startAbilityForResult(intent, 1);
}

在新页面中

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_second);

Component secondBackFirstButton = findComponentById(ResourceTable.Id_second_back_first);
secondBackFirstButton.setClickedListener(component -> terminate());

Text showParametersText = (Text) findComponentById(ResourceTable.Id_second_text);
showParametersText.setText(intent.getStringParam("key"));
}

在SecondAbility页面中设置返回MainAbility需要的数据

1
2
3
4
5
6
7
 @Override 
protected void onActive() {
super.onActive();
Intent intent = new Intent();
intent.setParam("key", "我从SecondAbility跳回来啦");
setResult(0, intent);
}

如果是在AbilitySlice中,我们也可以通过下面方式完成setResult()设置返回结果哟~
getAbility().setResult(0, intent);

完成返回结果的回调函数onAbilityResult(),将返回结果显示在页面上

1
2
3
4
5
6
7
8
@Override 
protected void onAbilityResult(int requestCode, int resultCode, Intent resultData) {
if (resultCode != 0 || resultData == null) {
return;
}
String result = resultData.getStringParam("key");
backValueText.setText(result);
}

此外,页面跳转可以加入自定义路由。

在第一个Ability的AbilitySlice跳转第二个Ability的非MainAbiltySlice

第二个Ability的非MainAbiltySlice,首先要注册好路由。然后在第一个AbilitySlice中使用 intent 的 setActive 方法即可。

1
2
3
4
//...
Intent intent = new Intent();
intent.setActive("com.felix.action.secoendPage");
startAbility(intent);

专题

* 流转

基础问题

Java8 新特性 Lambda 表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

格式:

1
2
3
(parameters) -> expression

(parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

简单例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 不需要参数,返回值为 5  
() -> 5

// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x

// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y

// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y

// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

使用 Lambda 表达式需要注意以下两点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

其他

快捷键 / 技巧

快速生成方法返回的接收对象。例子如下。

  • CTRL + ALT + V
  • 在代码末尾 ALT + ENTER,再按下 ENTER
  • 在代码末尾写 .var,再回车。
1
2
3
4
5
6
// 前
findComponentById(ResourceTable.Id_text);
// 后
Component component = findComponentById(ResourceTable.Id_text);
// 修改
Text text = (Text) findComponentById(ResourceTable.Id_text);

函数方法提示

  • CTRL + P

报错提示

  • ALT + 回车

导入继承的父类方法

  • CTRL + O

图片读取-PixelMap

一般来说,将通过图片读取工具函数,将图片转为PixelMap,是华为设备中常用的图片格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Image image = (Image) findComponentById(RecoueceTable.Id_Image);
image.setPixelMap(list.get(i).getImageUrl,slice);


// 所有的资源都是和当前页面所定位的。
PixelMap getPixeMapFromPath(String imageUrl, AbilitySlice slice){
try {
// 从路径中读取出了图片文件的数据
Resource resource = slice.getResourceManager().getRawFileEntry(imageUrl).openRawFile();
// 对图片文件进行说明,我们采用的是标准文件,无需特殊说明
ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
// 创建图片资源对象
ImageSource imageSource = ImageSouce.create(resource,sorceOptions);
// 需要对输出的图片进行处理,输出的为标准格式,无需特殊处理。
ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
// 输出一张图片
return imageSource.createPixelmap(decodingOptions);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

数据格式-JSON

数据都是以JSON的格式存放在系统中,便于交互和扩展。最终还是要转换为集合。

1
2
List<NewsType> newstype = 
ZSONArray.stringToClassList(Commonutils.getStringFromPath(...))
1
2
3
4
[
{"1":"ha"},
{"2":"hei"}
]

读取json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String getJsonFromPath(String path,AbilitySlice slice){
// 利用资源对象获取文件数据
try{
Resource resource = slice.getResourceManager().getRawFileEntry(path).openRawFile();
// 获取资源大小
int size = resource.available();
// 内存声明一块区域为大小为size
byte[] buffer = new byte[size];
// 若获取到了数据
if(resource.read(buffer) != -1){
retring new String(buffer, StandardCharsets.UTF_8);
}
} catch (IOException e){
e.printStackTrace();
}
}

读取之后转成集合

1
2
3
4
5
// 从路径中获取字符串
String json = getJsonFromPath("entry/resources/rawfile/NewType_datas.json",
MainAbilitySlice.class);
// 将字符串转为集合
list = ZSONArray.stringToClassList(json,NewsType.class);

HiLog日志系统

官网介绍:

HiLog-Class-ohos.hiviewdfx-Java API参考-HarmonyOS应用开发

HiLogLabel-Class-ohos.hiviewdfx-Java API参考-HarmonyOS应用开发

定义一个HiLogLable.

1
static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, MY_MODULE, "MY_TAG"); //MY_MODULE=0x00201

HiLog日志系统,让应用可以按照指定类型、指定级别、指定格式字符串输出日志内容,帮助开发者了解应用的运行状态,更好地调整运行状态,更好的调整程序。输入日志的接口由HiLog类提供。在输出日志前,需要先调用HiLog的辅助类HiLogLable定义日志标签。

1
2
3
4
5
public void onStart(Intent intent) {
HiLogLabel hiloglabel = new HiLogLabel(HiLog.LOG_APP,0,"MyLogTest");
String log_format = "%{public}s";
HiLog.info(hiloglabel,log_format,"onstar");
}

Label_Log(HiLogLabel)

  • 参数 type:指定输出日志类型。LOG_APP
  • 参数 domain:指定业务领域,取值为 0x0~0xFFFFF,开发者自定义
  • 参数 tag:用于指定日志标识,可以为任意字符串,建议标识类或者字符串。

Label_format(log_format)

  • 格式字符串,用于日志格式化输出。格式字符串中可以设置多个参数。例如:Failed to visit %s ,“%s”为参数类型为String的变参标识,可以在 args 中定义。
  • 每个参数需要添加隐私标识,分为 {public}{private} ,默认是 {private}{public} 表示日志打印结果可见,{private} 表示打印结果不可见,输出结果为 <private>

HiLog.info()

HiLog.info(HiLogLabel hiloglabel,String format,Object... args),参数解释如上。