使用Kuikly开发OpenHarmony项目-网络层
基于Kuikly框架,开发OpenHarmony项目;实现网络层封装,集成GitCode接口请求,实现GitCode口袋工具app开发
一、网络框架
1.1 Kuikly框架已经带有网络请求模块NetworkModule,看文档比较简单,主要没有有cancel相关的方法;另外在github找到Kuikly的另外的网络请求库KMM Network,但只支持Android、iOS和OpenHarmony

1.2 对比一下文档:
NetworkModule,看Module加载方式都需要通过pager来获取的,尝试直接调用构造方法创建新的NetworkModule,发现请求结果无法返回;而且也没有提供cancel方法。

KMM Network 有提供cancel方法,请求超时是 KMM Network的startTimeoutCheckTask进行处理。本来想用KMM Network的,但再引入Kuikly的协程库进行操作时,会报错。这里后续再详情定位是什么问题。

二、项目网络层设计
2.1 项目结构:

exception封装了跟网络请求相关的异常, 根据不同场景返回对应的异常类型;
open class NetworkException(var1: Throwable? = null): Exception(var1)
///服务端错误
///code 错误码
///msg 错误信息
class ServerException(val code: Int?, val msg: String?): NetworkException() {
}
///请求超时
class TimeoutException: NetworkException() {
}
///数据解析异常
class DataParseException(exception: Exception): NetworkException(exception)
///未知异常
class NetworkErrException(exception: Exception): NetworkException(exception)
///取消请求
class CancelException: NetworkException()
interceptor封装了请求拦截器,在onRequest对请求信息进行修改,在onResponse对响应信息进行处理;
open class BaseInterceptor {
open fun onRequest(request: ApiRequest) {
}
open fun onResponse(response: ApiResponse) {
}
}
HeaderInterceptor对业务层的请求信息进行处理,因为当前项目对接gitCode,参考gitCode在请求头添加accessToken信息;
class HeaderInterceptor: BaseInterceptor() {
override fun onRequest(request: ApiRequest) {
request.headers.put("Content-Type", "application/json")
request.headers.put("Authorization", "Bearer ${AppConfig.token}")
}
override fun onResponse(response: ApiResponse) {
}
}
BaseApi封装了请求逻辑
package com.example.mykuiklyapplication.network
import com.example.mykuiklyapplication.network.exception.CancelException
import com.example.mykuiklyapplication.network.exception.DataParseException
import com.example.mykuiklyapplication.network.exception.NetworkErrException
import com.example.mykuiklyapplication.network.exception.ServerException
import com.example.mykuiklyapplication.network.interceptor.BaseInterceptor
import com.tencent.kuikly.core.manager.PagerManager
import com.tencent.kuikly.core.module.NetworkModule
import com.tencent.kuikly.core.nvi.serialization.json.JSONObject
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
///响应数据解析
typealias ParseAPIData<D> = (responseData: JSONObject) -> D?
object BaseApi {
private val networkModule: NetworkModule
get() {
return PagerManager.getCurrentPager().acquireModule<NetworkModule>(NetworkModule.MODULE_NAME)
}
private var config: ApiConfig? = null
//拦截器
private val interceptorList = mutableListOf<BaseInterceptor>()
//添加拦截器
fun addInterceptor(interceptor: BaseInterceptor) {
interceptorList.add(interceptor)
}
///初始网络请求
fun init(config: ApiConfig? = null) {
this.config = config
}
//请求前,顺序调用拦截器
private fun checkReqInterceptor(req: ApiRequest) {
interceptorList.forEach {
it.onRequest(req)
}
}
//响应后,倒序调用拦截器
private fun checkResponseInterceptor(response: ApiResponse) {
for (index in interceptorList.indices.reversed()) {
interceptorList[index].onResponse(response)
}
}
//将业务层的请求参数封装为ApiRequest
private fun createApiRequest(basePath: String, params: Map<String, Any?>? = null,
url: String? = null, isPost: Boolean = false): ApiRequest {
val newReq = ApiRequest((url ?: config?.baseUrl ?: "") + basePath, isPost = isPost)
params?.let {
newReq.params.putAll(it)
}
newReq.timeout = config?.timeout ?: 20
return newReq
}
//发起请求
private suspend fun <D> req(basePath: String, questParams: Map<String, Any?>? = null, url: String? = null,
isPost: Boolean = false,
cancel: NetworkCancel<D>? = null,
parseData: ParseAPIData<D>? = null
): D? {
val request = createApiRequest(basePath, questParams, url, isPost = isPost)
checkReqInterceptor(request)
val param = JSONObject()
val headers = JSONObject()
for (key in request.params.keys) {
param.put(key, request.params[key])
}
for (key in request.headers.keys) {
headers.put(key, request.headers[key])
}
if (cancel?.isCancel == true) {
throw CancelException()
}
try {
return suspendCancellableCoroutine { continuation ->
cancel?.setCancelObj(continuation)
networkModule.httpRequest(
url = request.url,
isPost = request.isPost,
param = param,
headers = headers,
cookie = request.cookies,
timeout = request.timeout
) { data, success, errorMsg, response ->
try {
val apiResponse = ApiResponse(
data, success, errorMsg, response
)
checkResponseInterceptor(apiResponse)
if (apiResponse.success) {
//数据解析
try {
val resultData = parseData?.invoke(data)
continuation.resume(resultData)
cancel?.setCancelObj(null)
} catch (e: Exception) {
continuation.resumeWithException(DataParseException(e))
cancel?.setCancelObj(null)
}
} else {
continuation.resumeWithException(ServerException(
apiResponse.response.statusCode,
msg = apiResponse.errorMsg
))
cancel?.setCancelObj(null)
}
} catch (e: Exception) {
continuation.resumeWithException(NetworkErrException(e))
cancel?.setCancelObj(null)
}
}
}
} catch (e: Exception) {
throw NetworkErrException(e)
}
}
suspend fun <D> reqGet(basePath: String, params: Map<String, Any?>? = null, url: String? = null,
cancel: NetworkCancel<D>? = null,
parseData: ParseAPIData<D>? = null): D? {
return req(basePath, params, url, parseData = parseData)
}
suspend fun <D> reqPost(basePath: String, body: Map<String, Any?>? = null, url: String? = null,
cancel: NetworkCancel<D>? = null,
parseData: ParseAPIData<D>? = null
): D? {
return req(basePath, body, url, isPost = true, parseData = parseData)
}
}
ApiConfig类封装初始化数据,在BaseApi的init方法传入进行初始化
package com.example.mykuiklyapplication.network
///网络基础配置
data class ApiConfig(
val baseUrl: String, //请求域名
val timeout: Int //请求超时时间
)
ApiRequest封装业务层传递过来的数据信息
data class ApiRequest(
val url: String,
val isPost: Boolean = false,
val params: MutableMap<String, Any?> = mutableMapOf<String, Any?>(),
val headers: MutableMap<String, Any?> = mutableMapOf<String, Any?>(),
var cookies: String? = null,
var timeout: Int = 20,
)
ApiResponse封装响应信息
data class ApiResponse(
val data: JSONObject,
val success : Boolean,
val errorMsg: String,
val response: NetworkResponse
)
ApiCancel封装对请求的取消操作
class ApiCancel<D> {
private var hadCancel = false
val isCancel: Boolean
get() = hadCancel
private var cancelObj: CancellableContinuation<D>? = null
fun setCancelObj(obj: CancellableContinuation<D>?) {
cancelObj = obj
}
fun cancel() {
hadCancel = true
if (cancelObj?.isActive == true) {
cancelObj?.cancel(CancelException())
}
cancelObj = null
}
}
Api封装具体的接口请求,通过ParseData将响应数据返回,然后转换为对应的model类
object Api {
fun init() {
BaseApi.init(ApiConfig(
baseUrl = AppConfig.baseUrl,
timeout = AppConfig.urlTimeout,
))
BaseApi.addInterceptor(HeaderInterceptor())
}
//搜索用户
suspend fun searchUsers(keywords: String? = null, page: Int = 1, count: Int = 20,
cancel: ApiCancel<List<UserModel>>? = null,
): List<UserModel>? {
return BaseApi.reqGet<List<UserModel>>("search/users",
mapOf("q" to keywords,
"page" to page, "pre_page" to count
), cancel = cancel) {
parseUsers(it.optJSONArray("data")!!)
}
}
//搜索仓库
suspend fun searchRepositories(keywords: String? = null, page: Int = 1, count: Int = 20,
cancel: ApiCancel<List<RepositoryModel>>? = null,
): List<RepositoryModel>? {
return BaseApi.reqGet<List<RepositoryModel>>("search/repositories",
mapOf("q" to keywords,
"page" to page, "pre_page" to count
), cancel = cancel) {
parseRepositories(it.optJSONArray("data")!!)
}
}
}
三、GitCode口袋工具开发
3.1 对OpenHarmony开启网络权限

3.2 GitCode访问令牌获取, 调用GitCode的接口需要用到,在HeaderInterceptor添加到请求头中
相关接口可以查看GitCode的文档:https://docs.gitcode.com/docs/apis/

3.3 界面开发:因为对Kuikly的布局、组件封装还不太熟悉,简单实现了界面,主要把请求逻辑跑通;使用ComposeView封装用户组件UsersView、仓库组件RepositoriesView、搜索组件SearchInputView
输入内容后点击Search,把输入的内容返回给UsersView或者RepositoriesView

然后在onSearch中调用接口请求,获取到数据后拼接成字符串进行显示

运行后大概得效果:


代码已经上传到:https://gitcode.com/saicheng/OHOSGitCodeApp
四、总结
基于Kuikly框架,封装了基本的网络请求逻辑,完成简单的页面和进行接口请求;
后续考虑使用KMM Network重新再进行封装和完善功能
更多推荐


所有评论(0)