在知道用法之前,首先你是不是得知道这个Ability怎么读?对了,Ability (音译 :阿B了D),中文意思就是能力,不要给我扯什么音标啥的,不好使,你仔细想一下,你是因为英语学得好才来当程序员的吗?To young to simple!
Ability 是应用所具备能力的抽象,也是应用程序的重要组成部分。一个应用可以具备多种能力(即可以包含多个 Ability),HarmonyOS 支持应用以 Ability 为单位进行部署。Ability 可以分为 FA(Feature Ability)和 PA(Particle Ability)两种类型,每种类型为开发者提供了不同的模板,以便实现不同的业务功能。
从上面一段文字,去其糟粕,取其精华之后就是两点。FA(Feature Ability)和PA(Particle Ability)
FA(Feature Ability) (音译:非ture 阿B了D),中文意思是功能能力,它支持Page Ability 页面能力用于提供与用户交互的能力。一个Page 可以由一个或多个 AbilitySlice 构成,AbilitySlice 是指应用的单个页面及其控制逻辑的总和。
一个 Page 可以包含多个 AbilitySlice,但是 Page 进入前台时界面默认只展示一个AbilitySlice。默认展示的 AbilitySlice 是通过 setMainRoute() 方法来指定的。如果需要更改默认展示的 AbilitySlice,可以通过 addActionRoute() 方法为此 AbilitySlice 配置一条路由规则。此时,当其他 Page 实例期望导航到此 AbilitySlice 时,可以在 Intent 中指定 Action。addActionRoute() 方法中使用的动作命名,需要在应用配置文件(config.json)中注册:
比如
PA(Particle Ability) (音译:趴踢扣 阿B了D),这个里面也是支持两个能力, Service Ability 和 Data Ability 我相信你知道它们的意思,就是服务能力和数据能力。Service用于提供后台运行任务的能力。Data 用于对外部提供统一的数据访问抽象。在配置文件(config.json)中注册 Ability 时,可以通过配置 Ability 元素中的“type”属性来指定 Ability 模板类型,示例如下。其中,“type”的取值可以为“page”、“service”或“data”,分别代表 Page 模板、Service 模板、Data 模板。结合下面这个图来看知道是怎么回事了,type的属性值取决于你创建Ability是选择的类型,当然你也可以后面再改。
现在我们知道这个Page Ability是主要负责页面交互的,那么就可以理解为Android 的Activity。那么都知道Activity有生命周期,同样的Page Ability也是的。下面来看看它的生命周期。
首先来看官方的一张图
重点看蓝色方框的。粉红色的是当前应用的状态。
声明周期分别是onStart()、onActive()、onInactive()、onBackground()、onForeground()、onStop()
下面来看看详细的解释
onStart() 当系统首次创建 Page Ability实例时,触发该回调。对于一个 Page Ability实例,该回调在其生命周期过程中仅触发一次,Page Ability在该逻辑后将进入 INACTIVE 状态。开发者必须重写该方法,并在此配置默认展示的 AbilitySlice。如下图所示
和onCreate有点像。
onActive() Page Ability会在进入 INACTIVE 状态后来到前台,然后系统调用此回调。Page Ability 在此之后进入ACTIVE 状态,该状态是应用与用户交互的状态。Page Ability将保持在此状态,除非某类事件发生导致 Page Ability失去焦点,比如用户点击返回键或导航到其他 Page Ability。当此类事件发生时,会触发Page Ability回到 INACTIVE 状态,系统将调用 onInactive() 回调。此后,Page Ability可能重新回到ACTIVE 状态,系统将再次调用 onActive() 回调。因此,开发者通常需要成对实现 onActive() 和 onInactive(),并在 onActive() 中获取在 onInactive() 中被释放的资源。类似于Android的onResume。
onInactive() 当 Page Ability失去焦点时,系统将调用此回调,此后 Page 进入 INACTIVE 状态。开发者可以在此回调中实现 Page 失去焦点时应表现的恰当行为。类似于Android的onPause和onStop的集合体。
onBackground() 如果 Page Ability不再对用户可见,系统将调用此回调通知开发者用户进行相应的资源释放,此后Page Ability进入 BACKGROUND 状态。开发者应该在此回调中释放 Page Ability 不可见时无用的资源,或在此回调中执行较为耗时的状态保存操作。
onForeground() 处于 BACKGROUND 状态的 Page Ability仍然驻留在内存中,当重新回到前台时(比如用户重新导航到此 Page Ability),系统将先调用 onForeground()回调通知开发者,而后 Page 的生命周期状态回到 INACTIVE 状态。开发者应当在此回调中重新申请在 onBackground()中释放的资源,最后 Page 的生命周期状态进一步回到 ACTIVE 状态,系统将通过 onActive()回调通知开发者用户。
onStop() 系统将要销毁 Page Ability时,将会触发此回调函数,通知用户进行系统资源的释放。销毁 Page 的可能原因包括以下几个方面:
▪ 用户通过系统管理能力关闭指定 Page Ability,例如使用任务管理器关闭 Page Ability。
▪ 用户行为触发 Page Ability的 terminateAbility()方法调用,例如使用应用的退出功能。
▪ 配置变更导致系统暂时销毁 Page Ability并重建。
▪ 系统出于资源管理目的,自动触发对处于 BACKGROUND 状态 Page Ability的销毁。
OK,Page Ability 的生命周期就讲完了,具体要熟悉的话还是从实际开发中获取才行。
先来看下面这张图
说实话一开始创建项目的时候就只有这个MainAbility和HelloWorld以及slice包下的MainAbilitySlice,后来新建了一个SecondAbility,而SecondAbilitySlice是自动生成的,这说明一个问题,它们之间有不可告人的秘密。那么下面就戳穿这个秘密,摊牌了,它们是一对好基友。
解释:AbilitySlice 作为 Page Ability的组成单元,其生命周期是依托于其所属 Page Ability生命周期的。AbilitySlice 和 Page Ability具有相同的生命周期状态和同名的回调,当 Page Ability生命周期发生变化时,它的 AbilitySlice 也会发生相同的生命周期变化。此外,AbilitySlice 还具有独立于 Page Ability的生命周期变化,这发生在同一 Page Ability中的 AbilitySlice 之间导航时,此时 Page Ability的生命周期状态不会改变。AbilitySlice 生命周期回调与 Page Ability的相应回调类似,因此不再赘述。由于 AbilitySlice 承载具体的页面,开发者必须重写 AbilitySlice 的 **onStart()**回调,并在此方法中通过 **setUIContent()**方法设置页面,如下所示:
Page 与 AbilitySlice 生命周期关联
当 AbilitySlice 处于前台且具有焦点时,其生命周期状态随着所属 Page Ability的生命周期状态的变化而变化。当一个 Page Ability拥
有多个 AbilitySlice 时,例如:MyAbility 下有 FooAbilitySlice 和 BarAbilitySlice,当前 FooAbilitySlice 处于前台并获得焦点,并即将导航到 BarAbilitySlice,在此期间的生命周期状态变化顺序为:
先来看一下Service Ability的官方解释基于 Service 模板的 Ability(以下简称“Service”)主要用于后台运行任务(如执行音乐播放、文件下载等),但不提供用户交互界面。Service 可由其他应用或 Ability 启动,即使用户切换到其他应用,Service 仍将在后台继续运行。
Service 是单实例的。在一个设备上,相同的 Service 只会存在一个实例。如果多个 Ability 共用这个实例,只有当与 Service 绑定的所有 Ability 都退出后,Service 才能够退出。由于Service 是在主线程里执行的,因此,如果在 Service 里面的操作时间过长,开发者必须在Service 里创建新的线程来处理,防止造成主线程阻塞,应用程序无响应。其实和Android的Service有点像。
下面创建一个Service,右键你的包名 → New → Ability → Empty Service Ability。如下图所示
然后
上面的这个Visible你如果勾选上就是你的这个Service对其他应用程序可见,而Enable background mode表示后台模式,如果你打开这个开关,就表示你的Service要在后台运行,还可以自己选择你要在后台干嘛。
这个我翻译一下
这里你就必须要选一个,不选就不能创建这个Service Ability。下面我们就直接创建,不勾选,不打开。创建Service Ability不会生成AbilitySlice。
这个时候在config.json文件中会自动生成相关的属性。
可以看到相比于Page ,Service的属性要少一些,而且type的属性值是“service”。
下面看一下ServiceAbility的代码:
package com.llw.helloworld;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.rpc.IRemoteObject;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class ServiceAbility extends Ability {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
@Override
public void onStart(Intent intent) {
HiLog.error(LABEL_LOG, "ServiceAbility::onStart");
super.onStart(intent);
}
@Override
public void onBackground() {
super.onBackground();
HiLog.info(LABEL_LOG, "ServiceAbility::onBackground");
}
@Override
public void onStop() {
super.onStop();
HiLog.info(LABEL_LOG, "ServiceAbility::onStop");
}
@Override
public void onCommand(Intent intent, boolean restart, int startId) {
}
@Override
public IRemoteObject onConnect(Intent intent) {
return null;
}
@Override
public void onDisconnect(Intent intent) {
}
}
生命周期:onStart()、onCommand()、onConnect()、onDisconnect()、onStop()。
单个讲解
Ability 为开发者提供了startAbility() 方法来启动另外一个 Ability。因为 Service 也是 Ability的一种,开发者同样可以通过将 Intent 传递给该方法来启动 Service。不仅支持启动本地Service,还支持启动远程 Service。
开发者可以通过构造包含 DeviceId、BundleName 与 AbilityName 的 Operation 对象来设置目标 Service 信息。这三个参数的含义如下:
下面用代码来实践一下,比如我现在要在MainAbilitySlice的onStart方法中启动ServiceAbility。就可以这么写
/**
* 启动本地服务
*/
private void startupLocalService() {
Intent intent = new Intent();
//构建操作方式
Operation operation = new Intent.OperationBuilder()
// 设备id
.withDeviceId("")
// 应用的包名
.withBundleName("com.llw.helloworld")
// 跳转目标的路径名 通常是包名+类名
.withAbilityName("com.llw.helloworld.ServiceAbility")
.build();
//设置操作
intent.setOperation(operation);
startAbility(intent);
}
然后在onStart中调用即可。
那么怎么证明ServiceAbility是启动了呢?很简单,我们只要在ServiceAbility的onStart方法中打印一个日志就可以了。进入到ServiceAbility,你会发现创建的时候就给你写好了日志。
那么现在启动远程模拟器,然后运行HelloWorld。进入到主页
那么这个时候Service已经启动了,通过日志来看看,点击编译器下面的HiLog栏目,然后输入Demo,就能找到这个日志了。
那么现在我们就启动了这个本地的Service,那么如何启动远程的Service呢?
private void startupRemotelyService() {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId("deviceId")
.withBundleName("com.huawei.hiworld.himusic")
.withAbilityName("com.huawei.hiworld.himusic.entry.ServiceAbility")
// 设置支持分布式调度系统多设备启动的标识
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
intent.setOperation(operation);
startAbility(intent);
}
远程启动Service可以这么写,但是有一点你要确认,那就是你启动的这个服务是否允许其他应用程序发现?否则你就算知道这个服务的包名和类名也是白搭。还记得刚才在创建Service Ability的时候的Visible吗?勾选就是允许,默认是没有勾选的。那么我又想去勾选了咋办?难道我现在重新创建一个再勾选上?感觉这样是可以的,但是太蠢了。不够优雅。既然你也不知道怎么搞,我也不知道怎么搞,那么就实验一下,比如我再创建一个ServiceAbility。这里设置名为ServiceAbility2,然后勾选一下Visible,然后我们到config.json配置文件中去看之前的没有勾选的Service有啥不同。
现在你是不是就有种恍然大明白的感觉了。只要通过加一个visible的属性,设置为true,就可以了,如果没有这个属性,就是默认为false。OK,那么这就解决了这个启动Service的问题。
通过 startAbility() 方法来启动 Service。
@Override
public void onCommand(Intent intent, boolean restart, int startId) {
HiLog.error(LABEL_LOG, "ServiceAbility::onCommand");
}
有两种停止Service的方法,在Page Ability中停止,和在本Service中停止,先试一下第一种。
下面我们在MainAbilitySlice中增加一个停止服务的方法。
/**
* 停止本地服务 在Page Ability中停止Service
*/
private void stopLocalService() {
Intent intent = new Intent();
//构建操作方式
Operation operation = new Intent.OperationBuilder()
// 设备id
.withDeviceId("")
// 应用的包名
.withBundleName("com.llw.helloworld")
// 跳转目标的路径名 通常是包名+类名
.withAbilityName("com.llw.helloworld.ServiceAbility")
.build();
//设置操作
intent.setOperation(operation);
//停止服务
stopAbility(intent);
}
然后再点击按钮的时候调用。
然后先运行一下进入到主页面,然后点击Next按钮,看下面的日志。
可以看到当我们从其他的Page Ability中停止Service时,会先回调onBackground。因为这个时候服务是在前台运行的,系统会把服务放到后台,然后再通过stop来停止这个服务。
下面再看看在本Service中停止这个服务。可以通过一个延时服务来操作,下面来看看代码怎么写的。
/**
* 创建一个线程池
*/
final static ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
private void stopService() {
// 延时任务
service.schedule(threadFactory.newThread(new Runnable() {
@Override
public void run() {
//停止服务当前服务
terminateAbility();
}
//延时三秒执行
}), 3, TimeUnit.SECONDS);
}
/**
* 线程工厂
*/
private ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(final Runnable r) {
return new Thread() {
@Override
public void run() {
r.run();
}
};
}
};
为什么要这么写呢?因为DS里面推荐使用ScheduledExecutorService ,不然我就直接用Timer或者Thread就可以了。创建了一个线程池,然后创建一个线程工厂,在进行延时操作的时候,传入了三个参数,一个是线程工厂,里面有一个Runnable(),第二个参数代表数量,第三个参数是单位,上面的代码就是3秒。
下面直接运行到模拟器,然后等待三秒就会自动调用terminateAbility();停止Service。你会发现和通过其他的Page Ability停止服务的执行流程是一样的。
如果 Service 需要与 Page Ability 或其他应用的 Service Ability 进行交互,则应创建用于连接的 Connection。Service 支持其他 Ability 通过 connectAbility()方法与其进行连接。
在使用 connectAbility()处理回调时,需要传入目标 Service 的 Intent 与 IAbilityConnection的实例。IAbilityConnection 提供了两个方法供开发者实现:onAbilityConnectDone() 用来处理连接的回调,onAbilityDisconnectDone() 用来处理断开连接的回调。
在MainAbilitySlice中添加如下代码:
/**
* 连接服务
*/
private void connectService(){
// 连接 Service
Intent intent = new Intent();
//构建操作方式
Operation operation = new Intent.OperationBuilder()
// 设备id
.withDeviceId("")
// 应用的包名
.withBundleName("com.llw.helloworld")
// 跳转目标的路径名 通常是包名+类名
.withAbilityName("com.llw.helloworld.ServiceAbility")
.build();
//设置操作
intent.setOperation(operation);
//连接到服务
connectAbility(intent,connection);
}
// 创建连接回调实例
private IAbilityConnection connection = new IAbilityConnection() {
// 连接到 Service 的回调
@Override
public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int i) {
// 在这里开发者可以拿到服务端传过来 IRemoteObject 对象,从中解析出服务端传过来的信息
}
// 断开与连接的回调
@Override
public void onAbilityDisconnectDone(ElementName elementName, int i) {
}
};
然后在点击的时候调用
别Service的onConnect方法中加入日志打印
下面运行一下:
连接成功。
断开服务其实就比较的简单了,调用**disconnectAbility()**方法即可,而且不用传intent,但是要传IAbilityConnection进入,所以可以可以这样来测试,在连接到Service之后马上断开连接。
//断开服务
disconnectAbility(connection);
然后运行起来,进入应用页面,然后点击Next。
OK,到这一步,相信你已经会基本操作了。而Service的生命周期根据调用方法的不同,其生命周期有以下两种路径:
刚才我们说的都是后台的Service,那么怎么到前台来呢?最通用的前台服务就是音乐播放了,用手机的时候它会在通知栏创建,然后播放音乐,那么在鸿蒙中需要怎么使用前台服务呢?使用前台 Service 并不复杂,开发者只需在 Service 创建的方法里,调用keepBackgroundRunning()将 Service 与通知绑定。调用 keepBackgroundRunning()方法前需要在配置文件中声明 ohos.permission.KEEP_BACKGROUND_RUNNING 权限,该权限是 normal 级别,同时还需要在配置文件中添加对应的 backgroundModes 参数。在onStop()方法中调用 cancelBackgroundRunning()方法可停止前台 Service。
说这么多没啥用,下面来实际操作一下:
在connectService方法中注释断开服务
然后进入到ServiceAbility中,新一个启动前台服务的方法。
/**
* 启动前台服务
*/
private void startupForegroundService(){
//创建通知请求 设置通知id为9527
NotificationRequest request = new NotificationRequest(1005);
//创建普通通知
NotificationRequest.NotificationNormalContent content =
new NotificationRequest.NotificationNormalContent();
//设置通知的标题和内容
content.setTitle("Title").setText("Text");
//创建通知内容
NotificationRequest.NotificationContent notificationContent = new
NotificationRequest.NotificationContent(content);
//设置通知
request.setContent(notificationContent);
keepBackgroundRunning(1005,request);
HiLog.error(LABEL_LOG, "ServiceAbility::startupForegroundService");
}
然后在onStart中调用。
别忘了在config.json中给相关的代码配置:
然后直接运行到主页面,之后会先启动Service,然后将Service变成前台服务。运行之后如下:
说实话目前也就只是日志打印出来了,但是我也不知道当前这个服务是不是在前台。
然后在onCommand中取消前台服务:
@Override
public void onCommand(Intent intent, boolean restart, int startId) {
HiLog.error(LABEL_LOG, "ServiceAbility::onCommand");
cancelBackgroundRunning();
HiLog.error(LABEL_LOG, "ServiceAbility::cancelBackgroundRunning");
}
使用 Data 模板的 Ability(以下简称“Data”)有助于应用管理其自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。Data 既可用于同设备不同应用的数据共享,也支持跨设备不同应用的数据共享。
数据的存放形式多样,可以是数据库,也可以是磁盘上的文件。Data 对外提供对数据的增、删、改、查,以及打开文件等接口,这些接口的具体实现由开发者提供。说起来和Android的ContentProvider有些像。
Data 的提供方和使用方都通过 URI(Uniform Resource Identifier)来标识一个具体的数据,例如数据库中的某个表或磁盘上的某个文件。HarmonyOS 的 URI 仍基于 URI 通用标准,格式如下:
开发者可以通过 DataAbilityHelper 类来访问当前应用或其他应用提供的共享数据。
DataAbilityHelper 作为客户端,与提供方的 Data 进行通信。Data 接收到请求后,执行相应的处理,并返回结果。DataAbilityHelper 提供了一系列与 Data Ability 对应的方法。
如果待访问的 Data 声明了访问需要权限,则访问此 Data 需要在配置文件中声明需要此权限。比如
reqPermissions 表示应用运行时向系统申请的权限。
说了这么多还是来创建一个Data Ability吧,鼠标右键包名 → New → Ability → Empty Data Ability
这个的Visible和Service的Visible是同样的意思,勾选上就是运行其他应用程序访问数据。
然后打开config.json,看创建DataAbility时,自动生成了那些代码。
可以看到type为“data”,另外还自带一个提供给外部数据的权限,已经访问这个DataAbility的uri。
然后看一下DataAbility的代码:
package com.llw.helloworld;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.data.resultset.ResultSet;
import ohos.data.rdb.ValuesBucket;
import ohos.data.dataability.DataAbilityPredicates;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.net.Uri;
import ohos.utils.PacMap;
import java.io.FileDescriptor;
public class DataAbility extends Ability {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "ProviderAbility onStart");
}
@Override
public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
return null;
}
@Override
public int insert(Uri uri, ValuesBucket value) {
HiLog.info(LABEL_LOG, "ProviderAbility insert");
return 999;
}
@Override
public int delete(Uri uri, DataAbilityPredicates predicates) {
return 0;
}
@Override
public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
return 0;
}
@Override
public FileDescriptor openFile(Uri uri, String mode) {
return null;
}
@Override
public String[] getFileTypes(Uri uri, String mimeTypeFilter) {
return new String[0];
}
@Override
public PacMap call(String method, String arg, PacMap extras) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
}
在创建的时候就生成了一些代码,基本的增删改查、打开文件、获取URI类型、获取文件类型、还有一个回调。再加上一个onStart方法,总共是9个,乍一看比较多。下面先来介绍 DataAbilityHelper 具体的使用步骤。
创建 DataAbilityHelper
DataAbilityHelper 为开发者提供了 creator()方法来创建 DataAbilityHelper 实例。该方法为静态方法,有多个重载。最常见的方法是通过传入一个 context 对象来创建DataAbilityHelper 对象。
DataAbilityHelper 为开发者提供了一系列的接口来访问不同类型的数据(文件、数据库等)。
// 读取文件描述符
try {
//通过文件描述符 读取指定uri的文件 ,“r”(读), “w”(写), “rw”(读写),“wt”(覆盖写),“wa”(追加写),“rwt”(覆盖写且可读)
FileDescriptor fileDescriptor = helper.openFile(Uri.parse("dataability://com.llw.helloworld.DataAbility"),"r");
//获取文件输入流
FileInputStream fileInputStream = new FileInputStream(fileDescriptor);
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
/**
* 查询
*/
private void queryData(DataAbilityHelper helper) {
//构建uri
Uri uri = Uri.parse("dataability://com.llw.helloworld.DataAbility");
//构建查询字段
String[] column = {
"age"};
// 构造查询条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
//查询用户id在1~10之间的数据
predicates.between("userId",1,10);
//进行查询
try {
//用一个结果集来接收查询返回的数据
ResultSet resultSet = helper.query(uri,column,predicates);
//从第一行开始
resultSet.goToFirstRow();
//处理每一行的数据
do {
// 在此处理 ResultSet 中的记录
HiLog.info(LABEL_LOG, resultSet.toString());
}while (resultSet.goToNextRow());
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
/**
* 插入 单条数据
*/
private void insertData(DataAbilityHelper helper) {
//构建uri
Uri uri = Uri.parse("dataability://com.llw.helloworld.DataAbility");
// 构造插入数据
ValuesBucket valuesBucket = new ValuesBucket();
valuesBucket.putString("name","KaCo");
valuesBucket.putInteger("age",24);
try {
helper.insert(uri,valuesBucket);
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
/**
* 插入 多条数据
* @param helper 数据帮助类
*/
private void batchInsertData(DataAbilityHelper helper) {
//构建uri
Uri uri = Uri.parse("dataability://com.llw.helloworld.DataAbility");
// 构造插入数据
ValuesBucket[] valuesBuckets = new ValuesBucket[3];
//构建第一条数据
valuesBuckets[0] = new ValuesBucket();
valuesBuckets[0].putString("name","Jim");
valuesBuckets[0].putInteger("age",18);
//构建第二条数据
valuesBuckets[1] = new ValuesBucket();
valuesBuckets[1].putString("name","Tom");
valuesBuckets[1].putInteger("age",20);
//构建第三条数据
valuesBuckets[2] = new ValuesBucket();
valuesBuckets[2].putString("name","Kerry");
valuesBuckets[2].putInteger("age",24);
try {
//批量插入数据
helper.batchInsert(uri,valuesBuckets);
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
/**
* 删除数据
* @param helper 数据帮助类
*/
private void deleteData(DataAbilityHelper helper) {
//构建uri
Uri uri = Uri.parse("dataability://com.llw.helloworld.DataAbility");
// 构造删除条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
//用户id在1~10的数据
predicates.between("userId",1,10);
try {
//删除
helper.delete(uri,predicates);
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
/**
* 更新数据
* @param helper 数据帮助类
*/
private void updateData(DataAbilityHelper helper) {
//构造uri
Uri uri = Uri.parse("dataability://com.llw.helloworld.DataAbility");
//构造更新数据
ValuesBucket valuesBucket = new ValuesBucket();
valuesBucket.putString("name","Aoe");
valuesBucket.putInteger("age",66);
//构造更新条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
//userId为2的用户
predicates.equalTo("userId",2);
try {
//更新数据
helper.update(uri,valuesBucket,predicates);
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
/**
* 批量操作数据
* @param helper 数据帮助类
*/
private void executeBatchData(DataAbilityHelper helper) {
//构造uri
Uri uri = Uri.parse("dataability://com.llw.helloworld.DataAbility");
//构造批量操作
//第一个
ValuesBucket valuesBucket1 = new ValuesBucket();
valuesBucket1.putString("name","Karen");
valuesBucket1.putInteger("age",24);
//构建批量插入
DataAbilityOperation operation1 = DataAbilityOperation.newInsertBuilder(uri).withValuesBucket(valuesBucket1).build();
//第二个
ValuesBucket valuesBucket2 = new ValuesBucket();
valuesBucket2.putString("name","Leo");
valuesBucket2.putInteger("age",48);
DataAbilityOperation operation2 = DataAbilityOperation.newInsertBuilder(uri).withValuesBucket(valuesBucket2).build();
ArrayList<DataAbilityOperation> operations = new ArrayList<>();
operations.add(operation1);
operations.add(operation2);
try {
//获取批量操作数据的结果
DataAbilityResult[] results = helper.executeBatch(uri,operations);
HiLog.debug(LABEL_LOG,results.length+"");
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
} catch (OperationExecuteException e) {
e.printStackTrace();
}
}
确定数据存储方式
确定数据的存储方式,Data 支持以下两种数据形式:
/**
* uri 打开对应的文件
*/
private void openUriFile() {
//构建uri
Uri uri = Uri.parse("dataability://com.llw.helloworld.UserDataAbility");
//获取文件 通过uri获取解码路径列表的第2条数据
File file = new File(uri.getDecodedPathList().get(1));
//只读
file.setReadOnly();
try {
//文件输入流
FileInputStream fileInputStream = new FileInputStream(file);
//得到文件描述符
FileDescriptor fileDescriptor = fileInputStream.getFD();
//绑定文件描述符
MessageParcel.dupFileDescriptor(fileDescriptor);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
数据库存储
初始化数据库连接。系统会在应用启动时调用 onStart()方法创建 Data 实例。在此方法中,开发者应该创建数据库连接,并获取连接对象,以便后续和数据库进行操作。为了避免影响应用启动速度,开发者应当尽可能将非必要的耗时任务推迟到使用时执行,而不是在此方法中执行所有初始化。示例:
package com.llw.helloworld;
import ohos.data.orm.OrmObject;
public class BookStore extends OrmObject {
private int id;
private String bookName;
private double price;
private int page;
private String author;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
上面的代码是官方文档里面的,可以看到这里是有一个地方报错的,因为少了一个参数,然后看一下getOrmContext方法少什么参数。
然后来看一下OrmMigration的源码
这是一个抽象类,可以通过继承的方式去实现它里面的方法。
下面我创建一个TestOrmContext1继承OrmMigration,里面的代码如下:
package com.llw.helloworld;
import ohos.data.orm.OrmMigration;
import ohos.data.rdb.RdbStore;
public class TestOrmContext1 extends OrmMigration {
/**
* 此处用于配置数据库版本迁移的开始版本和结束版本,
* super(startVersion, endVersion)即数据库版本号从 1 升到 2。
*/
public TestOrmContext1() {
super(1, 2);
}
/**
* 迁移时
*
* @param rdbStore
*/
@Override
public void onMigrate(RdbStore rdbStore) {
rdbStore.executeSql("ALTER TABLE `BookStore` ADD COLUMN `addColumn1` INTEGER");
}
}
其实这个方法的意思就是在连接数据库的时候查询数据库的版本,决定是否要升级。
因为加了也报错,那么我为什么不加上去呢?你以为加上去就不报错了吗?我是不得其解,也许是我才疏学浅吧。
package com.llw.helloworld;
public class User extends OrmObject {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
/**
* 查询数据库
*
* @param uri 目标uri
* @param columns 查询的字段
* @param predicates 查询的条件
* @return 结果集
*/
@Override
public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
if(ormContext == null){
HiLog.error(LABEL_LOG,"failed to query, ormContext is null");
return null;
}
//查询数据库
OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates,User.class);
ResultSet resultSet = ormContext.query(ormPredicates,columns);
if (resultSet == null){
HiLog.info(LABEL_LOG,"resultSet is null");
}
return resultSet;
}
/**
* 插入单条数据
* @param uri 目标uri
* @param value 插入的数据
* @return 插入后的id
*/
@Override
public int insert(Uri uri, ValuesBucket value) {
if (ormContext == null) {
HiLog.info(LABEL_LOG, "failed to insert, ormContext is null");
return -1;
}
//获取uri解码路径
String path = uri.getDecodedPath();
PathMatcher pathMatcher = new PathMatcher();
if (pathMatcher.getPathId(path) == PathMatcher.NO_MATCH) {
HiLog.info(LABEL_LOG, "UserDataAbility insert path is not matched");
return -1;
}
// 构造插入数据
User user = new User();
user.setId(value.getInteger("id"));
user.setName(value.getString("name"));
user.setAge(value.getInteger("age"));
//插入数据库
boolean isSuccessed = true;
isSuccessed = ormContext.insert(user);
if (!isSuccessed) {
HiLog.error(LABEL_LOG, "failed to insert");
return -1;
}
isSuccessed = ormContext.flush();
if (!isSuccessed) {
HiLog.error(LABEL_LOG, "failed to insert flush");
return -1;
}
DataAbilityHelper.creator(this, uri).notifyChange(uri);
int id = Math.toIntExact(user.getRowId());
return id;
}
/**
* 删除
* @param uri 目标uri
* @param predicates 删除条件
* @return 删除的结果
*/
@Override
public int delete(Uri uri, DataAbilityPredicates predicates) {
if (ormContext == null) {
HiLog.error(LABEL_LOG, "failed to delete, ormContext is null");
return -1;
}
OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, User.class);
int value = ormContext.delete(ormPredicates);
DataAbilityHelper.creator(this, uri).notifyChange(uri);
return value;
}
/**
* 更新数据
* @param uri 目标uri
* @param value 更新的数据
* @param predicates 更新条件
* @return 更新的结果
*/
@Override
public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
if (ormContext == null) {
HiLog.error(LABEL_LOG, "failed to update, ormContext is null");
return -1;
}
OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, User.class);
int index = ormContext.update(ormPredicates, value);
HiLog.info(LABEL_LOG, "UserDataAbility update value:" + index);
DataAbilityHelper.creator(this, uri).notifyChange(uri);
return index;
}
说实话写这一篇文章花费了一番功夫,不断的浏览官网上的文档然后结合实际来写,写的不是很好,请勿见怪,另外就是觉得官网的教程只是一部分,更多的需要开发者自行去探索和发现,正所谓师傅领进门,修行在个人,鸿蒙需要成长,我们开发者同样也要成长,也许不会前进的路上会很坎坷,但经历过后就会发现另一番风景,我是初学者,保持初学的态度和动力,感谢您的阅读,山高水长,后会有期!