系列文章 :鸿蒙NEXT开发实战系列 -- 第4篇(共5篇) 适合人群 :有ArkUI基础,想掌握数据存储和网络请求的开发者 开发环境 :DevEco Studio 5.0.5+ | HarmonyOS NEXT (API 14) 阅读时长:约25分钟
一、引言:数据持久化与网络请求的重要性
在移动应用开发中,数据是应用的血液。一个优秀的应用不仅要能展示数据,更要能高效地存储数据和获取网络数据。想象一下:如果每次打开应用都要重新登录、每次查看历史记录都要等待网络加载、每次操作都要担心数据丢失------这样的应用体验将是灾难性的。
HarmonyOS NEXT提供了完整的数据管理解决方案:
-
Preferences:轻量级键值存储,适合保存用户设置、配置信息
-
RDB关系型数据库:结构化数据存储,适合保存复杂业务数据
-
HTTP网络请求:与服务器交互,获取和提交数据
本文将系统讲解这三种数据管理方式,并通过完整的实战项目,帮助你构建健壮的数据层架构。无论你是开发工具类应用、社交App还是企业管理软件,这些知识都必不可少。
二、Preferences轻量级存储教程
2.1 什么是Preferences
Preferences是HarmonyOS提供的轻量级键值存储方案,类似于Android的SharedPreferences和iOS的UserDefaults。它适用于:
-
用户设置(主题、语言、通知开关)
-
应用配置(服务器地址、版本号)
-
简单状态标记(是否首次启动、是否已登录)
特点:
-
数据以键值对形式存储
-
支持的数据类型:
number、string、boolean、Array等 -
数据存储在内存中,读写速度快
-
支持持久化到文件系统
2.2 基本用法
2.2.1 获取Preferences实例
import { preferences } from '@kit.ArkData';
// 获取Preferences实例
const store = await preferences.getPreferences(context, 'myStore');
2.2.2 写入数据
// 写入不同类型的数据
await store.put('userName', '张三');
await store.put('userAge', 25);
await store.put('isVip', true);
// 持久化到文件系统
await store.flush();
2.2.3 读取数据
// 读取数据,需要提供默认值
const userName = await store.get('userName', '');
const userAge = await store.get('userAge', 0);
const isVip = await store.get('isVip', false);
2.2.4 删除数据
// 删除单个键
await store.delete('userName');
// 清空所有数据
await store.clear();
// 持久化更改
await store.flush();
2.3 实战:存储用户设置
让我们实现一个用户设置页面,保存用户的偏好设置:
// UserSettings.ets
import { preferences } from '@kit.ArkData';
import { Context } from '@kit.AbilityKit';
// 用户设置接口
interface UserSettings {
theme: 'light' | 'dark';
fontSize: number;
notificationsEnabled: boolean;
language: string;
}
// 默认设置
const defaultSettings: UserSettings = {
theme: 'light',
fontSize: 16,
notificationsEnabled: true,
language: 'zh-CN'
};
// 设置管理器
class SettingsManager {
private store: preferences.Preferences | null = null;
private context: Context;
constructor(context: Context) {
this.context = context;
}
// 初始化
async init() {
this.store = await preferences.getPreferences(this.context, 'userSettings');
}
// 保存设置
async saveSettings(settings: UserSettings) {
if (!this.store) {
throw new Error('SettingsManager未初始化');
}
await this.store.put('settings', JSON.stringify(settings));
await this.store.flush();
console.info('设置已保存');
}
// 读取设置
async getSettings(): Promise<UserSettings> {
if (!this.store) {
throw new Error('SettingsManager未初始化');
}
const settingsStr = await this.store.get('settings', '');
if (settingsStr === '') {
return defaultSettings;
}
try {
return JSON.parse(settingsStr as string) as UserSettings;
} catch (e) {
console.error('解析设置失败:', e);
return defaultSettings;
}
}
// 重置设置
async resetSettings() {
await this.saveSettings(defaultSettings);
}
}
// 使用示例
@Component
struct SettingsPage {
@State settings: UserSettings = defaultSettings;
private settingsManager: SettingsManager | null = null;
async aboutToAppear() {
this.settingsManager = new SettingsManager(getContext(this));
await this.settingsManager.init();
this.settings = await this.settingsManager.getSettings();
}
build() {
Column() {
Text('用户设置')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 主题选择
Row() {
Text('主题模式')
.width(120)
Select([
{ value: '浅色模式' },
{ value: '深色模式' }
])
.selected(this.settings.theme === 'dark' ? 1 : 0)
.onSelect(async (index: number) => {
this.settings.theme = index === 0 ? 'light' : 'dark';
await this.settingsManager?.saveSettings(this.settings);
})
}
.width('100%')
.padding(10)
// 字体大小
Row() {
Text('字体大小')
.width(120)
Slider({
value: this.settings.fontSize,
min: 12,
max: 24,
step: 2
})
.onChange(async (value: number) => {
this.settings.fontSize = value;
await this.settingsManager?.saveSettings(this.settings);
})
Text(`${this.settings.fontSize}px`)
.margin({ left: 10 })
}
.width('100%')
.padding(10)
// 通知开关
Row() {
Text('通知提醒')
.width(120)
Toggle({ type: ToggleType.Switch, isOn: this.settings.notificationsEnabled })
.onChange(async (isOn: boolean) => {
this.settings.notificationsEnabled = isOn;
await this.settingsManager?.saveSettings(this.settings);
})
}
.width('100%')
.padding(10)
// 重置按钮
Button('重置设置')
.margin({ top: 20 })
.onClick(async () => {
await this.settingsManager?.resetSettings();
this.settings = defaultSettings;
})
}
.padding(20)
.width('100%')
}
}
2.4 踩坑记录
踩坑1:忘记调用flush()
问题 :写入数据后没有调用flush(),导致数据没有持久化,应用重启后数据丢失。
原因 :Preferences的数据默认存储在内存中,只有调用flush()才会写入文件系统。
解决方案 :每次修改数据后都调用flush(),或者封装一个自动flush的方法:
async putAndFlush(key: string, value: preferences.ValueType) {
await this.store?.put(key, value);
await this.store?.flush(); // 立即持久化
}
踩坑2:并发读写问题
问题:多个地方同时读写同一个Preferences文件,导致数据不一致。
解决方案:使用单例模式管理Preferences实例:
class PreferencesSingleton {
private static instances: Map<string, preferences.Preferences> = new Map();
static async getInstance(context: Context, name: string): Promise<preferences.Preferences> {
if (!this.instances.has(name)) {
const store = await preferences.getPreferences(context, name);
this.instances.set(name, store);
}
return this.instances.get(name)!;
}
}
踩坑3:存储大量数据
问题:将大量数据(如列表、图片)存储在Preferences中,导致性能问题。
原因:Preferences不适合存储大量数据,它的设计目的是轻量级配置存储。
解决方案:大数据应使用RDB或文件存储,Preferences只存储配置信息。
三、关系型数据库(RDB)使用教程
3.1 RDB简介
RDB(Relational Database)是HarmonyOS提供的关系型数据库,基于SQLite实现。它适用于:
-
结构化数据存储(用户信息、订单记录)
-
复杂查询(条件筛选、排序、分页)
-
数据关联(多表查询、事务处理)
特点:
-
支持标准SQL语法
-
支持事务处理
-
支持数据加密
-
支持数据库版本升级
3.2 创建数据库和表
3.2.1 获取RDB Store
import { relationalStore } from '@kit.ArkData';
const STORE_CONFIG: relationalStore.RdbStoreConfig = {
name: 'myApp.db',
securityLevel: relationalStore.SecurityLevel.S1
};
const store = await relationalStore.getRdbStore(context, STORE_CONFIG);
3.2.2 创建表
// 创建用户表
const createTableSql = `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT UNIQUE,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`;
await store.execute(createTableSql);
3.3 CRUD操作
3.3.1 插入数据
// 插入单条数据
const user: relationalStore.ValuesBucket = {
name: '张三',
age: 25,
email: 'zhangsan@example.com'
};
const rowId = await store.insert('users', user);
console.info(`插入成功,行ID: ${rowId}`);
// 批量插入
const users: relationalStore.ValuesBucket[] = [
{ name: '李四', age: 30, email: 'lisi@example.com' },
{ name: '王五', age: 28, email: 'wangwu@example.com' }
];
for (const user of users) {
await store.insert('users', user);
}
3.3.2 查询数据
// 查询所有用户
const resultSet = await store.query('users', ['id', 'name', 'age', 'email']);
// 遍历结果
while (resultSet.goToNextRow()) {
const id = resultSet.getLong(resultSet.getColumnIndex('id'));
const name = resultSet.getString(resultSet.getColumnIndex('name'));
const age = resultSet.getLong(resultSet.getColumnIndex('age'));
const email = resultSet.getString(resultSet.getColumnIndex('email'));
console.info(`用户: ${name}, 年龄: ${age}, 邮箱: ${email}`);
}
resultSet.close();
// 条件查询
const predicates = new relationalStore.RdbPredicates('users');
predicates.equalTo('age', 25);
predicates.orderByAsc('name');
const filteredResultSet = await store.query(predicates, ['id', 'name', 'age']);
3.3.3 更新数据
const updatePredicates = new relationalStore.RdbPredicates('users');
updatePredicates.equalTo('name', '张三');
const updateValues: relationalStore.ValuesBucket = {
age: 26,
email: 'zhangsan_new@example.com'
};
const changedRows = await store.update(updateValues, updatePredicates);
console.info(`更新了 ${changedRows} 行`);
3.3.4 删除数据
const deletePredicates = new relationalStore.RdbPredicates('users');
deletePredicates.equalTo('name', '王五');
const deletedRows = await store.delete(deletePredicates);
console.info(`删除了 ${deletedRows} 行`);
3.4 实战:用户信息管理
让我们实现一个完整的用户信息管理模块:
// UserManager.ets
import { relationalStore } from '@kit.ArkData';
import { Context } from '@kit.AbilityKit';
// 用户接口
interface User {
id?: number;
name: string;
age: number;
email: string;
phone?: string;
}
// 用户管理器
class UserManager {
private store: relationalStore.RdbStore | null = null;
private context: Context;
constructor(context: Context) {
this.context = context;
}
// 初始化数据库
async init() {
const config: relationalStore.RdbStoreConfig = {
name: 'userManager.db',
securityLevel: relationalStore.SecurityLevel.S1
};
this.store = await relationalStore.getRdbStore(this.context, config);
// 创建用户表
const createSql = `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT UNIQUE,
phone TEXT,
created_at TEXT DEFAULT (datetime('now','localtime')),
updated_at TEXT DEFAULT (datetime('now','localtime'))
)
`;
await this.store.execute(createSql);
console.info('数据库初始化完成');
}
// 添加用户
async addUser(user: User): Promise<number> {
if (!this.store) throw new Error('数据库未初始化');
const values: relationalStore.ValuesBucket = {
name: user.name,
age: user.age,
email: user.email,
phone: user.phone || ''
};
const rowId = await this.store.insert('users', values);
console.info(`添加用户成功,ID: ${rowId}`);
return rowId;
}
// 获取所有用户
async getAllUsers(): Promise<User[]> {
if (!this.store) throw new Error('数据库未初始化');
const resultSet = await this.store.query('users',
['id', 'name', 'age', 'email', 'phone']);
const users: User[] = [];
while (resultSet.goToNextRow()) {
users.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
age: resultSet.getLong(resultSet.getColumnIndex('age')),
email: resultSet.getString(resultSet.getColumnIndex('email')),
phone: resultSet.getString(resultSet.getColumnIndex('phone'))
});
}
resultSet.close();
return users;
}
// 根据ID查询用户
async getUserById(id: number): Promise<User | null> {
if (!this.store) throw new Error('数据库未初始化');
const predicates = new relationalStore.RdbPredicates('users');
predicates.equalTo('id', id);
const resultSet = await this.store.query(predicates,
['id', 'name', 'age', 'email', 'phone']);
let user: User | null = null;
if (resultSet.goToNextRow()) {
user = {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
age: resultSet.getLong(resultSet.getColumnIndex('age')),
email: resultSet.getString(resultSet.getColumnIndex('email')),
phone: resultSet.getString(resultSet.getColumnIndex('phone'))
};
}
resultSet.close();
return user;
}
// 更新用户
async updateUser(id: number, updates: Partial<User>): Promise<boolean> {
if (!this.store) throw new Error('数据库未初始化');
const predicates = new relationalStore.RdbPredicates('users');
predicates.equalTo('id', id);
const values: relationalStore.ValuesBucket = {
...updates,
updated_at: new Date().toISOString()
};
const changedRows = await this.store.update(values, predicates);
return changedRows > 0;
}
// 删除用户
async deleteUser(id: number): Promise<boolean> {
if (!this.store) throw new Error('数据库未初始化');
const predicates = new relationalStore.RdbPredicates('users');
predicates.equalTo('id', id);
const deletedRows = await this.store.delete(predicates);
return deletedRows > 0;
}
// 搜索用户
async searchUsers(keyword: string): Promise<User[]> {
if (!this.store) throw new Error('数据库未初始化');
const predicates = new relationalStore.RdbPredicates('users');
predicates.like('name', `%${keyword}%`);
const resultSet = await this.store.query(predicates,
['id', 'name', 'age', 'email', 'phone']);
const users: User[] = [];
while (resultSet.goToNextRow()) {
users.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
age: resultSet.getLong(resultSet.getColumnIndex('age')),
email: resultSet.getString(resultSet.getColumnIndex('email')),
phone: resultSet.getString(resultSet.getColumnIndex('phone'))
});
}
resultSet.close();
return users;
}
// 分页查询
async getUsersByPage(page: number, pageSize: number): Promise<User[]> {
if (!this.store) throw new Error('数据库未初始化');
const offset = (page - 1) * pageSize;
const sql = `SELECT * FROM users ORDER BY id DESC LIMIT ${pageSize} OFFSET ${offset}`;
const resultSet = await this.store.querySql(sql);
const users: User[] = [];
while (resultSet.goToNextRow()) {
users.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
age: resultSet.getLong(resultSet.getColumnIndex('age')),
email: resultSet.getString(resultSet.getColumnIndex('email')),
phone: resultSet.getString(resultSet.getColumnIndex('phone'))
});
}
resultSet.close();
return users;
}
}
// 使用示例
@Component
struct UserListPage {
@State users: User[] = [];
@State searchKeyword: string = '';
private userManager: UserManager | null = null;
async aboutToAppear() {
this.userManager = new UserManager(getContext(this));
await this.userManager.init();
await this.loadUsers();
}
async loadUsers() {
if (this.searchKeyword) {
this.users = await this.userManager!.searchUsers(this.searchKeyword);
} else {
this.users = await this.userManager!.getAllUsers();
}
}
build() {
Column() {
// 搜索栏
Row() {
TextInput({ placeholder: '搜索用户...', text: this.searchKeyword })
.onChange((value: string) => {
this.searchKeyword = value;
})
.layoutWeight(1)
Button('搜索')
.onClick(() => this.loadUsers())
}
.padding(10)
// 用户列表
List({ space: 10 }) {
ForEach(this.users, (user: User) => {
ListItem() {
Row() {
Column() {
Text(user.name)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`年龄: ${user.age} | 邮箱: ${user.email}`)
.fontSize(14)
.fontColor('#666')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Button('删除')
.type(ButtonType.Circle)
.onClick(async () => {
await this.userManager?.deleteUser(user.id!);
await this.loadUsers();
})
}
.padding(15)
.backgroundColor('#fff')
.borderRadius(8)
.shadow({ radius: 4, color: '#ddd', offsetX: 0, offsetY: 2 })
}
})
}
.layoutWeight(1)
.padding(10)
// 添加用户按钮
Button('添加测试用户')
.margin(10)
.onClick(async () => {
const testUser: User = {
name: `用户${Date.now()}`,
age: Math.floor(Math.random() * 50) + 18,
email: `user${Date.now()}@example.com`
};
await this.userManager?.addUser(testUser);
await this.loadUsers();
})
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
}
3.5 踩坑记录
踩坑1:忘记关闭ResultSet
问题:查询后没有关闭ResultSet,导致内存泄漏。
解决方案:使用try-finally确保ResultSet被关闭:
async getUsers(): Promise<User[]> {
const resultSet = await this.store!.query('users', ['id', 'name']);
try {
const users: User[] = [];
while (resultSet.goToNextRow()) {
// 处理数据
}
return users;
} finally {
resultSet.close(); // 确保关闭
}
}
踩坑2:主键冲突
问题:插入数据时遇到主键冲突错误。
原因:设置了AUTOINCREMENT的主键,但手动指定了已存在的ID。
解决方案:让数据库自动生成主键,或者使用冲突解决策略:
// 方法1:不指定ID
const values: relationalStore.ValuesBucket = {
name: '张三',
age: 25
};
// 方法2:使用INSERT OR REPLACE
const sql = `INSERT OR REPLACE INTO users (id, name, age) VALUES (1, '张三', 25)`;
await this.store!.execute(sql);
踩坑3:数据库升级问题
问题:应用更新后,数据库表结构变化,导致崩溃。
解决方案:实现数据库版本升级:
class DatabaseUpgrade {
static async upgrade(store: relationalStore.RdbStore, oldVersion: number, newVersion: number) {
if (oldVersion < 2) {
// 版本1到版本2:添加phone字段
await store.execute(`ALTER TABLE users ADD COLUMN phone TEXT`);
}
if (oldVersion < 3) {
// 版本2到版本3:添加address字段
await store.execute(`ALTER TABLE users ADD COLUMN address TEXT`);
}
}
}
四、HTTP网络请求封装教程
4.1 网络请求概述
HarmonyOS NEXT提供了@ohos.net.http模块进行网络请求。在实际开发中,我们需要封装一个通用的HTTP工具类,具备以下功能:
-
支持GET、POST、PUT、DELETE等请求方法
-
自动处理请求头和响应头
-
错误处理和重试机制
-
请求拦截器和响应拦截器
-
支持文件上传和下载
4.2 使用@ohos.net.http
4.2.1 基本用法
import { http } from '@kit.NetworkKit';
// 创建httpRequest对象
const httpRequest = http.createHttp();
// 发送请求
httpRequest.request(
'https://api.example.com/data',
{
method: http.RequestMethod.GET,
header: {
'Content-Type': 'application/json'
},
extraData: '',
connectTimeout: 10000,
readTimeout: 10000
},
(err, data) => {
if (!err) {
console.info(`状态码: ${data.responseCode}`);
console.info(`响应数据: ${data.result}`);
} else {
console.error(`请求失败: ${err.message}`);
}
httpRequest.destroy();
}
);
4.2.2 Promise方式
async function fetchData(): Promise<string> {
const httpRequest = http.createHttp();
try {
const response = await httpRequest.request(
'https://api.example.com/data',
{
method: http.RequestMethod.GET
}
);
if (response.responseCode === 200) {
return response.result as string;
} else {
throw new Error(`请求失败,状态码: ${response.responseCode}`);
}
} finally {
httpRequest.destroy();
}
}
4.3 封装通用请求工具
让我们封装一个功能完整的HTTP工具类:
// HttpClient.ets
import { http } from '@kit.NetworkKit';
// 请求配置
interface RequestConfig {
baseURL?: string;
timeout?: number;
headers?: Record<string, string>;
}
// 响应数据
interface ResponseData<T = any> {
code: number;
data: T;
message: string;
}
// 请求错误
class RequestError extends Error {
code: number;
constructor(message: string, code: number) {
super(message);
this.code = code;
}
}
// HTTP客户端类
class HttpClient {
private config: RequestConfig;
private interceptors: {
request: Array<(config: RequestConfig) => RequestConfig>;
response: Array<(data: any) => any>;
};
constructor(config: RequestConfig = {}) {
this.config = {
baseURL: config.baseURL || '',
timeout: config.timeout || 10000,
headers: config.headers || {}
};
this.interceptors = {
request: [],
response: []
};
}
// 添加请求拦截器
addRequestInterceptor(interceptor: (config: RequestConfig) => RequestConfig) {
this.interceptors.request.push(interceptor);
return this;
}
// 添加响应拦截器
addResponseInterceptor(interceptor: (data: any) => any) {
this.interceptors.response.push(interceptor);
return this;
}
// 发送请求
async request<T = any>(
url: string,
method: http.RequestMethod,
data?: any,
headers?: Record<string, string>
): Promise<ResponseData<T>> {
let config = { ...this.config };
// 应用请求拦截器
for (const interceptor of this.interceptors.request) {
config = interceptor(config);
}
const httpRequest = http.createHttp();
try {
const fullUrl = config.baseURL + url;
const options: http.HttpRequestOptions = {
method: method,
header: {
...config.headers,
...headers
},
connectTimeout: config.timeout,
readTimeout: config.timeout,
extraData: data ? JSON.stringify(data) : undefined
};
const response = await httpRequest.request(fullUrl, options);
if (response.responseCode === 200) {
let result = JSON.parse(response.result as string) as ResponseData<T>;
// 应用响应拦截器
for (const interceptor of this.interceptors.response) {
result = interceptor(result);
}
return result;
} else {
throw new RequestError(
`请求失败,状态码: ${response.responseCode}`,
response.responseCode
);
}
} catch (error) {
if (error instanceof RequestError) {
throw error;
}
throw new RequestError(
`网络请求异常: ${(error as Error).message}`,
-1
);
} finally {
httpRequest.destroy();
}
}
// GET请求
async get<T = any>(url: string, headers?: Record<string, string>): Promise<ResponseData<T>> {
return this.request<T>(url, http.RequestMethod.GET, undefined, headers);
}
// POST请求
async post<T = any>(url: string, data?: any, headers?: Record<string, string>): Promise<ResponseData<T>> {
return this.request<T>(url, http.RequestMethod.POST, data, headers);
}
// PUT请求
async put<T = any>(url: string, data?: any, headers?: Record<string, string>): Promise<ResponseData<T>> {
return this.request<T>(url, http.RequestMethod.PUT, data, headers);
}
// DELETE请求
async delete<T = any>(url: string, headers?: Record<string, string>): Promise<ResponseData<T>> {
return this.request<T>(url, http.RequestMethod.DELETE, undefined, headers);
}
}
// 导出单例
export const httpClient = new HttpClient({
baseURL: 'https://api.example.com',
timeout: 15000
});
// 添加请求拦截器 - 添加token
httpClient.addRequestInterceptor((config) => {
const token = globalThis.token || '';
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
});
// 添加响应拦截器 - 处理业务错误
httpClient.addResponseInterceptor((data) => {
if (data.code !== 0) {
throw new RequestError(data.message, data.code);
}
return data;
});
4.4 实战:获取天气数据
让我们使用封装的HTTP客户端获取天气数据:
// WeatherService.ets
import { httpClient } from './HttpClient';
// 天气数据接口
interface WeatherData {
city: string;
temperature: number;
humidity: number;
weather: string;
wind: string;
forecast: Array<{
date: string;
high: number;
low: number;
weather: string;
}>;
}
// 天气服务
class WeatherService {
// 获取当前天气
static async getCurrentWeather(city: string): Promise<WeatherData> {
const response = await httpClient.get<WeatherData>(`/weather/current?city=${city}`);
return response.data;
}
// 获取天气预报
static async getForecast(city: string, days: number = 7): Promise<WeatherData[]> {
const response = await httpClient.get<WeatherData[]>(
`/weather/forecast?city=${city}&days=${days}`
);
return response.data;
}
}
// 使用示例
@Component
struct WeatherPage {
@State weather: WeatherData | null = null;
@State loading: boolean = false;
@State error: string = '';
async aboutToAppear() {
await this.loadWeather();
}
async loadWeather() {
this.loading = true;
this.error = '';
try {
this.weather = await WeatherService.getCurrentWeather('北京');
} catch (e) {
this.error = (e as Error).message;
} finally {
this.loading = false;
}
}
build() {
Column() {
if (this.loading) {
LoadingProgress()
.width(50)
.height(50)
} else if (this.error) {
Column() {
Text(this.error)
.fontSize(16)
.fontColor('#ff4d4f')
Button('重试')
.margin({ top: 10 })
.onClick(() => this.loadWeather())
}
} else if (this.weather) {
// 天气卡片
Column() {
Text(this.weather.city)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text(`${this.weather.temperature}°C`)
.fontSize(48)
.fontWeight(FontWeight.Bold)
.margin({ top: 10 })
Text(this.weather.weather)
.fontSize(18)
.margin({ top: 5 })
Row() {
Column() {
Text('湿度')
.fontSize(14)
.fontColor('#666')
Text(`${this.weather.humidity}%`)
.fontSize(18)
}
.layoutWeight(1)
Column() {
Text('风力')
.fontSize(14)
.fontColor('#666')
Text(this.weather.wind)
.fontSize(18)
}
.layoutWeight(1)
}
.margin({ top: 20 })
.width('100%')
}
.padding(20)
.backgroundColor('#e3f2fd')
.borderRadius(12)
.margin(10)
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
4.5 踩坑记录
踩坑1:网络权限问题
问题:请求失败,提示没有网络权限。
解决方案 :在module.json5中添加网络权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
踩坑2:HTTPS证书问题
问题:请求HTTPS接口时证书验证失败。
解决方案:在开发阶段可以跳过证书验证(生产环境不推荐):
// 开发环境配置
const options: http.HttpRequestOptions = {
method: http.RequestMethod.GET,
// 跳过证书验证(仅开发环境)
certificate: undefined
};
踩坑3:请求超时
问题:网络请求经常超时。
解决方案:
-
设置合理的超时时间
-
实现重试机制
-
添加超时重试逻辑:
async requestWithRetry<T>(
url: string,
method: http.RequestMethod,
data?: any,
maxRetries: number = 3
): Promise<ResponseData<T>> {
let lastError: Error | null = null;for (let i = 0; i < maxRetries; i++) {
try {
return await this.request<T>(url, method, data);
} catch (error) {
lastError = error as Error;
console.warn(请求失败,第${i + 1}次重试: ${lastError.message});// 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); }}
throw lastError;
}
五、数据缓存策略
5.1 缓存的重要性
合理的数据缓存策略可以:
-
提升用户体验:减少加载时间,实现秒开效果
-
降低服务器压力:减少重复请求
-
支持离线使用:在没有网络时仍能查看历史数据
5.2 多级缓存设计
我们设计一个三级缓存架构:
-
内存缓存(L1):最快,但容量有限,应用退出后丢失
-
Preferences缓存(L2):中等速度,适合配置数据
-
RDB缓存(L3):最慢,但容量大,支持复杂查询
5.3 实战:实现缓存管理器
// CacheManager.ets
import { preferences } from '@kit.ArkData';
import { relationalStore } from '@kit.ArkData';
import { Context } from '@kit.AbilityKit';
// 缓存项
interface CacheItem<T> {
data: T;
timestamp: number;
expireTime: number; // 过期时间(毫秒)
}
// 缓存配置
interface CacheConfig {
memoryMaxSize: number; // 内存缓存最大条数
defaultExpireTime: number; // 默认过期时间(毫秒)
}
// 缓存管理器
class CacheManager {
private static instance: CacheManager;
private memoryCache: Map<string, CacheItem<any>> = new Map();
private preferencesStore: preferences.Preferences | null = null;
private rdbStore: relationalStore.RdbStore | null = null;
private config: CacheConfig;
private context: Context;
private constructor(context: Context, config: CacheConfig) {
this.context = context;
this.config = config;
}
// 获取单例
static async getInstance(context: Context, config: CacheConfig): Promise<CacheManager> {
if (!CacheManager.instance) {
CacheManager.instance = new CacheManager(context, config);
await CacheManager.instance.init();
}
return CacheManager.instance;
}
// 初始化
private async init() {
// 初始化Preferences
this.preferencesStore = await preferences.getPreferences(
this.context,
'cacheStore'
);
// 初始化RDB
const rdbConfig: relationalStore.RdbStoreConfig = {
name: 'cache.db',
securityLevel: relationalStore.SecurityLevel.S1
};
this.rdbStore = await relationalStore.getRdbStore(this.context, rdbConfig);
// 创建缓存表
await this.rdbStore.execute(`
CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY,
value TEXT,
timestamp INTEGER,
expire_time INTEGER
)
`);
}
// 设置缓存
async set<T>(key: string, data: T, expireTime?: number): Promise<void> {
const item: CacheItem<T> = {
data,
timestamp: Date.now(),
expireTime: expireTime || this.config.defaultExpireTime
};
// L1: 内存缓存
this.memoryCache.set(key, item);
this.cleanMemoryCache();
// L2: Preferences缓存(只存小数据)
const dataStr = JSON.stringify(data);
if (dataStr.length < 1024) { // 小于1KB
await this.preferencesStore?.put(key, dataStr);
await this.preferencesStore?.flush();
}
// L3: RDB缓存
const values: relationalStore.ValuesBucket = {
key: key,
value: dataStr,
timestamp: item.timestamp,
expire_time: item.expireTime
};
const predicates = new relationalStore.RdbPredicates('cache');
predicates.equalTo('key', key);
const existing = await this.rdbStore?.query(predicates, ['key']);
if (existing && existing.rowCount > 0) {
await this.rdbStore?.update(values, predicates);
} else {
await this.rdbStore?.insert('cache', values);
}
existing?.close();
}
// 获取缓存
async get<T>(key: string): Promise<T | null> {
// L1: 优先从内存缓存获取
const memoryItem = this.memoryCache.get(key);
if (memoryItem && !this.isExpired(memoryItem)) {
return memoryItem.data as T;
}
// L2: 从Preferences获取
const prefData = await this.preferencesStore?.get(key, '');
if (prefData) {
try {
const data = JSON.parse(prefData as string) as T;
// 回写到内存缓存
this.memoryCache.set(key, {
data,
timestamp: Date.now(),
expireTime: this.config.defaultExpireTime
});
return data;
} catch (e) {
// 解析失败,继续从RDB获取
}
}
// L3: 从RDB获取
const predicates = new relationalStore.RdbPredicates('cache');
predicates.equalTo('key', key);
const resultSet = await this.rdbStore?.query(
predicates,
['value', 'timestamp', 'expire_time']
);
if (resultSet && resultSet.goToNextRow()) {
const value = resultSet.getString(resultSet.getColumnIndex('value'));
const timestamp = resultSet.getLong(resultSet.getColumnIndex('timestamp'));
const expireTime = resultSet.getLong(resultSet.getColumnIndex('expire_time'));
resultSet.close();
const item: CacheItem<T> = {
data: JSON.parse(value),
timestamp,
expireTime
};
if (!this.isExpired(item)) {
// 回写到上级缓存
this.memoryCache.set(key, item);
return item.data as T;
}
}
resultSet?.close();
return null;
}
// 删除缓存
async delete(key: string): Promise<void> {
// 从所有缓存级别删除
this.memoryCache.delete(key);
await this.preferencesStore?.delete(key);
const predicates = new relationalStore.RdbPredicates('cache');
predicates.equalTo('key', key);
await this.rdbStore?.delete(predicates);
}
// 清空所有缓存
async clear(): Promise<void> {
this.memoryCache.clear();
await this.preferencesStore?.clear();
await this.rdbStore?.execute('DELETE FROM cache');
}
// 检查是否过期
private isExpired(item: CacheItem<any>): boolean {
return Date.now() - item.timestamp > item.expireTime;
}
// 清理内存缓存
private cleanMemoryCache() {
if (this.memoryCache.size > this.config.memoryMaxSize) {
// 删除最早添加的项
const firstKey = this.memoryCache.keys().next().value;
if (firstKey) {
this.memoryCache.delete(firstKey);
}
}
}
}
// 使用示例
@Component
struct DataPage {
@State data: any = null;
@State loading: boolean = false;
private cacheManager: CacheManager | null = null;
async aboutToAppear() {
this.cacheManager = await CacheManager.getInstance(getContext(this), {
memoryMaxSize: 100,
defaultExpireTime: 5 * 60 * 1000 // 5分钟
});
await this.loadData();
}
async loadData() {
this.loading = true;
const cacheKey = 'user_list';
try {
// 先尝试从缓存获取
let data = await this.cacheManager!.get(cacheKey);
if (!data) {
// 缓存未命中,从网络获取
data = await this.fetchFromNetwork();
// 存入缓存
await this.cacheManager!.set(cacheKey, data, 10 * 60 * 1000); // 缓存10分钟
}
this.data = data;
} catch (e) {
console.error('加载数据失败:', (e as Error).message);
} finally {
this.loading = false;
}
}
async fetchFromNetwork() {
// 模拟网络请求
return new Promise(resolve => {
setTimeout(() => {
resolve({
users: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
});
}, 1000);
});
}
build() {
Column() {
if (this.loading) {
LoadingProgress()
.width(50)
.height(50)
} else if (this.data) {
Text(JSON.stringify(this.data, null, 2))
.fontSize(14)
.padding(10)
}
Button('刷新数据')
.margin({ top: 20 })
.onClick(async () => {
await this.cacheManager?.delete('user_list');
await this.loadData();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
六、完整项目实战
6.1 项目需求分析
让我们实现一个新闻阅读App的数据层,包含:
-
用户登录状态管理(Preferences)
-
新闻列表缓存(RDB + HTTP)
-
图片缓存(文件系统)
6.2 数据层架构设计
┌─────────────────────────────────────┐
│ 业务层 (UI) │
├─────────────────────────────────────┤
│ 数据层 (Data) │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 用户数据 │ │ 新闻数据 │ │
│ │ (Preferences)│ │ (RDB+HTTP) │ │
│ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────┤
│ 基础设施 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ HttpClient │ │ CacheManager│ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
6.3 核心代码实现
// NewsApp.ets
// 1. 用户数据管理
class UserDataManager {
private store: preferences.Preferences | null = null;
async init(context: Context) {
this.store = await preferences.getPreferences(context, 'userData');
}
// 保存登录信息
async saveLoginInfo(token: string, userId: string) {
await this.store?.put('token', token);
await this.store?.put('userId', userId);
await this.store?.put('loginTime', Date.now().toString());
await this.store?.flush();
}
// 获取token
async getToken(): Promise<string> {
return (await this.store?.get('token', '')) as string;
}
// 检查是否已登录
async isLoggedIn(): Promise<boolean> {
const token = await this.getToken();
return token !== '';
}
// 退出登录
async logout() {
await this.store?.clear();
await this.store?.flush();
}
}
// 2. 新闻数据管理
class NewsDataManager {
private rdbStore: relationalStore.RdbStore | null = null;
private httpClient: HttpClient;
constructor(httpClient: HttpClient) {
this.httpClient = httpClient;
}
async init(context: Context) {
const config: relationalStore.RdbStoreConfig = {
name: 'news.db',
securityLevel: relationalStore.SecurityLevel.S1
};
this.rdbStore = await relationalStore.getRdbStore(context, config);
// 创建新闻表
await this.rdbStore.execute(`
CREATE TABLE IF NOT EXISTS news (
id TEXT PRIMARY KEY,
title TEXT,
summary TEXT,
content TEXT,
image_url TEXT,
category TEXT,
publish_time TEXT,
cached_at INTEGER
)
`);
}
// 获取新闻列表
async getNewsList(category: string, page: number): Promise<any[]> {
// 先从缓存获取
const cachedNews = await this.getNewsFromCache(category, page);
if (cachedNews.length > 0) {
return cachedNews;
}
// 缓存未命中,从网络获取
try {
const response = await this.httpClient.get<any[]>(
`/news?category=${category}&page=${page}`
);
const newsList = response.data;
// 存入缓存
await this.saveNewsToCache(newsList);
return newsList;
} catch (error) {
console.error('获取新闻失败:', (error as Error).message);
return [];
}
}
// 从缓存获取新闻
private async getNewsFromCache(category: string, page: number): Promise<any[]> {
const predicates = new relationalStore.RdbPredicates('news');
predicates.equalTo('category', category);
predicates.orderByDesc('publish_time');
const pageSize = 20;
const offset = (page - 1) * pageSize;
const sql = `SELECT * FROM news WHERE category = '${category}'
ORDER BY publish_time DESC LIMIT ${pageSize} OFFSET ${offset}`;
const resultSet = await this.rdbStore?.querySql(sql);
const newsList: any[] = [];
if (resultSet) {
while (resultSet.goToNextRow()) {
newsList.push({
id: resultSet.getString(resultSet.getColumnIndex('id')),
title: resultSet.getString(resultSet.getColumnIndex('title')),
summary: resultSet.getString(resultSet.getColumnIndex('summary')),
imageUrl: resultSet.getString(resultSet.getColumnIndex('image_url')),
publishTime: resultSet.getString(resultSet.getColumnIndex('publish_time'))
});
}
resultSet.close();
}
return newsList;
}
// 保存新闻到缓存
private async saveNewsToCache(newsList: any[]) {
for (const news of newsList) {
const values: relationalStore.ValuesBucket = {
id: news.id,
title: news.title,
summary: news.summary,
content: news.content,
image_url: news.imageUrl,
category: news.category,
publish_time: news.publishTime,
cached_at: Date.now()
};
const predicates = new relationalStore.RdbPredicates('news');
predicates.equalTo('id', news.id);
const existing = await this.rdbStore?.query(predicates, ['id']);
if (existing && existing.rowCount > 0) {
await this.rdbStore?.update(values, predicates);
} else {
await this.rdbStore?.insert('news', values);
}
existing?.close();
}
}
}
// 3. 应用入口
@Entry
@Component
struct NewsApp {
@State isLoggedIn: boolean = false;
private userDataManager: UserDataManager = new UserDataManager();
private newsDataManager: NewsDataManager = new NewsDataManager(httpClient);
async aboutToAppear() {
const context = getContext(this);
await this.userDataManager.init(context);
await this.newsDataManager.init(context);
this.isLoggedIn = await this.userDataManager.isLoggedIn();
}
build() {
Column() {
if (this.isLoggedIn) {
// 显示新闻列表
NewsListPage({
newsDataManager: this.newsDataManager,
onLogout: async () => {
await this.userDataManager.logout();
this.isLoggedIn = false;
}
})
} else {
// 显示登录页面
LoginPage({
onLoginSuccess: async (token: string, userId: string) => {
await this.userDataManager.saveLoginInfo(token, userId);
this.isLoggedIn = true;
}
})
}
}
.width('100%')
.height('100%')
}
}
// 新闻列表页面组件
@Component
struct NewsListPage {
@Prop newsDataManager: NewsDataManager;
@State newsList: any[] = [];
@State loading: boolean = false;
onLogout: () => Promise<void> = async () => {};
async aboutToAppear() {
await this.loadNews();
}
async loadNews() {
this.loading = true;
try {
this.newsList = await this.newsDataManager.getNewsList('technology', 1);
} finally {
this.loading = false;
}
}
build() {
Column() {
// 顶部栏
Row() {
Text('科技新闻')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button('退出')
.onClick(() => this.onLogout())
}
.padding(10)
.width('100%')
// 新闻列表
List({ space: 10 }) {
ForEach(this.newsList, (news: any) => {
ListItem() {
Row() {
Column() {
Text(news.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(news.summary)
.fontSize(14)
.fontColor('#666')
.maxLines(2)
.margin({ top: 5 })
Text(news.publishTime)
.fontSize(12)
.fontColor('#999')
.margin({ top: 5 })
}
.layoutWeight(1)
Image(news.imageUrl)
.width(80)
.height(60)
.objectFit(ImageFit.Cover)
.borderRadius(4)
.margin({ left: 10 })
}
.padding(10)
.backgroundColor('#fff')
.borderRadius(8)
}
})
}
.layoutWeight(1)
.padding(10)
// 加载状态
if (this.loading) {
LoadingProgress()
.width(30)
.height(30)
.margin({ bottom: 10 })
}
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
}
// 登录页面组件
@Component
struct LoginPage {
@State username: string = '';
@State password: string = '';
@State loading: boolean = false;
@State errorMsg: string = '';
onLoginSuccess: (token: string, userId: string) => Promise<void> = async () => {};
async handleLogin() {
if (!this.username || !this.password) {
this.errorMsg = '请输入用户名和密码';
return;
}
this.loading = true;
this.errorMsg = '';
try {
// 模拟登录请求
const response = await httpClient.post('/login', {
username: this.username,
password: this.password
});
await this.onLoginSuccess(response.data.token, response.data.userId);
} catch (e) {
this.errorMsg = (e as Error).message;
} finally {
this.loading = false;
}
}
build() {
Column() {
Text('新闻阅读App')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 40 })
TextInput({ placeholder: '请输入用户名', text: this.username })
.onChange((value: string) => this.username = value)
.margin({ bottom: 10 })
TextInput({ placeholder: '请输入密码', text: this.password })
.type(InputType.Password)
.onChange((value: string) => this.password = value)
.margin({ bottom: 20 })
if (this.errorMsg) {
Text(this.errorMsg)
.fontColor('#ff4d4f')
.margin({ bottom: 10 })
}
Button(this.loading ? '登录中...' : '登录')
.width('100%')
.height(45)
.onClick(() => this.handleLogin())
.enabled(!this.loading)
}
.padding(30)
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
七、总结与下期预告
总结
本文详细讲解了HarmonyOS NEXT的三种数据管理方案:
-
Preferences:轻量级键值存储,适合配置信息
-
RDB关系型数据库:结构化数据存储,支持复杂查询
-
HTTP网络请求:与服务器交互,获取和提交数据
我们还实现了:
-
通用的HTTP客户端封装
-
多级缓存管理器
-
完整的新闻阅读App数据层
关键要点:
-
Preferences适合小量配置数据,记得调用
flush() -
RDB适合结构化数据,注意关闭ResultSet和处理并发
-
HTTP请求要处理错误和超时,实现重试机制
-
合理使用缓存策略提升性能和用户体验
下期预告
第5篇:性能优化实战指南:启动速度、内存、渲染全方位优化
下期我们将深入讲解:
-
应用启动速度优化策略
-
内存管理与泄漏排查
-
列表渲染性能优化(LazyForEach、@Reusable)
-
网络请求性能优化
-
DevEco Studio Profiler性能分析工具实战
敬请期待!
系列文章导航:
-
第4篇:数据持久化与网络请求全攻略(当前)
鸿蒙开发者,专注HarmonyOS应用开发与架构设计。欢迎关注我的CSDN博客,获取更多鸿蒙开发干货。
标签:鸿蒙NEXT | HarmonyOS NEXT | ArkUI | ArkTS | DevEco Studio | 数据持久化 | Preferences | RDB | HTTP请求 | 网络请求 | 缓存策略 | @ohos.net.http