HarmonyOS Sample 之 DataAbility RDB数据库操作 原创 精华

Buty9147
发布于 2021-7-26 18:00
浏览
5收藏

@toc

DataAbility RDB数据库操作

介绍

使用Data模板的Ability(以下简称“Data”)有助于应用管理其自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。Data既可用于同设备不同应用的数据共享,也支持跨设备不同应用的数据共享。
数据的存放形式多样,可以是数据库,也可以是磁盘上的文件。Data对外提供对数据的增、删、改、查,以及打开文件等接口,这些接口的具体实现由开发者提供。
本示例演示了如何使用Data Ability对RDB数据库进行增、删、改、查,以及读取文本文件。
模仿手机的备忘录,实现了简单的操作。

搭建环境

安装DevEco Studio,详情请参考DevEco Studio下载
设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

步骤

1.创建一个DataAbility和数据库常量类

a.创建一个Empty DataAbility
entity右键,New- Ability-Empty Data Ability,然后输入名称 NoteDataAbility
HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙开发者社区

b.创建一个数据库常量类 Const.java
存放数据库名称、表名称、字段列名称、存储路径等
需要注意的是,
BASE_URI 3个杠后面的部分要和config.json Data Ability 声明的uri完全一致,否则应用无法启动

/**
 * Const
 */
public class Const {
    /**
     * DataAbility base uri
     * scheme:协议方案名,固定为“dataability”,代表Data Ability所使用的协议类型。
     * authority:设备ID。如果为跨设备场景,则为目标设备的ID;如果为本地设备场景,则不需要填写。
     * path:资源的路径信息,代表特定资源的位置信息。
     * query:查询参数。
     * fragment:可以用于指示要访问的子资源。
     * 本地设备的“device_id”字段为空,因此在“dataability:”后面有三个“/”
     *
     * BASE_URI 3个杠后面的部分要和config.json  Data Ability 声明的uri完全一致,否则应用无法启动
     *
     */
    public static final String BASE_URI = "dataability:///ohos.samples.dataability.NoteDataAbility";

    /**
     * Database name
     */
    public static final String DB_NAME = "note.db";

    /**
     * Database table name
     */
    public static final String DB_TAB_NAME = "note";

    /**
     * Database column name:Id
     */
    public static final String DB_COLUMN_ID = "Id";
    /**
     * Database column name:noteTitle
     */
    public static final String DB_COLUMN_TITLE = "noteTitle";

    /**
     * Database column name:writeTime
     */
    public static final String DB_COLUMN_TIME = "writeTime";

    /**
     * Database column name:noteCategory
     */
    public static final String DB_COLUMN_CATEGORY = "noteCategory";
    /**
     * Database column name:noteContent
     */
    public static final String DB_COLUMN_CONTENT = "noteContent";

    /**
     * Database data path
     */
    public static final String DATA_PATH = "/note";


    /**
     * 文件名称
     */
    public static final String FILE_NAME = "userdataability.txt";

}

c.config.json相关配置
config.json涉及NoteDataAbility.java 的地方有3处,
第1处在module对象下,
HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙开发者社区

第2处是abilities对象下,
permissions表示其他应用的能力调用当前能力所需的权限。
默认情况下隐藏"visible"字段(值为false),表示仅本应用可访问该Data,开发人员可根据需求修改permissions、visible值、uri等内容。当外部应用需要访问/控制此数据库字段时,在该Data Ability配置中增加"visible": true,并在外面应用的配置文件config.json中申请permissions权限。
HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙开发者社区

第3处是reqPermissions对象下,
说明:如果待访问的Data Ability是由本应用创建,则可以不声明该权限。
HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙开发者社区

2.声明数据库存储对象和数据库配置

在NoteDataAbility.java 添加如下代码

//声明数据库存储对象
private RdbStore rdbStore;
//数据库配置,指定数据库名称
private StoreConfig storeConfig = StoreConfig.newDefaultConfig(Const.DB_NAME);

3.实现打开RDB数据库回调函数

在NoteDataAbility.java 添加如下代码

// 管理数据库创建、升级和降级。
// 您可以创建一个子类来实现 #onCreate、#onUpgrade 或 #onOpen 方法。 
// 如果一个数据库已经存在,它将被打开; 如果不存在数据库,则将创建一个数据库。 
// 在数据库升级过程中,也会调用该类的方法。
private RdbOpenCallback rdbOpenCallback = new RdbOpenCallback() {
    @Override
    public void onCreate(RdbStore rdbStore) {
        //创建表
        rdbStore.executeSql(
                "create table if not exists " + Const.DB_TAB_NAME + "2 (" +
                        Const.DB_COLUMN_ID + " integer primary key autoincrement ," +
                        Const.DB_COLUMN_TITLE + " text not null," +
                        Const.DB_COLUMN_CONTENT + " text not null," +
                        Const.DB_COLUMN_TIME + " text not null," +
                        Const.DB_COLUMN_CATEGORY + " text not null" +

                        ")"
        );
    }

    @Override
    public void onUpgrade(RdbStore rdbStore, int i, int i1) {
        //数据库升级
    }
};

4.初始化RDB数据库存储对象

在NoteDataAbility.java 添加如下代码

@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    HiLog.info(LABEL_LOG, "NoteDataAbility onStart");
    //数据库帮助类
    DatabaseHelper databaseHelper = new DatabaseHelper(this);
    //初始化RDB数据库存储对象
    rdbStore = databaseHelper.getRdbStore(storeConfig, 1, rdbOpenCallback);

}

5.实现对数据库的基本操作函数

NoteDataAbility.java操作数据库的方法都需要自己实现,包括:添加、修改、查询、删除,还有打开文件,主要使用rdbStore对象。

@Override
public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
    HiLog.info(LABEL_LOG, "NoteDataAbility query");
    RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, Const.DB_TAB_NAME);
    return rdbStore.query(rdbPredicates, columns);
}

@Override
public int insert(Uri uri, ValuesBucket value) {
    HiLog.info(LABEL_LOG, "NoteDataAbility insert");
    //long to int
    int rowId = (int) rdbStore.insert(Const.DB_TAB_NAME, value);
    //通知观察者数据发生变化
    DataAbilityHelper.creator(this).notifyChange(uri);
    return rowId;
}

@Override
public int delete(Uri uri, DataAbilityPredicates predicates) {
    //rdb 条件,通过DataAbilityUtils将DataAbilityPredicates转成 RdbPredicates
    RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, Const.DB_TAB_NAME);
    //执行删除
    int rowId = rdbStore.delete(rdbPredicates);
    HiLog.info(LABEL_LOG, "%{public}s", "delete");
    //通知观察者数据发生变化
    DataAbilityHelper.creator(this).notifyChange(uri);
    return rowId;
}

@Override
public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
    //rdb 条件,通过DataAbilityUtils将DataAbilityPredicates转成 RdbPredicates
    RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, Const.DB_TAB_NAME);
    int rowId =rdbStore.update(value, rdbPredicates);
    //通知观察者数据发生变化
    DataAbilityHelper.creator(this).notifyChange(uri);
    return rowId;
}

@Override
public FileDescriptor openFile(Uri uri, String mode) {

    //获取应用程序在设备内部存储器上存放文件的目录
    File file = new File(getFilesDir(), uri.getDecodedQuery());

    FileDescriptor fileDescriptor = null;
    try {
        FileInputStream fis = new FileInputStream(file);
        //获取FD
        fileDescriptor = fis.getFD();
        //获取一个新的文件描述符,它是现有文件描述符的副本
        return MessageParcel.dupFileDescriptor(fileDescriptor);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return fileDescriptor;
}

6.数据的订阅和通知

在NoteDataAbility.java 中, 我们看到insert/update/delete方法都有一行

DataAbilityHelper.creator(this).notifyChange(uri);

目的是在数据库数据发生变化时,通知数据的订阅者。

而在MainAbilitySlice.java 类中有如下方法,在OnStart()中被调用,实现了数据变化的订阅

private void initDatabaseHelper() {
    //创建实例
    dataAbilityHelper = DataAbilityHelper.creator(this);
    //注册一个观察者来观察给定 Uri 指定的数据,dataObserver表示 IDataAbilityObserver 对象
    dataAbilityHelper.registerObserver(Uri.parse(Const.BASE_URI), dataAbilityObserver);
}

同时,数据变化订阅方还需要实现IDataAbilityObserver接口,在数据变化时会自动回调,完成对应的逻辑处理。

//观察者模式,数据变化时回调
private final IDataAbilityObserver dataAbilityObserver=() -> {
    HiLog.info(LABEL, "%{public}s", "database changed");
    //筛选数据
    initLists(this);
};

当数据订阅者不再需要订阅Data变化时,则调用unregisterObserver​(Uri uri, IDataAbilityObserver dataObserver)方法取消。

@Override
protected void onStop() {
    super.onStop();
    dataAbilityHelper.unregisterObserver(Uri.parse(Const.BASE_URI), dataAbilityObserver);
}

观察者模式的作用在于
当数据库表格的内容产生变化时,可以主动通知与该表格数据相关联的进程或者应用,从而使得相关进程或者应用接收到数据变化后完成相应的处理。

7.访问Data Ability,新建AddNoteAbility,在AddNoteAbilitySlice实现数据的添加和修改

开发者可以通过DataAbilityHelper类来访问当前应用或其他应用提供的共享数据。
DataAbilityHelper作为客户端,与提供方的Data进行通信。DataAbilityHelper提供了一系列与Data Ability通信的方法。
a.数据的添加

/**
 * 保存数据
 *
 * @param component component
 */
private void saveNote(Component component) {
    ValuesBucket valuesBucket = new ValuesBucket();
    TextField noteTitle = (TextField) findComponentById(ResourceTable.Id_add_note_title);
    if (noteTitle.getText().isEmpty()) {
        DialLogUtils dialog = new DialLogUtils(this, "标题不能为空!");
        dialog.showDialog();
        return;
    }
    TextField noteContent = (TextField) findComponentById(ResourceTable.Id_add_note_content);
    if (noteContent.getText().isEmpty()) {
        DialLogUtils dialog = new DialLogUtils(this, "内容不能为空!");
        dialog.showDialog();
        return;
    }
    Text noteCategory = (Text) findComponentById(ResourceTable.Id_add_note_category);
    Text noteTime = (Text) findComponentById(ResourceTable.Id_add_note_time);

    HiLog.debug(LABEL, "%{public}s", "saveNote, noteId:[" + noteId + "],noteCategory:" + noteCategory.getText());
    int rowId;
    //放入键值
    valuesBucket.putString(Const.DB_COLUMN_TITLE, noteTitle.getText());
    valuesBucket.putString(Const.DB_COLUMN_CATEGORY, noteCategory.getText());
    valuesBucket.putString(Const.DB_COLUMN_CONTENT, noteContent.getText());
    valuesBucket.putString(Const.DB_COLUMN_TIME, noteTime.getText());
    try {
        if (noteId.isEmpty()) {
            HiLog.debug(LABEL, "%{public}s", "saveNote, insert");
            //插入数据
            rowId = dataAbilityHelper.insert(Uri.parse(Const.BASE_URI + Const.DATA_PATH), valuesBucket);
            HiLog.debug(LABEL, "%{public}s", "insert,rowId:" + rowId);

        } else {
            HiLog.debug(LABEL, "%{public}s", "saveNote, update");
            //指定修改谓语
            DataAbilityPredicates predicates = new DataAbilityPredicates();
            predicates.equalTo(Const.DB_COLUMN_ID, noteId);
            //修改数据
            rowId = dataAbilityHelper.update(Uri.parse(Const.BASE_URI + Const.DATA_PATH), valuesBucket, predicates);
            HiLog.debug(LABEL, "%{public}s", "update,rowId:" + rowId);
        }

        //返回列表页
        backListPage();
    } catch (DataAbilityRemoteException | IllegalStateException exception) {
        HiLog.error(LABEL, "%{public}s", "insert: dataRemote exception|illegalStateException");
    }
}

b.修改和删除数据

@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    //设置UI布局资源
    super.setUIContent(ResourceTable.Layout_ability_add_note);
    //
    initDatabaseHelper();
    //返回按钮
    Component backButton = findComponentById(ResourceTable.Id_back_image);
    backButton.setClickedListener(component -> terminateAbility());
    TextField noteContent = (TextField) findComponentById(ResourceTable.Id_add_note_content);


    //修改笔记
    if (intent.hasParameter("Id")) {
        HiLog.info(LABEL, "%{public}s", "change data coming");
        noteId = intent.getStringParam("Id");
        HiLog.info(LABEL, "%{public}s", "noteId:" + noteId);
        if (noteId != null) {
            DataAbilityPredicates predicates = new DataAbilityPredicates();
            predicates.equalTo(Const.DB_COLUMN_ID, noteId);
            //查询数据
            NoteListItemInfo itemInfo = queryOne(predicates);
            HiLog.info(LABEL, "%{public}s", "noteTitle:" + itemInfo.getNoteTitle() + ",category:" + itemInfo.getNoteCategory());
            //设置显示
            TextField noteTitle = (TextField) findComponentById(ResourceTable.Id_add_note_title);
            noteTitle.setText(itemInfo.getNoteTitle());

            noteContent.setText(itemInfo.getNoteContent());

            Text category = (Text) findComponentById(ResourceTable.Id_add_note_category);
            category.setText(itemInfo.getNoteCategory());

            Text noteTime = (Text) findComponentById(ResourceTable.Id_add_note_time);
            noteTime.setText(itemInfo.getNoteTime());


            Component deleteButton = findComponentById(ResourceTable.Id_delete_image);
            //设置删除按钮可用,只有修改笔记才能删除
            deleteButton.setClickable(true);
            //添加事件
            deleteButton.setClickedListener(component -> {
                try {
                    int rowId = dataAbilityHelper.delete(Uri.parse(Const.BASE_URI + Const.DATA_PATH), predicates);
                    HiLog.info(LABEL, "%{public}s", "deleteNote,rowId:" + rowId);

                    //返回列表页
                    backListPage();
                } catch (DataAbilityRemoteException e) {
                    HiLog.error(LABEL, "%{public}s", "delete: exception|DataAbilityRemoteException");
                }
            });
        }
    } else {
        Text timeText = (Text) findComponentById(ResourceTable.Id_add_note_time);
        String time24 = sdf.format(new Date());
        timeText.setText(time24);
    }

    //保存笔记
    Component insertButton = findComponentById(ResourceTable.Id_finish_image);
    insertButton.setClickedListener(this::saveNote);

}

c.查询数据

private NoteListItemInfo queryOne(DataAbilityPredicates predicates) {
    HiLog.info(LABEL, "%{public}s", "database query");
    String[] columns = new String[]{
            Const.DB_COLUMN_ID,
            Const.DB_COLUMN_TITLE, Const.DB_COLUMN_TIME,
            Const.DB_COLUMN_CATEGORY, Const.DB_COLUMN_CONTENT};
    try {
        ResultSet resultSet = dataAbilityHelper.query(
                Uri.parse(Const.BASE_URI + Const.DATA_PATH), columns, predicates);

        //无数据
        if (resultSet.getRowCount() == 0) {
            HiLog.info(LABEL, "%{public}s", "query:No result found");
            return null;
        }
        //
        resultSet.goToFirstRow();
        //根据列索引获取列值
        String noteId = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_ID));
        String noteTitle = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_TITLE));
        String noteTime = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_TIME));
        String noteCategory = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_CATEGORY));
        String noteContent = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_CONTENT));
        Element image = ElementScatter.getInstance(getContext()).parse(ResourceTable.Graphic_icon_nodata);
        HiLog.info(LABEL, "%{public}s", "set  show:" + noteCategory);
        //
        return new NoteListItemInfo(noteId, noteTitle, noteContent, noteTime, noteCategory, image);

    } catch (DataAbilityRemoteException | IllegalStateException exception) {
        HiLog.error(LABEL, "%{public}s", "query: dataRemote exception|illegalStateException");
    }
    return null;
}

实践中遇到的小知识点记录一下

1. 如何监听 TextField 文本变更事件

/**
 * 监听TextFiled 文本变化
 */
private void initSearchBtnEvent(AbilitySlice slice) {
    TextField searchTF = (TextField) findComponentById(ResourceTable.Id_tf_note_search);
    //添加文本观察器 TextObserver 以检测文本是否发生更改。
    searchTF.addTextObserver(new Text.TextObserver() {
        @Override
        public void onTextUpdated(String s, int i, int i1, int i2) {
            HiLog.info(LABEL, "addTextObserver 按键事件触发.....");
            //筛选数据
            initLists(slice);
        }
    });
}

2. ListContainer 组件添加点击事件

在 Provider 中 getComponent添加,在初始化Provider时传递AbilitySlice对象过来

public ListItemProvider(List<ItemInfo> itemList, AbilityContext context,AbilitySlice slice) {
    this.itemList = itemList;
    this.context = context;
    this.typeFactory = new ListTypeFactory();
    this.slice=slice;
}
@Override
public Component getComponent(int index, Component component, ComponentContainer componentContainer) {
    Component itemComponent = component;
    ViewHolder viewHolder;
    if (itemComponent == null) {
        itemComponent = LayoutScatter.getInstance(componentContainer.getContext())
                .parse(getItemComponentType(index), componentContainer, false);

    }
    viewHolder = typeFactory.getViewHolder(getItemComponentType(index), itemComponent);
    viewHolder.setUpComponent(getItem(index), context);


    //设置点击事件
    itemComponent.setClickedListener(component1 -> {
        //获取noteId
        String noteId="";
        if(getItem(index) instanceof NoteListItemInfo){
            //HiLog.debug(LABEL, "%{public}s", "ItemInfo instanceof SingleButtonDoubleLineListItemInfo");
            noteId=((NoteListItemInfo)getItem(index)).getNoteId();
        }
        HiLog.debug(LABEL, "%{public}s", "noteId:" + noteId);
        //1.携带笔记ID参数,跳转到AddNoteAbilitySlice
        Intent intent = new Intent();
        if(noteId!=null){
            //保存要传递的参数
            intent.setParam("Id", noteId);
            Operation operation = new Intent.OperationBuilder()
                    .withDeviceId("")
                    .withBundleName("com.buty.samples")
                    .withAbilityName(AddNoteAbility.class).build();
            intent.setOperation(operation);

            slice.startAbility(intent);
        }else {
            HiLog.error(LABEL, "%{public}s", "noteId is null");
        }
    });

    return itemComponent;
}

效果展示

HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙开发者社区

完整代码

附件直接下载

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
DataAbility.zip 1.82M 146次下载
已于2021-7-26 18:00:39修改
7
收藏 5
回复
举报
4条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

感谢楼主分享,分享的十分全面。

回复
2021-7-26 18:10:47
Der_带鱼
Der_带鱼

直接插眼!嘿嘿

回复
2021-7-26 19:27:49
Anzia
Anzia

搞的真快,这部分我学的很不扎实……

回复
2021-7-26 20:40:24
Buty9147
Buty9147

感谢各位的支持!

回复
2021-7-27 10:22:16
回复
    相关推荐