鸿蒙APP开发从入门到精通:网络请求与数据持久化
《鸿蒙APP开发从入门到精通》第4篇:网络请求与数据持久化 🗄️
内容承接与核心价值
这是《鸿蒙APP开发从入门到精通》的第4篇——数据基础篇,承接第3篇的「自定义组件与数据双向绑定」,100%复用项目架构,为后续第6-12篇的电商购物车全栈项目铺垫网络数据获取和本地数据持久化的核心技术。
学习目标:
- 掌握鸿蒙官方HTTP请求库的用法;
- 实现商品列表与用户信息的网络请求;
- 理解数据持久化的原理与实现方式;
- 使用Preferences与SQLite实现本地数据存储;
- 优化网络请求与数据加载的用户体验(加载动画、错误处理)。
学习重点:
- 鸿蒙HTTP请求库的配置与使用;
- 网络请求的错误处理与重试机制;
- Preferences与SQLite的使用场景与实现;
- 数据加载动画与空状态界面的实现。
一、 网络请求基础 📡
1.1 鸿蒙HTTP请求库
HarmonyOS Next提供了官方HTTP请求库,支持HTTP/HTTPS协议,功能包括:
- 发送GET/POST/PUT/DELETE请求;
- 设置请求头与请求参数;
- 上传/下载文件;
- 拦截器(请求拦截、响应拦截);
- 超时与重试机制。
1.2 实战:HTTP请求库配置
1. 配置网络权限
在「entry/src/main/module.json5」中添加网络权限:
{"module":{"requestPermissions":[{"name":"ohos.permission.INTERNET"},{"name":"ohos.permission.READ_NETWORK_STATE"},{"name":"ohos.permission.WRITE_NETWORK_STATE"}]}}2. 网络请求工具类
⌨️ entry/src/main/ets/utils/HttpUtils.ets
import http from'@ohos.net.http';// 网络请求配置constHTTP_CONFIG={ baseUrl:'https://api.example.com',// 替换为你的API地址 timeout:10000,// 超时时间(毫秒) retryCount:3// 重试次数};// 网络请求工具类exportclassHttpUtils{privatestatic instance: HttpUtils |null=null;private httpRequest: http.HttpRequest |null=null;// 单例模式staticgetInstance(): HttpUtils {if(!HttpUtils.instance){ HttpUtils.instance =newHttpUtils();}return HttpUtils.instance;}// 初始化HTTP请求privateinitRequest():void{if(!this.httpRequest){this.httpRequest = http.createHttp();}}// 发送GET请求asyncget<T>(url:string, params?: Record<string,any>):Promise<T>{this.initRequest();const fullUrl =this.buildUrl(url, params);const response =awaitthis.httpRequest!.request(fullUrl,{ method: http.RequestMethod.GET, header:{'Content-Type':'application/json'}, connectTimeout:HTTP_CONFIG.timeout, readTimeout:HTTP_CONFIG.timeout });returnthis.handleResponse<T>(response);}// 发送POST请求asyncpost<T>(url:string, data?: Record<string,any>):Promise<T>{this.initRequest();const fullUrl =this.buildUrl(url);const response =awaitthis.httpRequest!.request(fullUrl,{ method: http.RequestMethod.POST, header:{'Content-Type':'application/json'}, extraData: data, connectTimeout:HTTP_CONFIG.timeout, readTimeout:HTTP_CONFIG.timeout });returnthis.handleResponse<T>(response);}// 构建完整URLprivatebuildUrl(url:string, params?: Record<string,any>):string{let fullUrl =`${HTTP_CONFIG.baseUrl}${url}`;if(params){const queryString = Object.keys(params).map(key =>`${key}=${encodeURIComponent(params[key])}`).join('&'); fullUrl +=`?${queryString}`;}return fullUrl;}// 处理响应privateasynchandleResponse<T>(response: http.HttpResponse):Promise<T>{if(response.responseCode ===200){const result =JSON.parse(response.result.toString());if(result.code ===200){return result.data;}else{thrownewError(result.message ||'请求失败');}}else{thrownewError(`HTTP错误:${response.responseCode}`);}}}二、 网络请求实战 🛠️
2.1 实战目标
基于第3篇的「MyFirstHarmonyApp」项目架构,实现以下功能:
- 商品列表网络请求:从真实API获取商品数据,替换模拟数据;
- 用户信息网络请求:实现用户登录、注册、个人信息查询;
- 网络请求拦截器:实现请求拦截与响应拦截;
- 网络状态检测:检测网络连接状态,优化用户体验。
2.2 🔧 业务服务实现
1. 商品服务
⌨️ entry/src/main/ets/services/ProductService.ets
import{ HttpUtils }from'../utils/HttpUtils';import{ GoodsModel }from'../models/HomeModel';// 商品API响应数据模型exportinterfaceProductResponseModel{ code:number; message:string; data:Array<GoodsModel>;}// 商品服务exportclassProductService{privatestatic instance: ProductService |null=null;// 单例模式staticgetInstance(): ProductService {if(!ProductService.instance){ ProductService.instance =newProductService();}return ProductService.instance;}// 获取商品列表asyncgetProductList():Promise<Array<GoodsModel>>{const response =await HttpUtils.getInstance().get<Array<GoodsModel>>('/api/products');return response;}// 获取商品详情asyncgetProductDetail(id:number):Promise<GoodsModel>{const response =await HttpUtils.getInstance().get<GoodsModel>(`/api/products/${id}`);return response;}}2. 用户服务
⌨️ entry/src/main/ets/services/UserService.ets
import{ HttpUtils }from'../utils/HttpUtils';// 用户登录请求数据模型exportinterfaceLoginRequestModel{ username:string; password:string;}// 用户登录响应数据模型exportinterfaceLoginResponseModel{ code:number; message:string; data:{ token:string; userInfo: UserInfoModel;};}// 用户信息数据模型exportinterfaceUserInfoModel{ id:number; username:string; avatar:string; nickname:string; email:string; phone:string; gender:number; birthday:string;}// 用户服务exportclassUserService{privatestatic instance: UserService |null=null;// 单例模式staticgetInstance(): UserService {if(!UserService.instance){ UserService.instance =newUserService();}return UserService.instance;}// 用户登录asynclogin(request: LoginRequestModel):Promise<LoginResponseModel['data']>{const response =await HttpUtils.getInstance().post<LoginResponseModel['data']>('/api/login', request);return response;}// 用户注册asyncregister(request: LoginRequestModel):Promise<LoginResponseModel['data']>{const response =await HttpUtils.getInstance().post<LoginResponseModel['data']>('/api/register', request);return response;}// 获取用户信息asyncgetUserInfo():Promise<UserInfoModel>{const response =await HttpUtils.getInstance().get<UserInfoModel>('/api/user/info');return response;}}2.3 🔧 网络状态检测
1. 网络状态服务
⌨️ entry/src/main/ets/services/NetworkService.ets
import network from'@ohos.net.network';// 网络状态类型exporttypeNetworkType='wifi'|'cellular'|'none';// 网络状态服务exportclassNetworkService{privatestatic instance: NetworkService |null=null;// 单例模式staticgetInstance(): NetworkService {if(!NetworkService.instance){ NetworkService.instance =newNetworkService();}return NetworkService.instance;}// 获取当前网络状态getCurrentNetworkType(): NetworkType {const networkType = network.getNetworkTypeSync();switch(networkType){case network.NetworkType.WIFI:return'wifi';case network.NetworkType.CELLULAR:return'cellular';default:return'none';}}// 监听网络状态变化onNetworkTypeChange(callback:(type: NetworkType)=>void):void{ network.on('netAvailable',()=>{callback(this.getCurrentNetworkType());}); network.on('netUnavailable',()=>{callback('none');});}}2. 网络状态组件
⌨️ entry/src/main/ets/components/NetworkStatusComponent.ets
import{ NetworkService }from'../services/NetworkService';@Componentexport struct NetworkStatusComponent {@State networkType: NetworkService.NetworkType ='wifi';build(){Row({ space:8}){Image(this.networkType ==='wifi'?$r('app.media.wifi'):this.networkType ==='cellular'?$r('app.media.cellular'):$r('app.media.none')).width(20).height(20).objectFit(ImageFit.Contain);Text(this.networkType ==='wifi'?'WiFi':this.networkType ==='cellular'?'移动网络':'无网络').fontSize(14).textColor(this.networkType ==='none'?'#FF0000':'#666666');}.width('auto').height('auto').padding(8,16,8,16).backgroundColor(this.networkType ==='none'?'#FFE0E0':'#F5F5F5').borderRadius(20).visible(this.networkType ==='none');}aboutToAppear(){this.networkType = NetworkService.getInstance().getCurrentNetworkType(); NetworkService.getInstance().onNetworkTypeChange((type: NetworkService.NetworkType)=>{this.networkType = type;});}}三、 数据持久化实战 🗄️
3.1 数据持久化分类
HarmonyOS Next提供了3种数据持久化方式:
| 方式 | 功能 | 适用场景 |
|---|---|---|
| Preferences | 轻量级存储,支持键值对存储 | 用户设置、应用配置、购物车数据等小体积数据 |
| SQLite | 关系型数据库,支持复杂查询 | 商品列表、用户信息、订单数据等结构化数据 |
| Distributed Data | 分布式存储,支持跨设备数据同步 | 超级终端多设备协同场景 |
3.2 实战:Preferences存储
1. Preferences工具类
⌨️ entry/src/main/ets/utils/PreferencesUtils.ets
import preferences from'@ohos.data.preferences';import{ UIAbilityContext }from'@ohos.abilityAccessCtrl';// Preferences工具类exportclassPreferencesUtils{privatestatic instance: PreferencesUtils |null=null;private dataPreferences: preferences.Preferences |null=null;// 单例模式staticgetInstance(): PreferencesUtils {if(!PreferencesUtils.instance){ PreferencesUtils.instance =newPreferencesUtils();}return PreferencesUtils.instance;}// 初始化Preferencesasyncinit(context: UIAbilityContext):Promise<void>{if(!this.dataPreferences){this.dataPreferences =await preferences.getPreferences(context,'app_preferences');}}// 获取值get<T>(key:string, defaultValue:T):T{if(!this.dataPreferences){return defaultValue;}returnthis.dataPreferences.getSync(key, defaultValue)asT;}// 设置值asyncput<T>(key:string, value:T):Promise<void>{if(!this.dataPreferences){return;}awaitthis.dataPreferences.put(key, value);awaitthis.dataPreferences.flush();}// 删除值asyncdelete(key:string):Promise<void>{if(!this.dataPreferences){return;}awaitthis.dataPreferences.delete(key);awaitthis.dataPreferences.flush();}// 清空所有值asyncclear():Promise<void>{if(!this.dataPreferences){return;}const keys =this.dataPreferences.keys();for(const key of keys){awaitthis.dataPreferences.delete(key);}awaitthis.dataPreferences.flush();}}2. Preferences应用
⌨️ entry/src/main/ets/services/UserService.ets(修改)
import{ HttpUtils }from'../utils/HttpUtils';import{ PreferencesUtils }from'../utils/PreferencesUtils';// 用户服务exportclassUserService{// ...// 用户登录(添加token存储)asynclogin(request: LoginRequestModel):Promise<LoginResponseModel['data']>{const response =await HttpUtils.getInstance().post<LoginResponseModel['data']>('/api/login', request);// 存储token到Preferencesawait PreferencesUtils.getInstance().put('token', response.token);return response;}// 获取用户信息(添加token认证)asyncgetUserInfo():Promise<UserInfoModel>{const token = PreferencesUtils.getInstance().get<string>('token',''); HttpUtils.getInstance().addHeader('Authorization',`Bearer ${token}`);const response =await HttpUtils.getInstance().get<UserInfoModel>('/api/user/info');return response;}}3.3 实战:SQLite存储
1. SQLite数据库管理类
⌨️ entry/src/main/ets/utils/SQLiteUtils.ets
import relationalStore from'@ohos.data.relationalStore';import{ UIAbilityContext }from'@ohos.abilityAccessCtrl';// 数据库配置constDB_CONFIG={ name:'app_db.db', securityLevel: relationalStore.SecurityLevel.S1};// SQLite数据库管理类exportclassSQLiteUtils{privatestatic instance: SQLiteUtils |null=null;private rdbStore: relationalStore.RdbStore |null=null;// 单例模式staticgetInstance(): SQLiteUtils {if(!SQLiteUtils.instance){ SQLiteUtils.instance =newSQLiteUtils();}return SQLiteUtils.instance;}// 初始化数据库asyncinit(context: UIAbilityContext):Promise<void>{if(!this.rdbStore){this.rdbStore =await relationalStore.getRdbStore(context,DB_CONFIG);// 创建用户表awaitthis.createUserTable();// 创建商品表awaitthis.createProductTable();// 创建购物车表awaitthis.createCartTable();}}// 创建用户表privateasynccreateUserTable():Promise<void>{const sql =` CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, avatar TEXT, nickname TEXT, email TEXT, phone TEXT, gender INTEGER, birthday TEXT ) `;awaitthis.rdbStore!.executeSql(sql);}// 创建商品表privateasynccreateProductTable():Promise<void>{const sql =` CREATE TABLE IF NOT EXISTS product ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, imageUrl TEXT, price REAL, originalPrice REAL, sales INTEGER ) `;awaitthis.rdbStore!.executeSql(sql);}// 创建购物车表privateasynccreateCartTable():Promise<void>{const sql =` CREATE TABLE IF NOT EXISTS cart ( id INTEGER PRIMARY KEY AUTOINCREMENT, productId INTEGER NOT NULL, name TEXT NOT NULL, imageUrl TEXT, price REAL, count INTEGER, isChecked INTEGER ) `;awaitthis.rdbStore!.executeSql(sql);}// 查询数据asyncquery(sql:string, args?:Array<string>):Promise<Array<relationalStore.ResultSet>>{if(!this.rdbStore){return[];}const resultSet =awaitthis.rdbStore.querySql(sql, args);const data:Array<relationalStore.ResultSet>=[];if(resultSet.goToFirstRow()){do{ data.push(resultSet);}while(resultSet.goToNextRow());}return data;}// 插入数据asyncinsert(tableName:string, values: Record<string,any>):Promise<number>{if(!this.rdbStore){return-1;}const rowId =awaitthis.rdbStore.insert(tableName, values);return rowId;}// 更新数据asyncupdate(tableName:string, values: Record<string,any>, whereClause?:string, whereArgs?:Array<string>):Promise<number>{if(!this.rdbStore){return0;}const count =awaitthis.rdbStore.update(tableName, values, whereClause, whereArgs);return count;}// 删除数据asyncdelete(tableName:string, whereClause?:string, whereArgs?:Array<string>):Promise<number>{if(!this.rdbStore){return0;}const count =awaitthis.rdbStore.delete(tableName, whereClause, whereArgs);return count;}}2. SQLite应用
⌨️ entry/src/main/ets/services/ProductService.ets(修改)
import{ HttpUtils }from'../utils/HttpUtils';import{ GoodsModel }from'../models/HomeModel';import{ SQLiteUtils }from'../utils/SQLiteUtils';// 商品服务exportclassProductService{// ...// 获取商品列表(先从本地数据库获取,再从网络获取)asyncgetProductList():Promise<Array<GoodsModel>>{// 先从本地数据库获取const localData =awaitthis.getProductListFromLocal();if(localData.length >0){return localData;}// 本地数据库无数据,从网络获取const remoteData =awaitthis.getProductListFromRemote();// 将网络数据存入本地数据库awaitthis.saveProductListToLocal(remoteData);return remoteData;}// 从本地数据库获取商品列表privateasyncgetProductListFromLocal():Promise<Array<GoodsModel>>{const sql ='SELECT * FROM product';const resultSet =await SQLiteUtils.getInstance().query(sql);const data:Array<GoodsModel>=[]; resultSet.forEach(row =>{ data.push({ id: row.getDouble(row.getColumnIndex('id')), name: row.getString(row.getColumnIndex('name')), imageUrl: row.getString(row.getColumnIndex('imageUrl')), price: row.getDouble(row.getColumnIndex('price')), originalPrice: row.getDouble(row.getColumnIndex('originalPrice')), sales: row.getDouble(row.getColumnIndex('sales'))});});return data;}// 从网络获取商品列表privateasyncgetProductListFromRemote():Promise<Array<GoodsModel>>{const response =await HttpUtils.getInstance().get<Array<GoodsModel>>('/api/products');return response;}// 将商品列表存入本地数据库privateasyncsaveProductListToLocal(data:Array<GoodsModel>):Promise<void>{for(const item of data){await SQLiteUtils.getInstance().insert('product',{ id: item.id, name: item.name, imageUrl: item.imageUrl, price: item.price, originalPrice: item.originalPrice, sales: item.sales });}}}四、 用户体验优化 🎨
4.1 加载动画
⌨️ entry/src/main/ets/components/LoadingComponent.ets
@Componentexport struct LoadingComponent {@Prop isLoading:boolean=false;build(){Column({ space:16}){Progress({ value:0, total:100, type: ProgressType.ScaleRing }).width(60).height(60).style({ strokeWidth:5, scaleCount:12, duration:1000});Text('加载中...').fontSize(14).textColor('#666666');}.width('100%').height('100%').justifyContent(FlexAlign.Center).visible(this.isLoading);}}4.2 错误处理
⌨️ entry/src/main/ets/components/ErrorComponent.ets
@Componentexport struct ErrorComponent {@Prop isError:boolean=false;@Prop errorMessage:string='请求失败';onRetry:()=>void;build(){Column({ space:16}){Image($r('app.media.error')).width(80).height(80).objectFit(ImageFit.Contain);Text(this.errorMessage).fontSize(14).textColor('#666666');Button('重试').width(120).height(48).backgroundColor('#007DFF').onClick(()=>this.onRetry());}.width('100%').height('100%').justifyContent(FlexAlign.Center).visible(this.isError);}}五、 项目运行与效果验证 📱
5.1 项目配置
1. 配置API地址
在「entry/src/main/ets/utils/HttpUtils.ets」中替换为你的真实API地址:
constHTTP_CONFIG={ baseUrl:'https://your-api-address.com',// 替换为你的API地址// ...};2. 初始化数据持久化
在「entry/src/main/ets/entryability/EntryAbility.ts」中初始化Preferences和SQLite:
import{ PreferencesUtils }from'../utils/PreferencesUtils';import{ SQLiteUtils }from'../utils/SQLiteUtils';exportdefaultclassEntryAbilityextendsUIAbility{onCreate(want: Want, launchParam: AbilityConstant.LaunchParam):void{// 初始化Preferences PreferencesUtils.getInstance().init(this.context);// 初始化SQLite SQLiteUtils.getInstance().init(this.context);}// ...}5.2 🔧 项目运行
① 在DevEco Studio中点击「Run」按钮;
② 选择调试设备,点击「OK」;
③ 等待编译安装完成,应用会自动在设备上启动。
效果验证
✅ 商品列表网络请求:从真实API获取商品数据,支持本地缓存;
✅ 用户信息网络请求:实现用户登录、注册、个人信息查询;
✅ 网络状态检测:在无网络时显示网络状态提示;
✅ 加载动画:商品列表加载时显示环形进度条;
✅ 错误处理:请求失败时显示错误信息与重试按钮。
六、 总结与未来学习路径 🚀
6.1 总结
本文作为《鸿蒙APP开发从入门到精通》的第4篇,完成了:
- 鸿蒙官方HTTP请求库的配置与使用;
- 商品列表与用户信息的网络请求实现;
- Preferences与SQLite数据持久化的应用;
- 网络请求拦截器、网络状态检测、加载动画、错误处理等用户体验优化。
6.2 未来学习路径
- 第5篇:页面路由与组件跳转;
- 第6篇:原子化服务与元服务卡片的开发;
- 第7篇:超级终端多设备协同开发;
- 第8篇:服务联邦跨服务无缝打通;
- 第9篇:安全加固与组件化架构;
- 第10篇:AI原生与用户增长;
- 第11篇:性能优化与Next原生合规;
- 第12篇:运维监控、生态运营与专属变现。
结语 ✅
恭喜你!你已经完成了《鸿蒙APP开发从入门到精通》的第4篇,掌握了网络请求与数据持久化的核心技术。
从现在开始,你已具备了与后端API交互和本地数据存储的能力。未来的8篇文章将逐步构建一个完整的鸿蒙电商购物车全栈项目,并最终实现华为应用市场上架变现。
让我们一起期待鸿蒙生态的爆发! 🎉🎉🎉