HTTP概述
HTTP,全称Hyper Text Transfer Protocol 超文本传输协议。
HTTP请求为短连接。客户端发起请求,服务器返回响应。本次连接即结束。
添加网络权限
在访问网络之前,需要在module.json5中给APP添加网络权限
"module": {"requestPermissions": [{"name": "ohos.permission.INTERNET"}]
}
HTTP请求开发步骤
导入http模块
import { http } from '@kit.NetworkKit'
创建http请求
let httpRequest = http.createHttp()
订阅响应事件
httpRequest.on('headersReceive', (header) => {…
});
发起请求
let promise = httpRequest.request(url,{method: http.RequestMethod.POST,extraData: {"param1": "value1","param2": "value2"},connectTimeout: 60000,readTimeout: 60000,expectDataType: http.HttpDataType.STRING,usingCache: true,priority: 1,usingProtocol: http.HttpProtocol.HTTP1_1,usingProxy: false,header: {'Content-Type': 'application/json'}})
请求参数解读如下表
字段 | 类型 |
---|---|
method | 请求方式 |
extraData | 请求的额外数据 |
connectTimeout | 连接超时时间 |
readTimeout | 读取超时时间 |
expectDataType | 指定返回数据的类型 |
usingCache | 是否使用缓存,默认true |
priority | 优先级,1~1000,默认1 |
usingProtocol | 是否使用协议 |
usingProxy | 是否使用http代理 |
header | http请求头 |
请求方式支持列表
字段 | 类型 |
---|---|
GET | 请求指定的页面信息,并返回响应主体 |
POST | 请求会向指定资源提交数据,请求服务器进行处理 |
PUT | 请求会向指定资源位置上传其最新内容 |
CONNECT | HTTP/1.1协议预留,能够将连接改为管道方式的代理服务器 |
HEAD | 类似GET请求,返回响应头信息,但不会返回响应主体 |
DELETE | 请求服务器删除所请求URI |
TRACE | 请求服务器回显其收到的请求信息 |
OPTIONS | 请求用于客户端查看服务器的性能 |
处理响应
let promise = httpRequest.request(…)promise.then((data) => {if (data.responseCode === http.ResponseCode.OK) {…}
})
data中包含以下字段
data包含的字段 | 类型 |
---|---|
responseCode | 响应结果状态码 |
result | 响应数据 |
resultType | 返回值类型 |
header | 响应头 |
cookies | 服务器返回的cookies |
常见responseCode状态码
名称 | 值 | 说明 |
---|---|---|
OK | 200 | 请求成功 |
CREATED | 201 | 已创建新的资源 |
NOT_FOUND | 404 | 找不到资源 |
VERSION | 505 | 服务器请求的HTTP协议版本 |
取消订阅
httpRequest.off("headersReceive")
销毁http对象(可选)
httpRequest.destroy()
HTTP流式请求开发步骤
HTTP流式请求通常用于下载文件
前几步与前文HTTP请求相同
// 导入https模块
import { http } from '@kit.NetworkKit’;
// 创建请求
let httpRequest = http.createHttp()
// 订阅响应事件
httpRequest.on('headersReceive', (header) => {
// 返回请求头
});
订阅更多响应事件
let res = ""
httpRequest.on("dataReceive", (data: ArrayBuffer) => {// 累加数据res += data
})
httpRequest.on("dataEnd", () => {// 接收完毕
})
使用requestInStream发起请求
与前文不同的是,这里使用requestInStream发起请求
let promise = httpRequest.requestInStream(url,{method: http.RequestMethod.POST,extraData: {"param1": "value1","param2": "value2"},connectTimeout: 60000,readTimeout: 60000,expectDataType: http.HttpDataType.STRING,usingCache: true,priority: 1,usingProtocol: http.HttpProtocol.HTTP1_1,usingProxy: false,header: {'Content-Type': 'application/json'}});
请求字段如下
字段 | 类型 |
---|---|
method | 请求方式 |
extraData | 请求的额外数据 |
connectTimeout | 连接超时时间 |
readTimeout | 读取超时时间 |
expectDataType | 指定返回数据的类型 |
usingCache | 是否使用缓存,默认true |
priority | 优先级,1~1000,默认1 |
usingProtocol | 是否使用协议 |
usingProxy | 是否使用http代理 |
header | http请求头 |
处理相应
let promise = httpRequest.requestInStream(…
)promise.then((responseCode) => {if (responseCode === http.ResponseCode.OK) {…}
})
取消订阅
httpRequest.off("headersReceive")
httpRequest.off("dataReceive")
httpRequest.off("dataReceiveProgress")
httpRequest.off("dataEnd")
销毁http对象(可选)
httpRequest.destroy()
案例:新闻加载
本章的知识有配套的工程源码,请参阅附件
本Demo将从网络api获取一个新闻列表,收到的数据为json格式,并将其显示到一个列表界面上面
我们将分这几个步骤来实现这个Demo
- 行布局
- ForEach实现列表
- 配置Http请求
- 发起请求处理响应
- 界面触发更新
行布局
我们简单做个行布局,横向排列一个文本和图片。
ListItem() {Row() {Text(item.title)Image(item.imgsrc)}
}
效果如下
ForEach实现列表
我们声明一个自定义类Item用于表达每行数据,包含title和imgsrc属性。声明一个@State属性newsList,类型为Item数组。在ForEach渲染的时候把newsList传入,每次循环的时候取得item实例,把其中的属性赋值给UI组件。
class Item {title?: stringimgsrc?: string
}
…
@State newsList: Item[] = new Array(10)
…
ForEach(this.newsList, (item: Item) => {ListItem() {…}
}, (item: Item) => item.title)
配置Http请求
参考上文,配置Http请求。请求的url可以使用https://v2.alapi.cn/api/new/toutiao?token=qlVquQZPYSeaCi6u。
let httpRequest = http.createHttp()
httpRequest.on('headersReceive', (header) => {console.info('header: ' + JSON.stringify(header))
})
let url = "https://v2.alapi.cn/api/new/toutiao?token=qlVquQZPYSeaCi6u"
let promise = httpRequest.request(url, // 请求url地址{// 请求方式method: http.RequestMethod.GET,// 开发者根据自身业务需要添加header字段header: {'Content-Type': 'application/json'}}
)
发起请求处理响应
用上文创建的promise任务发送请求,promise.then。在then回调里面拿到返回数据之后,赋值给newsList属性。
@State newsList: Item[] = new Array(10)
…
promise.then((data) => {if (data.responseCode === http.ResponseCode.OK) {console.info('Result:' + data.result)console.info('code:' + data.responseCode)this.newsList = JSON.parse(data.result as string)["data"]}
}).catch((err: BusinessError) => {console.info('error:' + JSON.stringify(err))
})
界面触发更新
newsList更新之后,由于被@State,会自定触发UI更新。在之前定义的ForEach循环中,再次使用newsList渲染,界面将显示相应信息。
@State newsList: Item[] = new Array(10)
…
this.newsList = JSON.parse(data.result as string)["data"]
…
List() {ForEach(this.newsList, (item: Item) => {ListItem() {Row() {Text(item.title)Image(item.imgsrc)}}}, (item: Item) => item.title)
}
案例:文件下载
本例将通过一个文件下载的Demo来学习Http流式请求。文件我们选择了一个常用磁盘工具DiskGenius,从其官网获得的下载链接,一共30m左右,大小和网络条件都比较合适。
分为以下步骤
- 简单界面布局
- 点击下载按钮设置保存文件
- 配置Http请求
- 开始下载
- 下载完成
简单界面布局
界面上我们需要这几个元素:下载链接输入框,进度条,下载按钮,下载完成的文字提示。同时,有几个@State修饰的属性。
@State progress用于更新Progress组件的进度显示。
@State downloadUrl用于设置下载链接。代码中可以填写好链接,通常不需要在界面上手动修改。
@State downloadFinished用于表示下载是否完成的状态。可以用于“下载完成”的文字显示与否。
@State progress: number = 0
@State downloadUrl: string = “…”
@State downloadFinished: boolean = false
…
Column() {TextInput({ text: this.downloadUrl })Progress({ value: this.progress })Button("下载").onClick(() => {…})Text("下载完成").visibility(this.downloadFinished ? Visibility.Visible : Visibility.Hidden)
}
大致效果图如下
点击下载按钮,设置保存文件
接下来,我们要配置点击按钮的动作。
当我们点击下载按钮的时候,会调起系统的文件管理器界面,我们会在界面上声明创建的文件名以及选择它所在的目录。
这里需要使用系统自带的DocumentViewPicker,调用实例的save方法,在异步回调then里面会获取到新文件的完整路径(包含文件名)。
在回调里面我们用获取到的文件路径来创建一个文件实例,保存在变量file里。
使用fs.openSync方法,打开文件,设定读写模式,并获取file实例。获取到file实例之后我们把它传到我们自己封装的download方法里。
Button("下载").onClick(() => {const documentViewPicker = new picker.DocumentViewPicker(); // 创建文件选择器实例documentViewPicker.save().then((documentSaveResult: Array<string>) => {let file = fs.openSync(documentSaveResult[0], fs.OpenMode.READ_WRITE);this.download(file)})})
配置Http请求
在download方法中,我们拿到了file实例,接下来我们看看怎么实现这个方法。
根据上文,我们同样先创建一个HttpRequest实例,然后注册几个事件监听:dataReceive,dataReceiveProgress和dataEnd。
在dataReceive中,我们会不停获取到文件的部分数据,每次获得数据的时候就用file实例写入磁盘。
在dataReceiveProgress中,我们会获取到文件下载进度。有两个数据,一个是已下载的大小,一个是总大小。我们可以利用这个数据,更新界面上进度条的显示。
在dataEnd中,我们可以关闭文件操作,并把@State修饰的progress属性置为0,让界面上的进度条回到初始显示状态。
download(file: fs.File): void {let httpRequest = http.createHttp()httpRequest.on('dataReceive', (data: ArrayBuffer) => {fs.writeSync(file.fd, data)})httpRequest.on('dataReceiveProgress', (info: http.DataReceiveProgressInfo) => {this.progress = info.receiveSize / info.totalSize * 100})httpRequest.on('dataEnd', () => {fs.closeSync(file)this.progress = 0})…
}
开始下载
创建完请求,注册完监听之后,我们就要触发请求了。不同于上文,我们这里使用httpRequest.requestInStream()方法来配置请求地址。获取到异步任务之后,调用promise.then()发起请求。在文件接收完之后,会触发此处的then回调。
download(file: fs.File): void {let httpRequest = http.createHttp()…let promise = httpRequest.requestInStream(// 请求url地址this.downloadUrl,{// 请求方式method: http.RequestMethod.GET,header: {'Content-Type': 'application/json'}})promise.then((data) => {console.info('Result:' + data)})
}
下载完成
当下载完成时,会显示完成的提示。
案例:图片下载
本例将通过一个图片下载的Demo来学习Http流式请求。我们选择了百度首页的百度logo图片地址。
分为以下步骤
- 简单界面布局
- 点击下载按钮调用下载方法
- 配置Http请求
- 开始下载
- 下载完成
简单界面布局
界面上我们需要这几个元素:下载链接输入框,进度条,下载按钮,下载完成的文字提示。同时,有几个@State修饰的属性。
@State progress用于更新Progress组件的进度显示。
@State downloadUrl用于设置下载链接。代码中可以填写好链接,通常不需要在界面上手动修改。
@State downloadFinished用于表示下载是否完成的状态。可以用于“下载完成”的文字显示与否。
@State pixelMap用于设置图片组件的数据源。
@State progress: number = 0
@State downloadUrl: string = “…”
@State downloadFinished: boolean = false
@State pixelMap?: PixelMap = undefined
imageBuffer: ArrayBuffer = new ArrayBuffer(0)
…
Column() {TextInput({ text: this.downloadUrl })Progress({ value: this.progress })Image(this.pixelMap)Button("下载").onClick(() => {…})Text("下载完成").visibility(this.downloadFinished ? Visibility.Visible : Visibility.Hidden)
}
大致效果图如下
点击下载按钮,调用下载方法
Button("下载").onClick(() => {this.download()})
配置Http请求
在download方法中,我们拿到了file实例,接下来我们看看怎么实现这个方法。
根据上文,我们同样先创建一个HttpRequest实例,然后注册几个事件监听:dataReceive,dataReceiveProgress和dataEnd。
在dataReceive中,我们会不停获取到文件的部分数据,每次获得数据的时候就拼接到imageBuffer尾部。
在dataReceiveProgress中,我们会获取到文件下载进度。有两个数据,一个是已下载的大小,一个是总大小。我们可以利用这个数据,更新界面上进度条的显示。
在dataEnd中,我们可以调用createImageSource,createPixelMap方法,把收到的二进制数据转换为pixel map。
并把@State修饰的progress属性置为0,让界面上的进度条回到初始显示状态。
download(file: fs.File): void {let httpRequest = http.createHttp()httpRequest.on('dataReceive', (data: ArrayBuffer) => {this.imageBuffer = concatenateArrayBuffers(this.imageBuffer, data)})httpRequest.on('dataReceiveProgress', (info: http.DataReceiveProgressInfo) => {this.progress = info.receiveSize / info.totalSize * 100})httpRequest.on('dataEnd', () => {let imageSource: image.ImageSource = image.createImageSource(this.imageBuffer);imageSource.createPixelMap().then((pixelMap: PixelMap) => {this.pixelMap = pixelMap})this.progress = 0})…
}
开始下载
创建完请求,注册完监听之后,我们就要触发请求了。不同于上文,我们这里使用httpRequest.requestInStream()方法来配置请求地址。获取到异步任务之后,调用promise.then()发起请求。在文件接收完之后,会触发此处的then回调。
download(file: fs.File): void {let httpRequest = http.createHttp()…let promise = httpRequest.requestInStream(// 请求url地址this.downloadUrl,{// 请求方式method: http.RequestMethod.GET,header: {'Content-Type': 'application/json'}})promise.then((data) => {console.info('Result:' + data)})
}
下载完成
当下载完成时,会显示完成的提示。
应用本地数据保存
什么是用户首选项
用户首选项为应用提供Key-Value键值型的数据存储能力,支持应用持久化轻量级数据,并对其进行增删除改查等。该存储对象中的数据会被缓存在内存中,因此它可以获得更快的存取速度,下面详细介绍下用户首选项的开发过程。
用户首选项运作机制
用户首选项的特点是:
1、以Key-Value形式存储数据
Key是不重复的关键字,Value是数据值。
2、非关系型数据库
区别于关系型数据库,它不保证遵循ACID(Atomicity, Consistency, Isolation and Durability)特性,数据之间无关系。
进程中每个文件对应一个Preferences实例,应用获取到实例后,可以从中读取数据,或者将数据存入实例中。通过调用flush方法可以将实例中的数据回写到文件里。
与关系数据库的区别
分类 | 关系型数据库 | 用户首选项 |
---|---|---|
数据库类型 | 关系型 | 非关系型 |
使用场景 | 提供复杂场景下的本地数据库管理机制 | 对Key-Value结构的数据进行存取和持久化操作 |
存储方式 | SQLite数据库 | 文件 |
约束与限制 | 1.连接池最大4个 2.同一时间只支持一个写操作 | 1.建议数据不超一万条 2.Key为string型 |
接口介绍
常用接口有:保存数据(put)、获取数据(get)、是否包含指定的key(has)、数据持久化(flush)、删除数据(delete)等,后面依次详细介绍接口使用。
接口使用前提
1、需要导入preferences模块到开发环境中,同时定义一个常量KEY_APP_FONT_SIZE。相关代码实现如下:
// entryAbility.ets
import { preferences } from '@kit.ArkData';
const KEY_APP_FONT_SIZE = 'appFontSize'; // 用户首选项Key字段
2、需要在entryAbility的onCreate方法获取用户首选项实例,同时定义一个Preferences实例配置选项options,其中name表示Preferences实例的名称,以便后续能进行保存、读取、删除等操作,获取实例需要上下文context和文件名字PREFERENCES_NAME,相关代码实现如下:
// entryAbility.ets onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {Logger.info(TAG, 'onCreate');// 设置字体默认大小try {let options: preferences.Options = { name: 'myStore' };let promise = preferences.getPreferences(this.context, options);...} catch (err) {console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);}}
保存数据(put)
1、在entryAbility的onCreate方法,先用has方法判断当前key是否有存在,如果没有就通过put方法把用户数据保存起来,该方法通过key-value键值对方式保存,常量KEY_APP_FONT_SIZE作为key,用户数据fontSize作为value,再通过flush方法把数据保存到文件,相关代码实现如下:
// entryAbility.ets onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {...// 设置字体默认大小try {let options: preferences.Options = { name: 'myStore' };let promise = preferences.getPreferences(this.context, options);promise.then((object: preferences.Preferences) => {object.has(KEY_APP_FONT_SIZE).then(async (isExist: boolean) => {Logger.info(TAG, 'preferences has changeFontSize is ' + isExist);if (!isExist) {await object?.put(KEY_APP_FONT_SIZE, CommonConstants.SET_SIZE_NORMAL);await object?.flush();}}).catch((err: Error) => {Logger.error(TAG, 'Has the value failed with err: ' + err);});console.info("Succeeded in getting preferences.");}).catch((err: base.BusinessError) => {console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);})} catch (err) {console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);}}
2、在SetFontSizePage页面,当手指移动Slider滑动条时,通过onChange方法回调获取当前进度值,再通过flush方法把数据保存到文件,相关代码实现如下:
// SetFontSizePage.etsbuild() {Row() {Slider({...}).onChange(async (value: number) => {// 保存当前进度值if (this.changeFontSize === 0) {this.fontSizeText = SetViewModel.getTextByFontSize(value);return;}this.changeFontSize = (value === CommonConstants.SET_SLIDER_MAX ? CommonConstants.SET_SIZE_HUGE : value);this.fontSizeText = SetViewModel.getTextByFontSize(this.changeFontSize);let myPreferences: preferences.Preferences | null = null;preferences.getPreferences(getContext(this), 'myStore').then((object: preferences.Preferences) => {myPreferences = object;myPreferences.put(KEY_APP_FONT_SIZE, this.changeFontSize).then(() => {myPreferences?.flush();});})})}}
获取数据(get)
在HomePage的onPageShow方法,调用preferences.get方法获取用户数据,该方法通过key-value键值对方式读取,常量KEY_APP_FONT_SIZE作为key,默认数据defValue作为value,把得到的结果赋值给变量fontSize,相关代码实现如下:
// HomePage.etsonPageShow() {preferences.getPreferences(getContext(this), 'myStore').then((object: preferences.Preferences) => {object.get(KEY_APP_FONT_SIZE, 0).then((data) => {this.changeFontSize = Number(data);});})}
是否包含指定的key(has)
通过has方法判断用户首选项中是否包含指定的key,保证指定的key不会被重复保存,相关代码实现如下:
// 判断保存的key是否存在
...
preferences.has(KEY_APP_FONT_SIZE).then(async (isExist: boolean) => {Logger.info(TAG, 'preferences has changeFontSize is ' + isExist);
}).catch((err: Error) => {Logger.error(TAG, 'Has the value failed with err: ' + err);
});
数据持久化(flush)
通过flush方法把应用数据保存到文件中,使得应用数据保存期限变长,相关代码实现如下:
// SetFontSizePage.ets
...
let myPreferences: preferences.Preferences | null = null;
preferences.getPreferences(getContext(this), 'myStore').then((object: preferences.Preferences) => {myPreferences = object;myPreferences.put(KEY_APP_FONT_SIZE, this.changeFontSize).then(() => {myPreferences?.flush();});
})
删除数据(delete)
删除用户首选项数据需要获取Preferences实例,用delete方法删除指定的key所对应的值,常量KEY_APP_FONT_SIZE作为key,通过Promise异步回调是否删除成功,相关代码实现如下:
...
let myPreferences: preferences.Preferences | null = null;
preferences.getPreferences(getContext(this), 'myStore').then((object: preferences.Preferences) => {myPreferences = object;myPreferences.delete(KEY_APP_FONT_SIZE).then(() => {console.info("Succeeded in deleting the key 'appFontSize'.");}).catch((err: BusinessError) => {console.error("Failed to delete the key 'appFontSize'. code =" + err.code +", message =" + err.message);});
})