rawfile文件内容保存到本地数据库
不知道各位在平时开发过程中是否有遇到过这样一个场景:在开发一个app的时候,有些数据是需要经过接口请求获取到的,但是用户在使用app的时候会遇到没有网络的情况,那么他这个时候就不能够获取到数据,页面显示也就是空白的,会导致用户体验感变差。那么,要如何处理这种情况呢?跟随本文一起学习一下吧......
关于rawfile
rawfile目录主要作用是:在鸿蒙操作系统中,rawfile目录一般用于存放原始资源文件。
一般在该目录中存放的本地文件类型为html类型或者txt类型。
如果需要在同一个模块中引用文件使用,可以通过$rawfile 函数直接引用这些文件的路径(相对于 rawfile 目录)。
例如:在rawfile目录中有一个index.html文件,使用web组件引用它:
Web({ src: $rawfile('index.html'), controller: this._controller })
但是这种通过$rawfile函数引用的方式,只可以引用文件,并不能获取到文件中的内容;例如html中的富文本还有txt中的文本。
如果需要获取到文件中的内容,需要使用到一个新的资源管理API——resourceManager,可以根据当前的配置提供获取应用资源信息读取。
获取rawfile文件内容
对于如何在rawfile文件内容,在resourceManager中有一个方法可以读取,getRawFileContentSync:用户获取resources/rawfile目录下对应的rawfile文件内容,使用同步形式返回。
注意:从API Version9之前使用resourceManager需要通过导入模块,在API Version9开始,在Stage模型中可以通过context获取到resourceManager对象的方式,直接调用其内部获取资源的接口。
通过getRawFileContentSync方法可以获取到文件内容,返回的是Uint8Array字节流类型数据:
getRawfile(name: string) {
const data = getContext(this).resourceManager.getRawFileContentSync(`${name}.txt`) //data是字节流类型数据
console.log(JSON.stringify(data))
return data
}
原本数据就只有4个文字:
打印出来的数据:
将内容写入沙箱
因为获取到的字节流类型数据,不能直接存储到数据库中使用,所以需要先写入沙箱经过转换后再从沙箱中获取内容。
fs文件管理
要将文件内容写入沙箱就需要用到文件管理这个API,该模块为基础模块操作API,提供基础文件操作能力,包括文件基本管理、文件目录管理、文件信息统计、文件流式读写等常用功能。
注意:在使用该模块对文件/目录进行操作之前,需要先获取其应用沙箱路径。
将本地rawfile目录中的文件数据存储到沙箱:
// 将本地rawfile目录中的文件数据存储到沙箱
getRawfile(name: string): string {
const data = getContext(this).resourceManager.getRawFileContentSync(`${name}.txt`) //data是字节流类型数据
// console.log(JSON.stringify(data))
const myBuffer: ArrayBufferLike = data.buffer
//获取上下文
const context = getContext(this)
//创建文件
const filePath = context.filesDir + `/${name}.txt`
// 检查文件是否存在
const res = fs.accessSync(filePath)
// 表示文件存在,由于文件存在如果再写入一份数据,长度不一致的话会导致错乱,需要先删除文件
if (res) {
fs.unlinkSync(filePath)
}
// 以可读可写模式打开,如果没有文件则会创建文件
const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
// 将字节流数据写入文件
fs.writeSync(file.fd, myBuffer)
// 读取文件内容,是字符串类型
const Str = fs.readTextSync(filePath)
// AlertDialog.show({ message: Str })
// 必须关闭文件,否则文件可能会一直执行,导致资源崩溃
fs.closeSync(file)
return Str
}
这里有两个需要注意的地方:第一就是在使用fs写入文件之前,需要判断该文件是否存在,否则会出现由于写入内容字节长度不一致导致的内容错乱。例如:
第一次写入内容:我是中国人,我自豪
输出结果:我是中国人,我自豪
第二次写入内容:我是帅哥
输出结果:我是帅哥人,我自豪
第三次输入内容:我是一个182cm大帅哥
输出结果:我是一个182cm大帅哥
这是因为在同一个文件中进行多次写入,并不会覆盖原有内容或者继续在后面增加内容,而是会通过字节形式从头开始一个字节一个字节对应覆盖,如果比上一次写入的内容短,那么就会出现第二次输出结果,如果比上一次写入的内容长,那么就会完全覆盖跟正常显示一样。
有人会疑惑为什么不每次写入文件都重新生成一个文件,在新的文件中写入;确实,这样的话在每个文件中每次写入的内容都可以完整显示,不会出现内容错乱,但是有个问题就是每次写入都生成一个文件会导致沙箱文件积多,占用缓存;并且在之后将文件内容导入数据库也会困难,因为太多文件名了,不好处理。
第二个需要注意的地方,在使用文件的时候,使用open打开过文件之后一定要记得使用close关闭文件,否则文件可能会一直执行,导致资源崩溃。博主在开发过程中不经意间出现过一次,找了很久,控制台崩溃了一直打印不出东西,最后重启ide才发现的。o(╥﹏╥)o
数据库管理
关系型数据库
关系型数据库(Relational Database,RDB)是一种基于关系模型来管理数据的数据库。关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。
注意:为保证插入并读取数据成功,建议一条数据不要超过2M。超出该大小,插入成功,读取失败。
该模块提供的常用功能:
- RdbPredicates:数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。
- RdbStore:提供管理关系数据库(RDB)方法的接口。
- ResultSet:提供用户调用关系型数据库查询接口之后返回的结果集合。
获取管理数据库的对象
// 获取管理数据库的对象
async getStoreInstance() {
try {
// 获取 store
const store = await relationalStore.getRdbStore(getContext(), {
name: cityData + '.db', // 数据库文件名,如果文件不存在会自动创建,已存在则不创建
securityLevel: relationalStore.SecurityLevel.S4 // 安全等级
})
// 执行创建表的语句(如果不存在才创建表,已存在则不创建)
store.executeSql(`CREATE TABLE IF NOT EXISTS cityData (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
filePath TEXT,
date_added INTEGER,
content TEXT
)`)
// 返回 store
return store
} catch (err) {
return Promise.reject('initDB error')
}
}
对数据库的操作一般都只有新增、更新、删除、查询、查询数量等等......
但是本场景开发只用到了新增、更新、查询、删除,更多方法可以参考RdbStore。
节省时间,我就将从rawfile获取内容写入沙箱,到获取数据库实例,以及对数据库的增删改查等操作封装在一起:
import { relationalStore, ValuesBucket } from '@kit.ArkData'
import fs from '@ohos.file.fs';
// 文件字段
export interface PageDBInfo extends ValuesBucket {
id: number | null // 新增时 id 为 null ,自动生成自增 id
name: string //文件名
filePath: string //沙箱文件路径
date_added: number //添加日期
content: string //内容,也就是页面数据
}
export class fileDB {
private store: relationalStore.RdbStore | null = null
private tableName = 'cityData' //表名
// 创建数据库的语句
private sqlCreate = `CREATE TABLE IF NOT EXISTS ${this.tableName} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
filePath TEXT,
date_added INTEGER,
content TEXT
)`
// 将本地rawfile目录中的文件数据存储到沙箱
getRawfile(name: string): string {
const data = getContext(this).resourceManager.getRawFileContentSync(`${name}.txt`) //data是字节流类型数据
// console.log(JSON.stringify(data))
const myBuffer: ArrayBufferLike = data.buffer
//获取上下文
const context = getContext(this)
//创建目录
// const fileDIr = context.filesDir + '/city'
// fs.mkdirSync(fileDIr)
//创建文件
const filePath = context.filesDir + `/${name}.txt`
// 检查文件是否存在
const res = fs.accessSync(filePath)
// 表示文件存在,由于文件存在如果再写入一份数据,长度不一致的话会导致错乱,需要先删除文件
if (res) {
fs.unlinkSync(filePath)
}
// 以可读可写模式打开,如果没有文件则会创建文件
const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
// 将字节流数据写入文件
fs.writeSync(file.fd, myBuffer)
// 读取文件内容,是字符串类型
const Str = fs.readTextSync(filePath)
// AlertDialog.show({ message: Str })
// 必须关闭文件,否则文件可能会一直执行,导致资源崩溃
fs.closeSync(file)
return Str
}
// 获取管理数据库的对象
async getStoreInstance() {
try {
// 如果 store 已存在,直接返回 store
if (this.store) {
return this.store
}
// 获取 store
const store = await relationalStore.getRdbStore(getContext(), {
name: this.tableName + '.db', // 数据库文件名,如果文件不存在会自动创建,已存在则不创建
securityLevel: relationalStore.SecurityLevel.S4 // 安全等级
})
// 执行创建表的语句(如果不存在才创建表,已存在则不创建)
store.executeSql(this.sqlCreate)
// 存储 store 用于下次快速获取
this.store = store
// 返回 store
return store
} catch (err) {
return Promise.reject('initDB error')
}
}
// 新增
async insert(value: PageDBInfo) {
// 获取数据库对象
const store = await this.getStoreInstance()
// 核心代码 insert,返回行的id
const rowId = await store.insert(this.tableName, value)
return rowId > -1 ? Promise.resolve(rowId) : Promise.reject('insert error')
}
// 更新
async update(item: PageDBInfo) {
// 如果该行数据不存在id,返回错误
if (!item.id) {
return Promise.reject('id error')
}
// 获取数据库对象
const store = await this.getStoreInstance()
// 获取数据库的谓词
const predicates = new relationalStore.RdbPredicates(this.tableName)
// 匹配数据表中name列中值为item.name的字段数据
predicates.equalTo('name', item.name)
// 核心代码 update,返回受影响的行数
const rowCount = await store.update(item, predicates)
return rowCount ? Promise.resolve(rowCount) : Promise.reject('update error')
}
// 删除
async delete(name: string) {
// 获取数据库对象
const store = await this.getStoreInstance()
// 获取数据库的谓词
const predicates = new relationalStore.RdbPredicates(this.tableName)
predicates.equalTo('name', name)
// 核心代码 delete,返回删除的行数
const rowCount = await store.delete(predicates)
return rowCount ? Promise.resolve(rowCount) : Promise.reject('delete error')
}
// 查询
async query(name: string) {
try {
const store = await this.getStoreInstance()
const predicates = new relationalStore.RdbPredicates(this.tableName)
// 根据 id 倒序排列(大到小)
predicates.orderByDesc('id')
// 添加name到条件中
predicates.equalTo('name', name)
// 核心代码 query
const resultSet = await store.query(predicates)
// resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从0开始。
let content = ''
while (resultSet.goToNextRow()) {
// 获取到内容,为字符串类型
content = resultSet.getString(resultSet.getColumnIndex('content'))
}
// 关闭结果集,释放内存空间,若不释放可能会引起fd泄露与内存泄露
resultSet.close()
// 返回结果
return content
} catch (err) {
return '查询失败'
}
}
}
export const FileDB = new fileDB()
需要注意的是:在查询方法中query方法返回的resultSet结果集是一个数据集合的游标,默认指向第-1个记录,有效的数据需要从0开始。还有最后需要通过结果集的close方法关闭结果集,释放内存空间,若不释放肯呢个会引起fd泄露以及内存泄露。
将rawfile文件内容保存到数据库
rawfile目录中文件内容:
写入到沙箱中的内容:
沙箱文件中内容一致,表示写入成功:
将数据插入到数据库中,显示内容:
查询数据库中该数据信息,控制台已经弹窗可以打印出内容:
点击删除之后返回删除的行数为1,再点击查询内容为空白,没有输出:
将rawfile文件中内容保存到本地数据库的方法基本就叙述完了,感谢查阅。
如果觉得对您有帮助,烦请点赞收藏哦,ღ( ´・ᴗ・` )比心