HarmonyOS Sample 之 DataAbility RDB数据库操作 原创 精华
@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
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对象下,
第2处是abilities对象下,
permissions表示其他应用的能力调用当前能力所需的权限。
默认情况下隐藏"visible"字段(值为false),表示仅本应用可访问该Data,开发人员可根据需求修改permissions、visible值、uri等内容。当外部应用需要访问/控制此数据库字段时,在该Data Ability配置中增加"visible": true,并在外面应用的配置文件config.json中申请permissions权限。
第3处是reqPermissions对象下,
说明:如果待访问的Data Ability是由本应用创建,则可以不声明该权限。
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;
}
效果展示
完整代码
附件直接下载
感谢楼主分享,分享的十分全面。
直接插眼!嘿嘿
搞的真快,这部分我学的很不扎实……
感谢各位的支持!