6.存储、权限

页面级UI状态存储文档中心

通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage支持UIAbility实例内多个页面间状态共享

页面内数据共享

const data: Record<string, string> = {
'uname': '张三',
'age': '18'
}
const storage = new LocalStorage(data)
@Entry(storage)
@Component
struct LocalStorageCase {
@LocalStorageLink('uname')
message: string = '';

build() {
Column({ space: 20 }) {
Text(this.message)
Text('------------------------------')
StorageChild01()
Button('改变-父')
.onClick(() => {
this.message = '法外狂徒'
})
}.height('100%').width('100%')
}
}

@Component
struct StorageChild01 {
@LocalStorageLink('uname')
message: string = '';

build() {
Column({ space: 20 }) {
Text(this.message)
Button('改变-子')
.onClick(() => {
this.message = '法外狂徒1'
})
}
}
}

传递给指定页面

EntryAbility.ets 已经有了直接添加代码即可

onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
const data: Record<string, string> = {
'uname': '张三',
'age': '18'
}
const storage = new LocalStorage(data)
//只要是当前windowStage内的页面,都可以共享这份数据
windowStage.loadContent('pages/02/LocalStorageCase01', storage);
}
const storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct LocalStorageCase {
@LocalStorageLink('uname')
message: string = '';

build() {
Column({ space: 20 }) {
Text(this.message)
Text('------------------------------')
Button('改变-父')
.onClick(() => {
this.message = '法外狂徒'
})
Button('跳转-父')
.onClick(() => {
router.pushUrl({ url: 'pages/02/LocalStorageCase02' })
})
}.height('100%').width('100%')
}
}
const storage = LocalStorage.getShared()

@Entry(storage)
@Component
struct LocalStorageCase02 {
@LocalStorageLink('uname')
message: string = '';

build() {
Column({ space: 20 }) {
Text(this.message)
Button('改变-02页面')
.onClick(() => {
this.message = '老实巴交';
})
Button('返回01')
.onClick(() => {
router.back();
})
}.height('100%').width('100%')
}
}

应用全局的UI状态存储文档中心

取出的结果是否有UI更新的需求-UI修饰符

// 页面1,当这里变为false则页面2变为false
AppStorage.setOrCreate('isLoggedIn', false);

// 页面2
@StorageLink('isLoggedIn') isLoggedIn: boolean = true;

持久化存储UI状态文档中心

持久化存储选定的AppStorage属性,持久化和读回UI的能力都需要依赖AppStorage

持久化变量最好是小于2kb的数据,如果开发者需要存储大量的数据,建议使用数据库api

查看:/data/app/el2/100/base/com.xinzai.myapplication/haps/entry/files/persistent_storage

这里寻找
//只能在UI界面声明,否则无法持久化
AppStorage.setOrCreate('isLoggedIn', true); //需要设置的时候持久化
PersistentStorage.persistProp('名字', 初始值);
PersistentStorage.persistProp('userInfo', 'xinzai')		//定义

AppStorage.setOrCreate('userInfo', 'test') //存:如果没有setOrCreate,更新set
AppStorage.get('userInfo')

首选项数据持久化文档中心

:::info
首选项

  • 每一个key的value的长度最大为8kb
  • 创建首选项-仓库的概念-应用可以有N个仓库,一个仓库中可以有N个key

:::

封装的首选项类

import { preferences } from '@kit.ArkData';

/**
* 偏好设置管理类,封装对本地持久化存储(preferences)的增删改查操作
* 使用示例:
* const prefs = new PreferenceManager(store, "user_settings");
* await prefs.setValue("token", "abc123");
*/
export class PreferenceManager {
private store: preferences.Preferences; // 实际的存储实例
private name: string; // 存储仓库名称(用于删除整个仓库时识别)

/**
* 构造函数
* @param store 已初始化的 preferences 存储实例
* @param name 当前存储仓库名称(需唯一)
*/
constructor(store: preferences.Preferences, name: string) {
this.store = store;
this.name = name;
}

/**
* 存储键值对(同步写入内存 + 异步刷盘)
* @param key 键名
* @param value 值(字符串类型)
* @returns Promise<void> 刷盘完成后 resolve
*/
async setValue(key: string, value: string): Promise<void> {
this.store.putSync(key, value); // 同步写入内存
await this.store.flush(); // 异步持久化到磁盘
}

/**
* 读取键对应的值
* @param key 键名
* @param defaultValue 可选默认值(未找到时返回)
* @returns 存储的值或默认值(字符串)
*/
getValue(key: string, defaultValue: string = ""): string {
return this.store.getSync(key, defaultValue) as string;
}

/**
* 删除指定键值对(同步删除 + 异步刷盘)
* @param key 键名
* @returns Promise<void> 刷盘完成后 resolve
*/
async deleteValue(key: string): Promise<void> {
this.store.deleteSync(key); // 同步删除
await this.store.flush(); // 异步持久化
}

/**
* 删除整个存储仓库(危险操作!)
* @returns Promise<void> 删除完成后 resolve
*/
async deleteStorage(): Promise<void> {
await preferences.deletePreferences(null, { name: this.name });
}
}

/**
* PreferenceManager 的工厂类(单例模式),用于集中管理多个存储仓库
* 使用示例:
* PreferenceManagerFactory.getInstance().init(context);
* const prefs = factory.getPreference("app_config");
*/
export class PreferenceManagerFactory {
private static instance: PreferenceManagerFactory; // 单例实例
private context: Context | null = null; // 应用上下文(必须初始化)
private stores: Map<string, PreferenceManager> = new Map(); // 存储多个仓库实例

private constructor() {} // 禁止外部 new 实例

/**
* 获取工厂单例
* @returns 全局唯一的 PreferenceManagerFactory 实例
*/
public static getInstance(): PreferenceManagerFactory {
if (!PreferenceManagerFactory.instance) {
PreferenceManagerFactory.instance = new PreferenceManagerFactory();
}
return PreferenceManagerFactory.instance;
}

/**
* 初始化工厂(必须在获取实例前调用)
* @param context 应用上下文
*/
public init(context: Context): void {
this.context = context;
}

/**
* 获取指定名称的存储管理器(如果不存在则创建)
* @param name 存储仓库名称
* @throws 未初始化 context 时抛出错误
* @returns PreferenceManager 实例
*/
public getPreference(name: string): PreferenceManager {
if (!this.context) {
throw new Error("PreferenceManagerFactory not initialized. Call init(context) first.");
}

// 缓存检查:避免重复创建同名仓库
if (!this.stores.has(name)) {
const store = preferences.getPreferencesSync(this.context, { name });
this.stores.set(name, new PreferenceManager(store, name));
}

return this.stores.get(name)!; // 非空断言(因为必定存在)
}
}

获取上下文 context

// ✅ 初始化首选项
PreferenceManagerFactory.getInstance().init(this.context)

使用

const userPref = PreferenceManagerFactory.getInstance().getPreference('userInfo')
await userPref.setValue('userid', res.data.data.id) //设置值
userPref.getValue('token') //获取

关系型数据库数据持久化

封装工具类,注意每次都需要调用创建数据库

import { BusinessError } from '@kit.BasicServicesKit';
import { relationalStore } from '@kit.ArkData';

type DBRow = Record<string, string | number | null>;

// ArkTS 中避免联合类型,使用明确类型
type QueryOperator =
| 'equalTo' // 等于,常用于精确匹配,例如 NAME = '张三'
| 'like' // 模糊匹配,常用于字符串搜索,例如 NAME LIKE '%张%'
| 'greaterThan' // 大于,例如 AGE > 18
| 'lessThan' // 小于,例如 AGE < 60
| 'greaterThanOrEqualTo' // 大于等于,例如 AGE >= 30
| 'lessThanOrEqualTo'; // 小于等于,例如 AGE <= 50

/**
* 查询条件结构定义
*/
interface QueryCondition {
field: string; // 查询字段,如 'NAME'、'AGE'
operator?: QueryOperator; // 查询操作符,默认为 'equalTo'
value: string | number; // 查询值(ArkTS 中不建议写联合类型,但此处使用时需确保类型一致)
}

class DBUtil {
private rdbStore: relationalStore.RdbStore | null = null;

/**
* 初始化数据库,仅建立连接,不建表,调用时可能需要配置一个变量防止每次都创建
* @param context 应用上下文
* @param storeConfig 数据库配置项
* @param onSuccess 初始化成功后的回调,方便顺便建表
*/
initDB(context: Context, storeConfig: relationalStore.StoreConfig, onSuccess?: () => void): void {
relationalStore.getRdbStore(context, storeConfig, (err, store) => {
if (err) {
console.error(`[RdbStore_getRdbStore] failed. Code: ${err.code}, message: ${err.message}`);
return;
}
console.log(`[RdbStore_getRdbStore] success.`);
this.rdbStore = store;
if (onSuccess) {
onSuccess();
}
});
}

/**
* 创建表(需先调用 initDB 后再使用)
* @param createTableSQL 建表 SQL 语句
*/
createTable(createTableSQL: string): void {
if (!this.rdbStore) {
console.error('RdbStore is not initialized. Please call initDB first.');
return;
}

this.rdbStore.executeSql(createTableSQL, [], (err) => {
if (err) {
console.error(`[RdbStore_executeSql] create table failed. Code: ${err.code}, message: ${err.message}`);
return;
}
console.log(`[RdbStore_executeSql] create table success.`);
});
}

/**
* 插入数据
* @param tableName 表名
* @param value 插入的键值对(ValuesBucket)
* @returns 插入的行 ID,失败返回 -1
*/
async insertData(tableName: string, value: relationalStore.ValuesBucket): Promise<number> {
if (this.rdbStore !== null) {
try {
console.info(`[RdbStore_insert] 尝试插入表 ${tableName} 数据: ${JSON.stringify(value)}`);
const rowId = await this.rdbStore.insert(tableName, value);

if (rowId === -1) {
console.error(`[RdbStore_insert] 插入失败(未抛异常),rowId = -1,可能存在字段类型错误或约束冲突`);
}

return rowId;
} catch (e) {
const err = e as BusinessError;
console.error(`[RdbStore_insert] ${tableName} failed. Code: ${err.code}, message: ${err.message}`);
}
} else {
console.error(`[RdbStore_insert] rdbStore 未初始化`);
}
return -1;
}

/**
* 通用查询方法(支持多字段条件)
* @param column 查询字段列,如 ['ID', 'NAME']
* @param tableName 表名
* @param conditions 可选条件数组,如 [{ field: 'NAME', operator: 'equalTo', value: '张三' }]
* @returns 返回原始数据对象数组
*/
async queryDB(
column: Array<string>,
tableName: string,
conditions?: Array<QueryCondition>
): Promise<Array<DBRow>> {
const predicates = new relationalStore.RdbPredicates(tableName);

// 构造查询条件
if (conditions) {
for (const condition of conditions) {
const field: string = condition.field;
const operator: QueryOperator = condition.operator ?? 'equalTo';
const value: string | number = condition.value;

// 用 switch 方式判断操作符,ArkTS 不支持动态函数调用
switch (operator) {
case 'equalTo':
predicates.equalTo(field, value);
break;
case 'like':
if (typeof value === 'string') {
predicates.like(field, value);
} else {
console.warn(`[queryDB] 'like' operator requires string value, got: ${typeof value}`);
}
break;
case 'greaterThan':
predicates.greaterThan(field, value);
break;
case 'lessThan':
predicates.lessThan(field, value);
break;
case 'greaterThanOrEqualTo':
predicates.greaterThanOrEqualTo(field, value);
break;
case 'lessThanOrEqualTo':
predicates.lessThanOrEqualTo(field, value);
break;
default:
console.warn(`[queryDB] Unsupported operator: ${operator}`);
}
}
}

const resultList: Array<DBRow> = [];

// 查询执行
if (this.rdbStore !== null) {
try {
const resultSet = await this.rdbStore.query(predicates, column);
console.info(`[queryDB] start query from ${tableName}`);

while (resultSet.goToNextRow()) {
const row: DBRow = {};
for (const col of column) {
const index = resultSet.getColumnIndex(col);
try {
row[col] = resultSet.getString(index);
} catch {
try {
row[col] = resultSet.getDouble(index);
} catch {
try {
row[col] = resultSet.getLong(index);
} catch {
row[col] = null;
}
}
}
}
resultList.push(row);
}

resultSet.close();
} catch (e) {
const err = e as BusinessError;
console.error(`[RdbStore_query] ${tableName} failed. Code: ${err.code}, message: ${err.message}`);
}
}

return resultList;
}

/**
* 根据 ID 删除指定记录
* @param tableName 表名
* @param id 主键 ID
*/
deleteDataByID(tableName: string, id: number | string): void {
const predicates = new relationalStore.RdbPredicates(tableName).equalTo('ID', id);
if (this.rdbStore !== null) {
this.rdbStore.delete(predicates, (err, rows) => {
if (err) {
console.error(`Delete failed. Code: ${err.code}, message: ${err.message}`);
return;
}
console.info(`Deleted rows: ${rows}`);
});
}
}

/**
* 根据 ID 更新记录
* @param tableName 表名
* @param id 主键 ID
* @param value 要更新的字段值(ValuesBucket)
*/
async updateDataByID(tableName: string, id: string, value: relationalStore.ValuesBucket): Promise<void> {
const predicates = new relationalStore.RdbPredicates(tableName).equalTo('ID', id);

if (this.rdbStore !== null) {
try {
const rows = await this.rdbStore.update(value, predicates);
console.info(`[更新成功] 表: ${tableName}, ID: ${id}, 更新字段: ${JSON.stringify(value)}, 影响行数: ${rows}`);
} catch (err) {
console.error(`[更新失败] 表: ${tableName}, ID: ${id}, 错误码: ${err.code}, 错误信息: ${err.message}`);
}
} else {
console.warn(`[警告] rdbStore 尚未初始化,无法更新 表: ${tableName}`);
}
}

// 自动判断是否需要 VACUUM 的实现
async autoVacuumIfNeeded(): Promise<void> {
if (!this.rdbStore) {
console.warn('[DBUtil_autoVacuumIfNeeded] rdbStore not initialized.');
return;
}

try {
let pageCount = 0;
let freeCount = 0;

// 查询 page_count
const pageResultSet = await this.rdbStore.querySql('PRAGMA page_count;', []);
if (pageResultSet.goToFirstRow()) {
const index = pageResultSet.getColumnIndex('page_count');
pageCount = pageResultSet.getLong(index);
}
pageResultSet.close();

// 查询 freelist_count
const freeResultSet = await this.rdbStore.querySql('PRAGMA freelist_count;', []);
if (freeResultSet.goToFirstRow()) {
const index = freeResultSet.getColumnIndex('freelist_count');
freeCount = freeResultSet.getLong(index);
}
freeResultSet.close();

const freeRatio: number = pageCount > 0 ? freeCount / pageCount : 0;

console.info(`[DBUtil_autoVacuumIfNeeded] pageCount=${pageCount}, freeCount=${freeCount}, freeRatio=${freeRatio}`);

if (pageCount > 1000 && freeRatio > 0.3) {
console.warn('[DBUtil_autoVacuumIfNeeded] Triggering VACUUM...');
await new Promise<void>((resolve, reject) => {
this.rdbStore!.executeSql('VACUUM;', [], (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
console.info('[DBUtil_autoVacuumIfNeeded] VACUUM completed.');
} else {
console.info('[DBUtil_autoVacuumIfNeeded] No need to VACUUM.');
}
} catch (e) {
const err = e as BusinessError;
console.error(`[DBUtil_autoVacuumIfNeeded] Failed. Code: ${err.code}, message: ${err.message}`);
}
}
}

const dbUtil = new DBUtil();

export { dbUtil };
import { relationalStore } from '@kit.ArkData';
import { dbUtil } from './DBUtil';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Page {
@State idValue: string = ''
@State name: string = ''
@State cha: string = ''
@State insertId: number = -1
// 数据库配置项
STORE_CONFIG: relationalStore.StoreConfig = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S1,
encrypt: false,
customDir: 'customDir/subCustomDir',
isReadOnly: false
};
// 建表语句
SQL_CREATE_TABLE = `
-- 表名:EMPLOYEE
CREATE TABLE IF NOT EXISTS EMPLOYEE (
ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
NAME TEXT NOT NULL
);
`;

aboutToAppear() {
console.log('[Page] aboutToAppear');
// 进入页面时整理数据库,清除空行
dbUtil.autoVacuumIfNeeded()
.then(() => {
console.log('[Page] autoVacuumIfNeeded done');
})
.catch((err: BusinessError) => {
console.error('[Page] autoVacuumIfNeeded error:', err);
});
}

build() {
Column({ space: 20 }) {
TextInput({ text: $$this.idValue, placeholder: '请输入ID(修改/删除时用)' })
TextInput({ text: $$this.name, placeholder: '请输入姓名' })

Button('创建数据库')
.onClick(() => {
dbUtil.initDB(getContext(), this.STORE_CONFIG, this.SQL_CREATE_TABLE)
})

Button('插入数据')
.onClick(async () => {
const valueBucket: relationalStore.ValuesBucket = {
NAME: this.name // 不需要传 ID,数据库自增
};
const rowId = await dbUtil.insertData('EMPLOYEE', valueBucket);
if (rowId !== -1) {
this.insertId = rowId;
}
})

Text(`插入行 ID: ${this.insertId}`)

Button('查询所有数据')
.onClick(async () => {
const result = await dbUtil.queryDB(['ID', 'NAME'], 'EMPLOYEE');
// 查询名字为xinzai的人
const result1 = await dbUtil.queryDB(
['ID', 'NAME'], 'EMPLOYEE',
[{ field: "NAME", operator: 'equalTo', value: 'xinzai' }]
);
console.log('查询结果:', JSON.stringify(result));
console.log('单个ID查询:', JSON.stringify(result1));
})

Button('修改数据')
.onClick(() => {
const id = Number(this.idValue);
if (!isNaN(id)) {
const updateValue: relationalStore.ValuesBucket = {
NAME: this.name
};
dbUtil.updateDataByID('EMPLOYEE', id, updateValue);
} else {
console.error('请输入有效的数字 ID');
}
})

Button('删除数据')
.onClick(() => {
const id = Number(this.idValue);
if (!isNaN(id)) {
dbUtil.deleteDataByID('EMPLOYEE', id);
} else {
console.error('请输入有效的数字 ID');
}
})
}
.height('100%')
.width('100%')
}
}

应用上下文Context文档中心

应用的全局上下文

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
AppStorage.setOrCreate('context', this.context)
}
// 使用
const context = AppStorage.get<Context>('context')

组件上下文

this.getUIContext()

权限

在src\main\module.json5 配置,受限权限需要申请才可以获得文档中心

当工程签名之后,记得运行工程需要登录账号,否则可能权限会失效

系统授权文档中心

"requestPermissions":[
{
"name" : "ohos.permission.枚举值" //申请权限的名字
}
],

用户授权文档中心

用户授权不能只写权限需要的名字,需要完善原因等

路径:\src\main\resources\base\element\string.json

{
"string": [
{
"name": "reason_mai",
"value": "申请麦克风权限,用于录音"
}
]
}
"requestPermissions":[
{
"name": "ohos.permission.枚举值", //申请权限的名字
"reason":"$string:reason_mai", //申请声明,需要在string.json文件中配置
"usedScene": {
"abilities": ["EntryAbility"], //需要在哪个abilities中使用
"when": "always"
}
}
]

配置了还需要在 UI 中对应需要的位置发起申请,用户同意之后才有权限,下面是封装好的申请权限的函数

import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit'

async function checkPermission(permissionName: Permissions[], callback: () => void) {
//不要一进应用就申请权限
const manger = abilityAccessCtrl.createAtManager() //权限控制
//申请权限
await manger.requestPermissionsFromUser(getContext(), permissionName as Permissions[])

//拿到包信息
const bundleInfo =
bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
//获取tokenid
const tokenId = bundleInfo.appInfo.accessTokenId
//检测是否有权限
const status = manger.checkAccessTokenSync(tokenId, permissionName[0])
if (status === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) { //如果没有权限设跳转到设置对应应用权限页面
//打开应用设置
(getContext() as common.UIAbilityContext).startAbility({
//包名:com.huawei.hmos.settings
bundleName: "com.huawei.hmos.settings",
//ability名字
abilityName: "com.huawei.hmos.settings.MainAbility",
//跳转的页面
uri: "application_info_entry",
//参数
parameters: {
pushParams: bundleInfo.name
}
})
} else { //如果有权限触发回调函数
callback()
}
}

export default checkPermission
Button('请打开位置交流')
.onClick(async () => {
checkPermission(["ohos.permission.APPROXIMATELY_LOCATION", "ohos.permission.LOCATION"],
async () => {
const result = await geoLocationManager.getCurrentLocation()
promptAction.showToast({ message: JSON.stringify(result) })
})
})