HarmonyOS 的网络请求
HarmonyOS NEXT 提供了 @ohos.net.http 模块作为原生 HTTP 客户端,同时也支持使用第三方库(如基于 Axios 封装的适配层)。发起网络请求前必须在 module.json5 中声明网络权限。
必须声明网络权限
在 module.json5 的 requestPermissions 中添加
"ohos.permission.INTERNET",否则网络请求会被系统拒绝,且不会有任何弹框提示。这是初学者最常见的坑。
基础 HTTP 请求
import http from '@ohos.net.http'
// 封装一个通用的 HTTP 客户端
class HttpClient {
private baseUrl: string
private headers: Record<string, string>
constructor(baseUrl: string) {
this.baseUrl = baseUrl
this.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
setToken(token: string) {
this.headers['Authorization'] = `Bearer ${token}`
}
async get<T>(path: string): Promise<T> {
// 每次请求创建一个新的 httpRequest 实例
const request = http.createHttp()
try {
const response = await request.request(
`${this.baseUrl}${path}`,
{
method: http.RequestMethod.GET,
header: this.headers,
connectTimeout: 10000,
readTimeout: 10000
}
)
if (response.responseCode !== 200) {
throw new Error(`HTTP ${response.responseCode}`)
}
return JSON.parse(response.result as string) as T
} finally {
request.destroy() // 必须销毁,防止内存泄漏
}
}
async post<T>(path: string, body: object): Promise<T> {
const request = http.createHttp()
try {
const response = await request.request(
`${this.baseUrl}${path}`,
{
method: http.RequestMethod.POST,
header: this.headers,
extraData: JSON.stringify(body)
}
)
return JSON.parse(response.result as string) as T
} finally {
request.destroy()
}
}
}
// 全局单例
export const apiClient = new HttpClient('https://api.example.com')
在组件中使用网络请求
interface Article {
id: number
title: string
content: string
createdAt: string
}
@Entry
@Component
struct ArticleListPage {
@State articles: Article[] = []
@State loading: boolean = false
@State error: string = ''
aboutToAppear() {
this.loadArticles()
}
async loadArticles() {
this.loading = true
this.error = ''
try {
this.articles = await apiClient.get<Article[]>('/articles')
} catch (e) {
this.error = '加载失败,请重试'
} finally {
this.loading = false
}
}
build() {
Column() {
if (this.loading) {
LoadingProgress().width(40).height(40)
} else if (this.error) {
Text(this.error).fontColor('#CF0A2C')
Button('重试').onClick(() => this.loadArticles())
} else {
List({ space: 12 }) {
ForEach(this.articles, (article: Article) => {
ListItem() {
ArticleRow({ article: article })
}
}, (article: Article) => article.id.toString())
}
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
关系型数据库 RDB
HarmonyOS NEXT 提供了 @ohos.data.relationalStore 模块,基于 SQLite 实现。适合存储结构化数据,支持 SQL 查询、事务、索引等完整数据库功能:
RdbStore
关系型数据库的操作句柄,通过 relationalStore.getRdbStore() 获取。一个应用通常只需要一个 RdbStore 实例(单例),在整个应用生命周期内复用。
ValuesBucket
插入或更新数据时使用的键值对对象,相当于一行数据。键是列名(string),值是对应的数据(number | string | boolean | Uint8Array)。
RdbPredicates
查询条件构建器,用链式方法描述 WHERE 子句、排序、分页等。避免手写 SQL 字符串拼接,防止 SQL 注入。
import { relationalStore } from '@kit.ArkData'
import { common } from '@kit.AbilityKit'
// 数据库管理类(单例模式)
class DatabaseManager {
private static instance: DatabaseManager
private rdbStore: relationalStore.RdbStore | null = null
static getInstance(): DatabaseManager {
if (!DatabaseManager.instance) {
DatabaseManager.instance = new DatabaseManager()
}
return DatabaseManager.instance
}
// 初始化数据库(建表)
async init(context: common.UIAbilityContext) {
const config: relationalStore.StoreConfig = {
name: 'AppDatabase.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1
}
// 建表 SQL(如果表不存在则创建)
const createArticleTable = `
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
is_favorite INTEGER DEFAULT 0
)
`
this.rdbStore = await relationalStore.getRdbStore(context, config)
await this.rdbStore.executeSql(createArticleTable)
console.log('数据库初始化完成')
}
// 插入文章
async insertArticle(title: string, content: string): Promise<number> {
const bucket: relationalStore.ValuesBucket = {
title: title,
content: content,
is_favorite: 0
}
return await this.rdbStore!.insert('articles', bucket)
}
// 查询所有文章
async getAllArticles(): Promise<Article[]> {
const predicates = new relationalStore.RdbPredicates('articles')
predicates.orderByDesc('created_at') // 按时间倒序
predicates.limitAs(50) // 最多 50 条
const cursor = await this.rdbStore!.query(predicates, ['id', 'title', 'content', 'created_at'])
const articles: Article[] = []
while (cursor.goToNextRow()) {
articles.push({
id: cursor.getLong(cursor.getColumnIndex('id')),
title: cursor.getString(cursor.getColumnIndex('title')),
content: cursor.getString(cursor.getColumnIndex('content')),
createdAt: new Date(cursor.getLong(cursor.getColumnIndex('created_at')) * 1000).toISOString()
})
}
cursor.close() // 必须关闭游标,防止内存泄漏
return articles
}
// 条件查询:搜索标题
async searchArticles(keyword: string): Promise<Article[]> {
const predicates = new relationalStore.RdbPredicates('articles')
predicates.contains('title', keyword) // LIKE '%keyword%'
const cursor = await this.rdbStore!.query(predicates)
// ... 同样的遍历逻辑
cursor.close()
return []
}
// 更新(切换收藏状态)
async toggleFavorite(articleId: number, isFavorite: boolean) {
const predicates = new relationalStore.RdbPredicates('articles')
predicates.equalTo('id', articleId)
await this.rdbStore!.update({ is_favorite: isFavorite ? 1 : 0 }, predicates)
}
// 删除
async deleteArticle(articleId: number) {
const predicates = new relationalStore.RdbPredicates('articles')
predicates.equalTo('id', articleId)
await this.rdbStore!.delete(predicates)
}
}
export const db = DatabaseManager.getInstance()
Preferences — 轻量键值存储
@ohos.data.preferences 提供轻量级键值存储,适合保存配置项、用户偏好等简单数据。相比 RDB,它不支持复杂查询,但读写更快,API 更简单。(注意与第3章的 PersistentStorage 的区别:Preferences 是纯文件级 API,更底层;PersistentStorage 是 ArkUI 框架层的封装,与组件状态深度集成。)
import preferences from '@ohos.data.preferences'
class UserPrefs {
private store: preferences.Preferences | null = null
async init(context: Context) {
this.store = await preferences.getPreferences(context, 'user_prefs')
}
// 设置值(支持 string / number / boolean / string[] / number[])
async set(key: string, value: preferences.ValueType) {
await this.store!.put(key, value)
await this.store!.flush() // 将内存数据持久化到磁盘(异步)
}
// 读取值,提供默认值
get<T extends preferences.ValueType>(key: string, defaultValue: T): T {
return this.store!.getSync(key, defaultValue) as T
}
// 监听数据变化(实时更新 UI)
onChange(key: string, callback: () => void) {
this.store!.on('change', (changedKey: string) => {
if (changedKey === key) callback()
})
}
}
// 使用示例
const prefs = new UserPrefs()
await prefs.init(context)
// 保存用户 token
await prefs.set('auth_token', 'eyJhbGciOiJIUzI1NiJ9...')
// 读取(同步,适合组件初始化时读取)
const token = prefs.get('auth_token', '')
文件操作与沙箱
HarmonyOS NEXT 采用严格的沙箱机制:每个应用只能访问自己沙箱目录内的文件,无法直接读写其他应用的文件或系统文件。理解应用沙箱结构对文件存储至关重要:
应用沙箱目录结构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/data/storage/el2/base/ (应用私有目录,加密保护)
├── files/ ← context.filesDir,应用文件
├── database/ ← RDB 数据库文件存放位置
├── preferences/ ← Preferences 文件存放位置
└── cache/ ← context.cacheDir,缓存文件(可被系统清理)
/data/storage/el1/base/ (设备级存储,重启保留)
└── files/ ← 适合重要但不加密的数据
说明:
• el1:设备重启后可用,但不加密
• el2:默认级别,用户解锁后才可访问,加密保护
import fs from '@ohos.file.fs'
class FileManager {
private filesDir: string
private cacheDir: string
constructor(context: Context) {
this.filesDir = context.filesDir
this.cacheDir = context.cacheDir
}
// 写入文本文件
async writeText(filename: string, content: string) {
const path = `${this.filesDir}/${filename}`
const file = await fs.open(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
await fs.write(file.fd, content)
await fs.close(file.fd)
}
// 读取文本文件
async readText(filename: string): Promise<string> {
const path = `${this.filesDir}/${filename}`
try {
const file = await fs.open(path, fs.OpenMode.READ_ONLY)
const stat = await fs.stat(path)
const buffer = new ArrayBuffer(stat.size)
await fs.read(file.fd, buffer)
await fs.close(file.fd)
return new TextDecoder().decode(buffer)
} catch (e) {
return '' // 文件不存在返回空
}
}
// 保存图片到缓存(如网络下载的封面图)
async saveImageToCache(filename: string, data: ArrayBuffer) {
const path = `${this.cacheDir}/${filename}`
const file = await fs.open(path, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE)
await fs.write(file.fd, data)
await fs.close(file.fd)
return path // 返回可在 Image 组件中使用的本地路径
}
// 列出目录下所有文件
async listFiles(dir: string = this.filesDir): Promise<string[]> {
const entries = await fs.listFile(dir)
return entries
}
}
数据层架构建议
一个健壮的数据层应该将网络请求、本地存储和业务逻辑分离,遵循仓库模式(Repository Pattern):
推荐数据层架构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
UI 组件(.ets)
│ 调用
▼
ViewModel / Service(业务逻辑层)
│ 调用
▼
Repository(数据仓库层)
├── 网络请求优先 → ApiClient → 服务端
└── 失败时读本地 → RdbStore / Preferences
本地有缓存 ──────────────────────
原则:
• UI 只关心"数据是什么",不关心"数据从哪来"
• Repository 决定从网络还是本地读数据
• 网络成功后写入本地缓存,实现离线可用
选型建议
- 用户设置/偏好(主题、语言、通知开关)→ PersistentStorage 或 Preferences
- 业务数据(文章列表、订单记录、聊天记录)→ RDB(支持复杂查询)
- 临时缓存(图片、网络响应缓存)→ 文件系统 cacheDir
- 会话级全局状态(登录信息、购物车)→ AppStorage