鸿蒙Ability
导语
Ability可以分为FA(Feature Ability)和PA(Particle Ability)两种类型。
①Feature Ability
FA代表有界面的Ability,支持Page Ability模板(是FA唯一支持的模板),用于提供与用户交互的能力。
②Particle Ability
PA代表无界面的Ability,支持Service Ability和Data Ability模板。
Data Ability
Data Ability是基于Data模板的Ability(下文简称Data),主要用于对外部提供统一的数据访问抽象。
Data有助于应用管理其自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。既可用于同设备不同应用的数据共享,也支持跨设备不同应用的数据共享。数据的存放形式多样,可以是数据库,也可以是磁盘上的文件。Data对外提供对数据的增、删、改、查,以及打开文件等接口,这些接口的具体实现由开发者提供。
使用Data模板的Ability形式仍然是Ability,因此,开发者需要为应用添加一个或多个Ability的子类,来提供程序与其他应用之间的接口。
Data为结构化数据和文件提供了不同API接口供用户使用,因此,开发者需要首先确定好使用何种类型的数据。
确定数据的存储方式,Data支持以下两种数据形式:
①文件数据:如文本、图片、音乐等。
②结构化数据:如数据库等。
HarmonyOS提供多种数据管理能力,例如:关系型数据库、对象关系映射数据库、轻量级偏好数据库、分布式数据服务等。
本文以对象关系映射数据库为例。
这个连接是以关系型数据库为例:https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/HarmonyOS-Relational-Database
Data的创建
UserDataAbility用于接收其他应用发送的请求,提供外部程序访问的入口,从而实现应用间的数据访问。
实现UserDataAbility,需要在“Project”窗口当前工程的主目录(“entry > src > main > java > com.xxx.xxx”)选择“File > New > Ability > Empty Data Ability”,设置“Data Name”后完成UserDataAbility的创建。
Data提供了文件存储和数据库存储两组接口供用户使用。
输入名字(我取名为UserDataAbility,这里使用DataAbility做示范)
这样就创建好了呀~(我取名为UserDataAbility,这里使用DataAbility做示范)
可以看见UserDataAbility用于接收其他应用发送的请求,提供外部程序访问的入口,从而实现应用间的数据访问。
Data提供了文件存储和数据库存储两组接口供用户使用。
(我取名为UserDataAbility,这里使用DataAbility做示范)
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, "DataAbility onStart");
}
//DataAbilityHelper 为开发者提供了一系列的接口来访问不同类型的数据(文件、数据库等)。
//查
@Override
public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
return null;
}
//增
@Override
public int insert(Uri uri, ValuesBucket value) {
HiLog.info(LABEL_LOG, "DataAbility 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;
}
//获取URI类型
@Override
public String getType(Uri uri) {
return null;
}
}
然后打开配置文件
可以看到系统自动生成了Data的配置代码,"type": "data"
表示了这是一个Data Ability,"permissions"
是自带的一个提供给外部数据的权限,也已经访问这个TestDataAbility的"uri"
。
URI
Data的提供方和使用方都通过URI(Uniform Resource Identifier)来标识一个具体的数据(例如数据库中的某个表或磁盘上的某个文件)。HarmonyOS的URI仍基于URI通用标准,格式如下:
scheme:协议方案名,固定为“dataability”,代表Data Ability所使用的协议类型。
authority:设备ID。如果为跨设备场景,则为目标设备的ID;如果为本地设备场景,则不需要填写。
path:资源的路径信息,代表特定资源的位置信息。
query:查询参数。
fragment:可以用于指示要访问的子资源。
URI示例:(官网抄的)
跨设备场景:dataability://device_id/com.domainname.dataability.persondata/person/10
本地设备:dataability:///com.domainname.dataability.persondata/person/10
(注意:如需开放对其他程序可见,和Service一样,需要设置visible值的设置为true)
Data的实现
Data提供了文件存储和数据库存储两组接口供用户使用。
文件存储
开发者需要在Data中重写FileDescriptor openFile(Uri uri, String mode)方法来操作文件
/**
* 文件存储
*
* @param uri 客户端传入的请求目标路径
* @param mode 开发者对文件的操作选项,可选方式包含“r”(读), “w”(写), “rw”(读写)等
* @return
* @throws FileNotFoundException
*/
@Override
public FileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
//ohos.rpc.MessageParcel类提供了一个静态方法,用于获取MessageParcel实例。
//开发者可通过获取到的MessageParcel实例,
//使用dupFileDescriptor()函数复制待操作文件流的文件描述符,并将其返回,供远端应用访问文件。
//创建messageParcel
MessageParcel messageParcel = MessageParcel.obtain();
//获取文件
File file = new File(uri.getDecodedPathList().get(0));//get(0)是获取URI完整字段中查询参数字段。
if (mode == null || !"rw".equals(mode)) {
file.setReadOnly();
}
//通过文件获取文件输入流
FileInputStream fileIs = new FileInputStream(file);
FileDescriptor fd = null;
try {
//根据文件输入流获取文件描述符
fd = fileIs.getFD();
} catch (IOException e) {
e.printStackTrace();
HiLog.info(LABEL_LOG, "failed to getFD");
}
//绑定文件描述符
return MessageParcel.dupFileDescriptor(fd);
}
具体敬请期待参考本人的《data文件存储》文章(还未发布)
数据库存储(以对象关系映射数据库为例)
1、初始化数据库连接
系统会在应用启动时调用onStart()方法创建Data实例。在此方法中,开发者应该创建数据库连接,并获取连接对象,以便后续和数据库进行操作。为了避免影响应用启动速度,开发者应当尽可能将非必要的耗时任务推迟到使用时执行,而不是在此方法中执行所有初始化。
private static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0xD00201, "Data_Log");
//添加全局变量
private static final String DATABASE_NAME = "BookStore.db";//数据库文件名
private static final String DATABASE_NAME_ALIAS = "BookStore";//创建一个别名
private static OrmContext mOrmContext = null;//数据库操纵接口
@Override
public void onStart(Intent intent) {
super.onStart(intent);
//创建DataAbilityHelper并获取DataAbilityHelper对象实例
//DataAbilityHelper为开发者提供了creator()方法来创建DataAbilityHelper实例
//该方法为静态方法,有多个重载。最常见的方法是通过传入一个context对象来创建DataAbilityHelper对象。
DatabaseHelper helper = new DatabaseHelper(this);//context入参类型为ohos.app.Context,注意不要使用slice.getContext()来获取context,请直接传入slice,否则会出现找不到类的报错。
//通过对象数据操作接口OrmContext,创建一个别名为“BookStore”,数据库文件名为“BookStore.db”的数据库。
mOrmContext = helper.getOrmContext(DATABASE_NAME_ALIAS, DATABASE_NAME, BookStore.class);//如果数据库已经存在,不会重复创建。
}
2、编写数据库操作方法
Ability定义了6个方法供用户处理对数据库表数据的增删改查。这6个方法在Ability中已默认实现,开发者可按需重写。
/**
* 查询数据库
*
* @param uri 查询的目标路径
* @param columns 查询的列名
* @param predicates 查询条件 由类DataAbilityPredicates构建
* @return 结果集
*/
@Override
public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
if (mOrmContext == null) {
HiLog.error(LABEL_LOG, "failed to query, ormContext is null");//查询失败,ormContext为空
return null;
}
//查询数据库
OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, User.class);//目标路径
ResultSet resultSet = mOrmContext.query(ormPredicates, columns);
//如果数据库找不到这条记录
if (resultSet.getRowCount() == 0) {//获取行计数
HiLog.error(LABEL_LOG, "resultSet is null");//结果集为空
}
//返回结果
return resultSet;
}
/**
* 向数据库中插入单条数据
*
* @param uri 插入的目标路径
* @param value 插入的数据值 插入的数据由ValuesBucket封装
* @return 插入后的id
*/
@Override
public int insert(Uri uri, ValuesBucket value) {
//参数校验
if (mOrmContext == null) {
HiLog.error(LABEL_LOG, "failed to insert, ormContext is null");//插入失败,ormContext为空
return -1;
}
//请求不是插入User表的
if (uri.getDecodedPath().equals("org.example.harmonydataabilitydemo.UserDataAbility")) {
return -1;
}
//构造插入数据
User user = new User();
user.setUserId(value.getInteger("userId"));
user.setFirstName(value.getString("firstName"));
user.setLastName(value.getString("lastName"));
user.setAge(value.getInteger("age"));
user.setBalance(value.getDouble("balance"));
//插入数据库
boolean isSuccessful;
isSuccessful = mOrmContext.insert(user);//直接传入OrmObject对象的增加接口
if (!isSuccessful) {
HiLog.error(LABEL_LOG, "failed to insert");//插入失败
return -1;
}
//只有在flush()接口被调用后才会持久化到数据库中
isSuccessful = mOrmContext.flush();
if (!isSuccessful) {
HiLog.error(LABEL_LOG, "failed to insert flush");//插入刷新flush()失败
return -1;
}
//DataAbilityHelper是用于数据操作的帮助程序类
//DataAbilityHelper它使用IDataAbilityObserver注册一个观察器以监视数据。
//调用creator(ohos.app.Context,ohos.utils.net.Uri,boolean)创建一个具有给定Uri的实例
DataAbilityHelper.creator(this, uri).notifyChange(uri);
int id = Math.toIntExact(user.getRowId());//获取列id
HiLog.error(LABEL_LOG, "UserDataAbility::insert id=" + id);//输出插入的数据id
//此方法返回一个int类型的值用于标识结果
return id;
}
/**
* 向数据库中插入多条数据 批量插入
* 接收一个ValuesBucket数组用于单次插入一组对象
* 它的作用是提高插入多条重复数据的效率。该方法系统已实现,开发者可以直接调用。
*
* @param uri 插入的目标路径
* @param values 插入的数据值
* @return
*/
@Override
public int batchInsert(Uri uri, ValuesBucket[] values) {
return super.batchInsert(uri, values);
}
/**
* 删除一条或多条数据
*
* @param uri 目标路径
* @param predicates 删除条件由类DataAbilityPredicates构建
* @return 删除的结果
*/
@Override
public int delete(Uri uri, DataAbilityPredicates predicates) {
if (mOrmContext == null) {
HiLog.error(LABEL_LOG, "failed to delete, ormContext is null");//删除失败,ormContext为零
return -1;
}
OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, User.class);
int value = mOrmContext.delete(ormPredicates);
DataAbilityHelper.creator(this, uri).notifyChange(uri);
HiLog.error(LABEL_LOG, "UserDataAbility::delete value=" + value);
return value;
}
/**
* 更新数据库
*
* @param uri 目标路径
* @param value 用户可以在ValuesBucket参数中指定要更新的数据
* @param predicates 在DataAbilityPredicates中构建更新的条件
* @return 更新的结果
*/
@Override
public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
if (mOrmContext == null) {
HiLog.error(LABEL_LOG, "failed to update, ormContext is null");//更新失败,ormContext为零
return -1;
}
OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, User.class);
int index = mOrmContext.update(ormPredicates, value);
HiLog.error(LABEL_LOG, "UserDataAbility::update index=" + index);
DataAbilityHelper.creator(this, uri).notifyChange(uri);
return index;
}
/**
* 批量操作数据库
*
* @param operations DataAbilityOperation中提供了设置操作类型、数据和操作条件的方法,用户可自行设置自己要执行的数据库操作
* @return
* @throws OperationExecuteException
*/
@Override
public DataAbilityResult[] executeBatch(ArrayList<DataAbilityOperation> operations) throws OperationExecuteException {
return super.executeBatch(operations);
}
注意!上述代码已经初始化了数据库类BookStore.class、实体类User.class,详情请参考本人文章《鸿蒙对象关系映射数据库》的 对象关系映射数据库存储开发步骤1、2、3
访问Data
开发者可以通过DataAbilityHelper类来访问当前应用或其他应用提供的共享数据。DataAbilityHelper作为客户端,与提供方的Data进行通信。Data接收到请求后,执行相应的处理,并返回结果。DataAbilityHelper提供了一系列与Data Ability对应的方法。
创建DataAbilityHelper
DataAbilityHelper为开发者提供了creator()方法来创建DataAbilityHelper实例。该方法为静态方法,有多个重载。最常见的方法是通过传入一个context对象来创建DataAbilityHelper对象。
DataAbilityHelper helper = DataAbilityHelper.creator(this);
访问Data Ability
访问文件
在访问文件的时候,需要先在配置文件中声明使用权限
"reqPermissions": [
{
"name": "ohos.permission.READ_USER_STORAGE"
}
]
然后需要动态申请权限(关于动态申请权限可以参考本人的《鸿蒙开发基础知识》文章)
private void requestPermissions() {
if (verifySelfPermission(SystemPermission.READ_USER_STORAGE) != IBundleManager.PERMISSION_GRANTED) {
requestPermissionsFromUser(new String[]{SystemPermission.READ_USER_STORAGE}, 0);
}
}
DataAbilityHelper为开发者提供了FileDescriptor openFile(Uri uri, String mode)方法来操作文件,该方法返回一个目标文件的FD(文件描述符),把文件描述符封装成流,开发者就可以对文件流进行自定义处理。
private Image mImageImg;
mImageImg = (Image) findComponentById(ResourceTable.Id_image_img);//图片
//选择图片
private void selectImage() {
ResultSet resultSet = null;
try {
/**
* 获取查询的结果集
* 第一个参数为文件对应的Uri
* 第二个参数为想要查询的字段,这里因为是查询所有的图片,所以填null即可
* 第三个参数为查询条件,同样为null
*/
//获取媒体外部存储中的图片URI需要使用的预定义字段是:AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI
resultSet = mDataAbilityHelper.query(AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI, null, null);
//因为这里只需要显示一张图片,所以就不遍历了,直接获取第一条即可
resultSet.goToFirstRow();
//通过指定的下标获取列索引,通过获得的列索引获取到该图片的资源id。
int mediaId = resultSet.getInt(resultSet.getColumnIndexForName(AVStorage.Images.Media.ID));//通过AVStorage访问系统相册
//将图片id转换为Uri类型
Uri uri = Uri.appendEncodedPathToUri(AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI, mediaId + "");
HiLog.error(LABEL_LOG, "" + uri);
//读取文件描述符
FileDescriptor fileDescriptor = mDataAbilityHelper.openFile(uri, "r");
//获取文件输入流
FileInputStream fileInputStream = new FileInputStream(fileDescriptor);
//使用文件描述符封装成的文件流,进行文件操作
ImageSource imageSource = ImageSource.create(fileInputStream, null);
PixelMap pixelMap = imageSource.createPixelmap(null);
mImageImg.setPixelMap(pixelMap);
} catch (DataAbilityRemoteException | FileNotFoundException e) {
e.printStackTrace();
}
}
访问数据库
DataAbilityHelper为开发者提供了增、删、改、查以及批量处理等方法来操作数据库。
可以在MainAbilitySlice中,编写button调用方法来展示访问数据库
值得注意的是
//下面的uriString要与config.json中DataAbility的uri保持一致
//但是!!!!!他有三个斜杠!!!!!如果你写错了,恭喜你会看见paths is illegal错误
private static final String BASE_URI = "dataability:///org.example.harmonydataabilitydemo.UserDataAbility";
paths is illegal错误
访问数据库方法如下:
//插入单条数据
private void insertData() {
//构造插入数据
ValuesBucket valuesBucket = new ValuesBucket();
valuesBucket.putString("firstName", "一");
valuesBucket.putString("lastName", "yi");
valuesBucket.putInteger("age", 11);
valuesBucket.putDouble("balance", 111.11);
//构造uri
Uri uri = Uri.parse(BASE_URI);
//进行数据插入
try {
int id = mDataAbilityHelper.insert(uri, valuesBucket);
HiLog.error(LABEL_LOG, "MainAbilitySlice::insert id=" + id);
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
//查询
private void queryData() {
//构造查询条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
//predicates提供了很多方法
predicates.equalTo("firstName", "一");
//查询年龄在1~50之间的数据
//predicates.between("age",1,50);
//构建查询字段
String[] columns = new String[]{"age", "lastName"};
//构造uri
Uri uri = Uri.parse(BASE_URI);
//进行查询
try {
//用一个结果集来接收查询返回的数据
ResultSet resultSet = mDataAbilityHelper.query(uri, columns, predicates);
if (resultSet.getRowCount() == 0) {
HiLog.error(LABEL_LOG, "MainAbilitySlice::query找不到这条记录");
return;
}
//从第一行开始
resultSet.goToFirstRow();
//处理每一行的数据
do {
//在此处理ResultSet中的记录
HiLog.error(LABEL_LOG, "MainAbilitySlice::query lastName=" + resultSet.getString(1));
} while (resultSet.goToNextRow());
// //处理结果
// while (resultSet.goToNextRow()) {
// //在此处理ResultSet中的记录
// HiLog.error(LABEL_LOG, "MainAbilitySlice::lastName=" + resultSet.getString(1));
// }
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
//更新
private void updateData() {
//构造更新条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
predicates.equalTo("firstName", "一");
//构造更新数据
ValuesBucket valuesBucket = new ValuesBucket();
valuesBucket.putString("lastName", "一");
valuesBucket.putInteger("age", 20);
//构造uri
Uri uri = Uri.parse(BASE_URI);
try {
//更新数据
int index = mDataAbilityHelper.update(uri, valuesBucket, predicates);
HiLog.error(LABEL_LOG, "MainAbilitySlice::update index=" + index);//更新了几条数据
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
//删除数据
private void deleteData() {
//构造删除条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
predicates.equalTo("firstName", "一");
//age在18~25的数据
//predicates.between("age",18,25);
//构造uri
Uri uri = Uri.parse(BASE_URI);
try {
//删除
mDataAbilityHelper.delete(uri, predicates);
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
//插入多条数据
private void insertMoreData() {
//构造插入数据
ValuesBucket[] valuesBuckets = new ValuesBucket[3];
//构建第一条数据
valuesBuckets[0] = new ValuesBucket();
valuesBuckets[0].putString("firstName", "二");
valuesBuckets[0].putString("lastName", "er");
valuesBuckets[0].putInteger("age", 26);
valuesBuckets[0].putDouble("balance", 120.5);
//构建第二条数据
valuesBuckets[1] = new ValuesBucket();
valuesBuckets[1].putString("firstName", "三");
valuesBuckets[1].putString("lastName", "san");
valuesBuckets[1].putInteger("age", 27);
valuesBuckets[1].putDouble("balance", 99.00);
//构建第三条数据
valuesBuckets[2] = new ValuesBucket();
valuesBuckets[2].putString("firstName", "四");
valuesBuckets[2].putString("lastName", "si");
valuesBuckets[2].putInteger("age", 28);
valuesBuckets[2].putDouble("balance", 200.00);
//构造uri
Uri uri = Uri.parse(BASE_URI);
try {
//批量插入数据
mDataAbilityHelper.batchInsert(uri, valuesBuckets);
} catch (DataAbilityRemoteException e) {
e.printStackTrace();
}
}
//批量操作数据
private void moreOperationData() {
//构造uri
Uri uri = Uri.parse(BASE_URI);
//构造批量操作
//第一个
ValuesBucket valuesBucket1 = new ValuesBucket();
valuesBucket1.putString("firstName", "五");
valuesBucket1.putString("lastName", "wu");
valuesBucket1.putInteger("age", 51);
valuesBucket1.putDouble("balance", 220.00);
//构建批量插入
DataAbilityOperation operation1 = DataAbilityOperation.newInsertBuilder(uri).withValuesBucket(valuesBucket1).build();
//第二个
ValuesBucket valuesBucket2 = new ValuesBucket();
valuesBucket2.putString("firstName", "一");
valuesBucket2.putString("lastName", "yiyi");
valuesBucket2.putInteger("age", 52);
valuesBucket2.putDouble("balance", 120.00);
DataAbilityOperation operation2 = DataAbilityOperation.newInsertBuilder(uri).withValuesBucket(valuesBucket2).build();
//第三个
//构造更新条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
predicates.equalTo("firstName", "一");
//构造更新数据
ValuesBucket valuesBucket3 = new ValuesBucket();
valuesBucket3.putString("lastName", "一一");
DataAbilityOperation operation3 = DataAbilityOperation.newUpdateBuilder(uri).withValuesBucket(valuesBucket3).build();
ArrayList<DataAbilityOperation> operations = new ArrayList<>();
operations.add(operation1);
operations.add(operation2);
operations.add(operation3);
try {
//获取批量操作数据的结果
DataAbilityResult[] results = mDataAbilityHelper.executeBatch(uri, operations);
HiLog.error(LABEL_LOG, "MainAbilitySlice::批量操作" + results.length);
} catch (DataAbilityRemoteException | OperationExecuteException e) {
e.printStackTrace();
}
}
项目展示