哇袄!!
大家好啊,今天来点大家想看的东西,说的鸿蒙之RCP
RCP概念
这里引用官方原文来解释下RCP是什么:Remote Communication Kit中的@hms.collaboration.rcp(后续简称RCP)指的是远程通信平台(remote communication platform),RCP提供了网络数据请求功能,相较于Network Kit中HTTP请求能力,RCP更具易用性,且拥有更多的功能。在开发过程中,如果有些场景使用Network Kit中HTTP请求能力达不到预期或无法实现,那么就可以尝试使用RCP中的数据请求功能来实现。
简单来说是一个比官方Network Kit中HTTP更加强大且全面的网络请求平台,二者的对比以及更多内容详情请见官方文档《基于RCP的网络请求开发实践》。
想直接看过程的直接去封装过程查看。
RCP的工作流程
以较为常见的GET方法为例子,RCP完成这一次网络请求的过程如下:
流程图:
时序图:
RCP组件简介
Session
Session类表示可用于发出HTTP请求的通信会话。它提供了各种HTTP方法(FETCH、GET、POST、PUT、HEAD、DELETE、CANCEL、CLOSE),可以通过API进行会话管理(创建/关闭对话),可以使用rcp.createSession方法创建Session对象(注:可创建Session对象上线为16个,超出则会报错,错误码:1007900994,报错信息:Sessions number reached limit。),cancel/close取消/关闭对话。
SessionConfiguration
createSession(sessionConfiguration?: SessionConfiguration): Session
,接收参数为SessionConfiguration
接口,该接口定义了本次会话的配置参数,接口具体属性如下:
名称 | 类型 | 只读 | 可选 | 说明 |
---|---|---|---|---|
interceptors | Interceptor[] | 否 | 是 | 请求/响应拦截器。起始版本: 5.0.0(12) |
requestConfiguration | Configuration | 否 | 是 | 指定与会话关联的HTTP请求的配置。包括transfer、proxy、DNS、connection和security configurations。 |
baseAddress | URLOrString | 否 | 是 | 设置会话中URL的基地址。这允许开发者为会话中的多个请求定义一个通用的基本URL。如果请求URL不是绝对URL,则把基地址预制在请求URL的前面。例如,"example.com?name=value",example.com为基地址,"?name=value"为请求URL。 |
headers | RequestHeaders | 否 | 是 | 为Session发出的HTTP请求定义headers(可自定义)。开发人员可以根据他们的需求定制的特定headers。 |
cookies | RequestCookies | 否 | 是 | 提供在与会话关联的HTTP请求中包含自定义cookie的方法。适用于需要将某些cookie附加到每个请求的场景。需要手动设置cookie,携带cookie为用户行为。 |
sessionListener | SessionListener | 否 | 是 | 允许开发人员将侦听器附加到会话,接收会话取消或关闭等事件的通知,更好地处理应用程序中与会话相关的事件。 |
connectionConfiguration | ConnectionConfiguration | 否 | 是 | 连接配置。用于指定此会话中允许的并发TCP连接总数以及单个主机所允许的最大并发TCP 连接数。起始版本: 5.0.0(12) 单个主机允许最大并发TCP连接数:默认6,最大2147483647; 允许最大同时TCP连接总数:默认64,最大2147483647; |
简单归类为以下几个部分:
- 全局配置:interceptors、sessionListener、connectionConfiguration
- 基础类型配置:baseAddress、headers、cookies
- 请求配置:requestConfiguration
Request
Request类表示一个HTTP请求,可以构造具体的请求信息,类属性如下:
名称 | 类型 | 只读 | 可选 | 说明 |
---|---|---|---|---|
id | string | 是 | 否 | 请求对象的唯一标识符。由系统生成。 |
url | URL | 否 | 否 | HTTP请求的URL。 |
method | HttpMethod | 否 | 否 | 要使用的HTTP方法。默认值为'GET'。 |
headers | RequestHeaders | 否 | 是 | HTTP请求头。默认值为undefined。 |
content | RequestContent | 否 | 是 | HTTP请求内容。默认值为undefined。 |
cookies | RequestCookies | 否 | 是 | HTTP请求的Cookie。将设置转换为HTTP Cookies标头。默认值为undefined。 |
transferRange | TransferRange/TransferRange[] | 否 | 是 | HTTP传输范围。转换为HTTP Range头。带有range头的HTTP请求要求服务器只返回HTTP响应的一部分。默认值为undefined。 |
configuration | Configuration | 否 | 是 | HTTP请求配置。用于覆盖默认或会话范围的设置。默认值为undefined。 |
destination | ResponseBodyDestination | 否 | 是 | HTTP响应体放置位置。起始版本:5.0.0(12) |
简单归类为以下几个部分:
- 必须属性:id、url、method
- 特有属性:content、transferRange、destination
- 可覆盖属性:headers、cookies、configuration
Configuration
Configuration接口包含一组配置参数,是http请求的配置对象,调整会话中http请求的行为包括传输、跟踪、代理、DNS和安全配置等的设置。
接口具体属性如下:
名称 | 类型 | 只读 | 可选 | 说明 |
---|---|---|---|---|
transfer | TransferConfiguration | 否 | 是 | 配置与HTTP请求期间的数据传输相关的各个方面,例如自动重定向和超时设置。 |
tracing | TracingConfiguration | 否 | 是 | 使开发人员能够在HTTP请求期间捕获详细的跟踪信息,有助于调试和性能分析。 |
proxy | ProxyConfiguration | 否 | 是 | 配置会话的代理设置,允许开发人员定义'system'、'no-proxy'或WebProxy配置。 |
dns | DnsConfiguration | 否 | 是 | 指定与DNS相关的配置,包括自定义DNS规则和通过HTTPS使用DNS的选项。 |
security | SecurityConfiguration | 否 | 是 | 包括用于处理安全相关方面的设置,如证书和服务器身份验证。 |
processing | ProcessingConfiguration | 否 | 是 | 响应处理配置。起始版本:5.0.0(12) |
在上述材料中可以看到,Request和Session中都存在Configuration参数,Configuration在这两者中的使用准则如下:
Configuration以Request中设置为准,如果Request没有设置,则以Session为准。
组装参数流程图:
Response
Response接口表示为http请求的响应对象,包含了请求的响应数据,具体属性如下:
名称 | 类型 | 只读 | 可选 | 说明 |
---|---|---|---|---|
request | Request | 是 | 否 | 收到此响应的相关HTTP请求内容。 |
statusCode | number | 是 | 否 | HTTP请求的结果代码。如果回调函数成功执行,将返回ResponseCode中定义的结果代码。否则,将在AsyncCallback中的error字段中返回错误代码。 |
headers | ResponseHeaders | 是 | 否 | 响应头。 |
effectiveUrl | URL | 是 | 是 | 重定向后请求的有效URL。默认值为undefined。 |
body | ArrayBuffer | 是 | 是 | 响应内容根据响应头中的Content-type返回。响应内容必须与服务器返回的数据类型相同。 |
downloadedTo | DownloadedTo | 是 | 是 | 内容下载的路径。在使用下载至文件对象和文件标识符两种方式时,默认情况下,并未设置回调信息。起始版本:5.0.0(12) |
debugInfo | DebugInfo[] | 是 | 是 | 与响应相关联的调试信息。默认值为undefined。 |
timeInfo | TimeInfo | 是 | 是 | HTTP请求各阶段的定时信息。默认值为undefined。 |
cookies | ResponseCookie[] | 是 | 是 | 响应中的Cookie数组。 |
httpVersion | HttpVersion | 是 | 是 | HTTP的版本。起始版本:5.0.0(12) |
reasonPhrase | string | 是 | 是 | HTTP响应状态行的reasonPhrase,提供与数字状态代码相关的文本描述。起始版本:5.0.0(12) |
简单归类为以下几个部分:
- 基础属性:request、statusCode、headers、body、httpVersion、reasonPhrase
- 其他属性: 除了基础属性的属性
封装过程
本次网络请求使用的API为wanAndroid中的开放API
UML
-
类图
-
组件图
-
流程图(以get为例)
网络层
RcpClient
RcpClient
使用全局session,负责在发起网络请求之前需要获取Session以及配置Seesion
const TAG: string = 'SessionConfigManager'
//默认拦截器
export let defaultInterceptors: rcp.Interceptor[] = [
//注册拦截器
]
//默认session config
export let defaultSessionConfig: rcp.SessionConfiguration = {
interceptors: defaultInterceptors, //默认拦截器
baseAddress: 'https://wanandroid.com/',
sessionListener: {
onCanceled: () => LogUtil.d(TAG, `Session was cancelled`),
onClosed: () => LogUtil.d(TAG, `Session was closed`)
},
//配置全局请求config
requestConfiguration: {
tracing: {
verbose: true,
collectTimeInfo: true,
},
transfer: {
timeout: {
connectMs: 60 * 1000
}
}
}
}
//管理当前session以及其config
export class RcpClient {
private session: rcp.Session
private currentConfig: rcp.SessionConfiguration = defaultSessionConfig
constructor() {
//使用默认config
this.session = rcp.createSession(defaultSessionConfig)
}
addInterceptor(interceptor: rcp.Interceptor) {
this.currentConfig.interceptors?.push(interceptor)
}
setSessionListener(listener: rcp.SessionListener) {
this.currentConfig.sessionListener = listener
}
setConnectionConfiguration(connectionConfig: rcp.ConnectionConfiguration) {
this.currentConfig.connectionConfiguration = connectionConfig
}
getInterceptors(): rcp.Interceptor[] {
return this.currentConfig.interceptors ?? []
}
//获取当前config
getDefaultConfig(): rcp.SessionConfiguration {
return this.currentConfig
}
//获取当前session
getSession(): rcp.Session {
return this.session
}
cancel(requestToCancel?: rcp.Request | rcp.Request[]): void {
this.session.cancel(requestToCancel)
}
close(): void {
this.session.close()
}
}
RequestConfig
RequestConfig
作为自定义请求接口,除了正常request需要的参数之外还可以根据业务需求添加字段。
import { rcp } from '@kit.RemoteCommunicationKit'
export interface RequestConfig {
url: rcp.URLOrString
method: rcp.HttpMethod
headers?: rcp.RequestHeaders
content?: rcp.RequestContent
cookies?: rcp.RequestCookies
transferRange?: rcp.TransferRange | rcp.TransferRange[]
configuration?: rcp.Configuration
destination?: rcp.ResponseBodyDestination
shotToast?: boolean
}
BaseModel
BaseModel
作为接收Response数据类
export class BaseModel<T> {
errorMsg: string = ""
errorCode: number = -1
data?: T
}
HttpCall
HttpCall
为网络请求单例类,外部通过export的HttpCall实例对象发起网络请求,使用BaseModel
作为基类数据类接收泛型数据T,发起网络请求前通过NetMonitor
检查当前网络是否可用,根据自定义接口RequestConfig.showToast
判断请求后是否需要吐司,使用rcp.TimeInfo
打印请求过程中的网络阶段耗时,最后exprot全局对象httpCall
暴露给业务层使用。
const TAG: string = 'HttpCall'
class HttpCall {
private static httpCall?: HttpCall
private client: RcpClient = new RcpClient()
private constructor() {
}
static getInstance() {
if (!HttpCall.httpCall) {
HttpCall.httpCall = new HttpCall()
}
return HttpCall.httpCall
}
get<T = Object>(url: rcp.URLOrString, param?: Object, showToast?: boolean): Promise<BaseModel<T>> {
return this.request({
url: url,
method: 'GET',
headers: { "content-type": 'application/json' },
content: param,
shotToast: showToast
})
}
post<T = Object>(url: rcp.URLOrString, param?: Object, showToast?: boolean): Promise<BaseModel<T>> {
return this.request({
url: url,
method: 'POST',
headers: { "content-type": 'application/x-www-form-urlencoded' },
content: param,
shotToast: showToast
})
}
request<T = Object>(requestConfig: RequestConfig): Promise<BaseModel<T>> {
return new Promise((resolve) => {
LogUtil.d(TAG, '请求开始')
//检查当前网络是否可用
if (!NetMonitor.getInstance().isNetWorkAvailable()) {
return
}
//TODO 请求参数处理
this.client.getSession().fetch(this.createCustomRequest(requestConfig)).then((response: rcp.Response) => {
LogUtil.d(TAG, JSON.stringify(response.request))
if (response.statusCode != 200) {
LogUtil.d(TAG, `statusCode error ${response.statusCode}`)
return
}
this.printTimeInfo(response.timeInfo)
let resData = response.toJSON() as BaseModel<T>
//是否需要Toast
if (requestConfig.shotToast && (resData.errorMsg.length != 0)) {
ToastUtil.showToast(resData.errorMsg)
}
if (resData) {
LogUtil.d(TAG, `请求成功: ${response.toString()}`)
resolve(resData)
} else {
LogUtil.d(TAG, `response 异常`)
}
})
})
}
private printTimeInfo(info?: rcp.TimeInfo) {
if (!info) {
LogUtil.d(TAG, `timeInfo is undefined`)
return;
}
let remainderDataTime = info?.totalTimeMs - info?.startTransferTimeMs;
let firstPackageTime = info?.startTransferTimeMs - info?.preTransferTimeMs;
let TLSTime = info?.tlsHandshakeTimeMs - info?.connectTimeMs;
LogUtil.d(TAG, `首包耗时${firstPackageTime}`)
LogUtil.d(TAG, `TLS握手(不包含建连时间)耗时${TLSTime}`)
LogUtil.d(TAG, `接收剩余数据的耗时${remainderDataTime}`)
}
private createCustomRequest(requestConfig: RequestConfig): rcp.Request {
const req: rcp.Request = new rcp.Request(requestConfig.url, requestConfig.method)
req.headers = requestConfig.headers
req.content = requestConfig.content
req.cookies = requestConfig.cookies
req.transferRange = requestConfig.transferRange
req.configuration = requestConfig.configuration
req.destination = requestConfig.destination
return req
}
getClient(): RcpClient {
return this.client
}
}
//对外唯一对象
export let httpCall: HttpCall = HttpCall.getInstance()
NetMonitor
NetMonitor
负责检查当前网络是否可用,监听默认网络状态
业务层
api
api
为API集合
export namespace api {
export const ARTICLE_HARMONY = 'harmony/index/json'
....
}
XxxService
XxxService
根据不同场景下的接口进行区分处理,将不同场景业务下的请求以XxxService表现。
以文章类请求为例子:
import { BaseModel, httpCall } from 'network'
import { api } from '../ApiPath'
export class ArticleService {
static getHarmonyArticle(): Promise<BaseModel<Object>> {
return httpCall.get<Object>(api.ARTICLE_HARMONY, '', true)
}
}
Client调用
客户端根据不同业务需求调用对应Service发起网络请获取指定数据类。
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Column({space: 20}) {
Text(this.message)
Button('Click').onClick(() => {
ArticleService.getHarmonyArticle()
})
}
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
}
效果
ps:文章案例仓库地址
结尾
鸿蒙笑传之RCP到这里就结束了,喜欢的xdm可以点个赞,不喜欢的白字儿们评论区吐口痰在走(你不许说他!他是我die!),不行了活不了了,下期再见,评论区扣1复活博主,爱你们捏💗💗。