一、网络框架

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重新再进行封装和完善功能

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐