鸿蒙 Ability 讲解(Data Ability讲解)

dmzhaoq1
发布于 2021-3-8 09:42
浏览
1收藏

四、Data Ability讲解

 

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

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

 

① URI 介绍


  Data 的提供方和使用方都通过 URI(Uniform Resource Identifier)来标识一个具体的数据,例如数据库中的某个表或磁盘上的某个文件。HarmonyOS 的 URI 仍基于 URI 通用标准,格式如下:

  • scheme:协议方案名,固定为“dataability”,代表 Data Ability 所使用的协议类型。
  • authority:设备 ID,如果为跨设备场景,则为目的设备的 IP 地址;如果为本地设备场景,则不需要填写。
  • path:资源的路径信息,代表特定资源的位置信息。
  • query:查询参数。
  • fragment:可以用于指示要访问的子资源。
    URI 示例:
    跨设备场景:dataability://device_id/com.huawei.dataability.persondata/person/10
    本地设备:dataability:///com.huawei.dataability.persondata/person/10


② 访问 Data和声明使用权限


  开发者可以通过 DataAbilityHelper 类来访问当前应用或其他应用提供的共享数据。
  DataAbilityHelper 作为客户端,与提供方的 Data 进行通信。Data 接收到请求后,执行相应的处理,并返回结果。DataAbilityHelper 提供了一系列与 Data Ability 对应的方法。鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区reqPermissions 表示应用运行时向系统申请的权限。
说了这么多还是来创建一个Data Ability吧,鼠标右键包名 → New → Ability → Empty Data Ability鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区这个的Visible和Service的Visible是同样的意思,勾选上就是运行其他应用程序访问数据。鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区然后打开config.json,看创建DataAbility时,自动生成了那些代码。鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区

可以看到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 对象。鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区

DataAbilityHelper 为开发者提供了一系列的接口来访问不同类型的数据(文件、数据库等)。

 

  • 访问文件
    DataAbilityHelper 为开发者提供了 FileDescriptor openFile(Uri uri, String mode)方法来操作文件。此方法需要传入两个参数,其中 uri 用来确定目标资源路径,mode 用来指定打开文件的方式,可选方式包含“r”(读), “w”(写), “rw”(读写),“wt”(覆盖写),“wa”(追加写),“rwt”(覆盖写且可读)。该方法返回一个目标文件的 FD(文件描述符),把文件描述符封装成流,开发者就可以对文件流进行自定义处理。比如:
    		// 读取文件描述符
            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();
            }
    ​

 

  • 访问数据库
    DataAbilityHelper 为开发者提供了增、删、改、查以及批量处理等方法来操作数据库。
    下面代码来说明一下:
  • query 查询方法,其中 uri 为目标资源路径,columns 为想要查询的字段。开发者的查询条件可以通过 DataAbilityPredicates 来构建。查询用户表中 id 在 1-10 之间的用户的年龄,并把结果打印出来,代码示例如下:
    	/**
         * 查询
         */
        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();
            }
        }
    ​

insert 插入方法,其中 uri 为目标资源路径,ValuesBucket 为要新增的对象。插入一条用户信息的代码示例如下:

	/**
     * 插入 单条数据
     */
    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();
        }
    }

batchInsert 批量插入方法,和 insert()类似。批量插入用户信息的代码示例如下:

	/**
     * 插入 多条数据
     * @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();
        }
    }

delete 删除方法,其中删除条件可以通过 DataAbilityPredicates 来构建。删除用户表中 id 在 1-10 之间的用户,代码示例如下:

	/**
     * 删除数据
     * @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();
        }
    }

update 更新方法,更新数据由 ValuesBucket 传入,更新条件由 DataAbilityPredicates 来构建。更新 id 为 2 的用户,代码示例如下:

	/**
     * 更新数据
     * @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();
        }
    }

executeBatch 此方法用来执行批量操作。DataAbilityOperation 中提供了设置操作类型、数据和操作条件的方法,开发者可自行设置自己要执行的数据库操作。插入多条数据的代码示例如下:

	/**
     * 批量操作数据
     * @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
确定数据存储方式
确定数据的存储方式,Data 支持以下两种数据形式:

 

文件数据:如文本、图片、音乐等。
结构化数据:如数据库等。
下面创建一个UserDataAbility,注意勾选上Visible鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区实现 UserDataAbility
  UserDataAbility 接收其他应用发送的请求,提供外部程序访问的入口,从而实现应用间的数据访问。Data 提供了文件存储和数据库存储两组接口供用户使用。

 

文件存储
  开发者需要在 Data 中重写 FileDescriptor openFile(Uri uri, String mode)方法来操作文件:uri 为客户端传入的请求目标路径;mode 为开发者对文件的操作选项,可选方式包含“r”(读), “w”(写), “rw”(读写)等。
  MessageParcel 类提供了一个静态方法,用于获取 MessageParcel 实例。通过dupFileDescriptor()函数复制待操作文件流的文件描述符,并将其返回,供远端应用使用。示例,根据传入uri打开对应的文件,在UserDataAbility中写入如下方法

	/**
     * 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 实例。在此方法中,开发者应该创建数据库连接,并获取连接对象,以便后续和数据库进行操作。为了避免影响应用启动速度,开发者应当尽可能将非必要的耗时任务推迟到使用时执行,而不是在此方法中执行所有初始化。示例:

初始化的时候连接数据库。
首先要创建一个数据实体bean

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;
    }
}

然后在UserDataAbility中如下:鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区上面的代码是官方文档里面的,可以看到这里是有一个地方报错的,因为少了一个参数,然后看一下getOrmContext方法少什么参数。

 

然后来看一下OrmMigration的源码鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区这是一个抽象类,可以通过继承的方式去实现它里面的方法。
下面我创建一个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");
    }
}

其实这个方法的意思就是在连接数据库的时候查询数据库的版本,决定是否要升级。
因为加了也报错,那么我为什么不加上去呢?你以为加上去就不报错了吗?我是不得其解,也许是我才疏学浅吧.鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区编写数据库操作方法
Ability 定义了 6 个方法供用户处理对数据库表数据的增删改查。
先创建一个用户的实体

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;
    }
}

query 该方法接收三个参数,分别是查询的目标路径,查询的列名,以及查询条件,查询条件由类DataAbilityPredicates 构建。根据传入的列名和查询条件查询用户表的代码示例如下:鸿蒙 Ability 讲解(Data Ability讲解)-鸿蒙开发者社区可以看到创建Data Ability的时候就会自动生成这个方法,下面的代码就在这个方法里面写:

 

	/**
     * 查询数据库
     *
     * @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;
    }

 

insert 该方法接收两个参数,分别是插入的目标路径和插入的数据值。其中,插入的数据由ValuesBucket 封装,服务端可以从该参数中解析出对应的属性,然后插入到数据库中。此方法返回一个 int 类型的值用于标识结果。接收到传过来的用户信息并把它保存到数据库中的代码示例如下:

 

	/**
     * 插入单条数据
     * @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;
    }

batchInsert 该方法为批量插入方法,接收一个 ValuesBucket 数组用于单次插入一组对象。它的作用是提高插入多条重复数据的效率。该方法系统已实现,开发者可以直接调用。


delete 该方法用来执行删除操作。删除条件由类 DataAbilityPredicates 构建,服务端在接收到该参数之后可以从中解析出要删除的数据,然后到数据库中执行。根据传入的条件删除用户表数据的代码示例如下:

	/**
     * 删除
     * @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;
    }

 

update 此方法用来执行更新操作。用户可以在 ValuesBucket 参数中指定要更新的数据,在DataAbilityPredicates 中构建更新的条件等。更新用户表的数据的代码示例如下:

	/**
     * 更新数据
     * @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;
    }

executeBatch 此方法用来批量执行操作。DataAbilityOperation 中提供了设置操作类型、数据和操作条件的方法,用户可自行设置自己要执行的数据库操作。该方法系统已实现,开发者可以直接调用。


五、结语


  说实话写这一篇文章花费了一番功夫,不断的浏览官网上的文档然后结合实际来写,写的不是很好,请勿见怪,另外就是觉得官网的教程只是一部分,更多的需要开发者自行去探索和发现,正所谓师傅领进门,修行在个人,鸿蒙需要成长,我们开发者同样也要成长,也许不会前进的路上会很坎坷,但经历过后就会发现另一番风景,我是初学者,保持初学的态度和动力,感谢您的阅读,山高水长,后会有期!


 

分类
已于2021-3-8 09:42:39修改
收藏 1
回复
举报
1条回复
按时间正序
/
按时间倒序
麒麟Berlin
麒麟Berlin

你的BookStore 好像集成错了,看源码 <T extends OrmDatabase>,应该集成OrmDatabase

回复
2021-3-19 22:55:25
回复
    相关推荐