美文网首页Android技术知识Android开发Android开发
手把手教你搭建android项目框架(四)network网络库封

手把手教你搭建android项目框架(四)network网络库封

作者: 支离破碎_SuperLee | 来源:发表于2023-08-25 12:19 被阅读0次

    前面我们说到,目前网络框架选择基本都为retrofit,目前算是最好用的android网络框架之一了。
    今天我们来封装一下retorit,让他更加好用。

    以下代码使用的lifecycleScope,均可使用viewModelScope。本文发布时均在activity中进行代码测试,正常开发过程中应使用viewmodel

    先看效果

     //最简单的get请求
            repo {
                api { "https://www.baidu.com/" }
            }.request<StringBaseResponse>(lifecycleScope) { result ->
                //以下方法不使用可忽略
                result.onSuccess {
                    //do something
                }
                result.onFailure {
                    //do something
                }
            }
    
    //带参数的请求
      repo {
                api { "https://www.baidu.com/" }
                params { "a" to "b" }
                params { "c" to "d" }
                requestMode { RequestMode.GET }
            }.request<StringBaseResponse>(lifecycleScope) { result ->
                //以下方法不使用可忽略
                result.onSuccess {
                    //do something
                }
                result.onCancel {
                    //do something
                }
                result.onCompletion {
                    //do something
                }
                result.onFailure {
                    //do something
                }
            }
    
    //并发请求,一个失败全失败
      val repo1 = repo {
                api { "https://www.baidu.com/" }
                params { "a" to "b" }
                params { "c" to "d" }
                requestMode { RequestMode.GET }
            }
            val repo2 = repo {
                api { "https://www.github.com/" }
                params { "a" to "b" }
                params { "c" to "d" }
                requestMode { RequestMode.GET }
            }
            lifecycleScope.request<StringBaseResponse, StringBaseResponse>(repo1, repo2) { result ->
                //以下方法不使用可忽略
                result.onSuccess {
                    val r1 = it.first
                    val r2 = it.second
                    //do something
                }
                result.onCancel {
                    //do something
                }
                result.onCompletion {
                    //do something
                }
                result.onFailure {
                    //do something
                }
            }
    

    下面开始封装思路,retrofit创建就不多说了,直接看代码

    object RetrofitClient {
      //这里我们填写自己需要的主域名,当然后期可更换,因此按规则填一个就行,否则创建retrofit会报错。
        private const val BASE_URL = "http://www.baidu.com"
    
        private fun provideOkHttpClient(): OkHttpClient {
            return OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
                .build()
        }
    
        fun provideRetrofit(): Retrofit {
            return Retrofit.Builder()
                .client(provideOkHttpClient())
                .baseUrl(BASE_URL)
    //这里我们不使用gsonconvertadapter,我们先都取String,后续我们再说为什么
                .addConverterFactory(StringConverterFactory())
                .build()
        }
    }
    
    class StringConverterFactory : Converter.Factory() {
        override fun responseBodyConverter(
            type: Type,
            annotations: Array<out Annotation>,
            retrofit: Retrofit
        ): Converter<ResponseBody, *> {
            return Converter<ResponseBody, String> { value -> value.string() }
        }
    }
    

    request属性类封装

    //这里的属性可能不全,需要的自行添加
    class BaseRequest {
        var api: String? = null
    
        var requestMode: Int = RequestMode.GET
    
        var file: File? = null
    
        private var params = mutableMapOf<String, Any>()
    
        var contentType: MediaType? = null
        
        fun api(init: () -> String) {
            api = init()
        }
    
        fun requestMode(init: () -> Int) {
            requestMode = init()
        }
    
        fun file(init: () -> File) {
            file = init()
        }
    
        fun params(init: () -> Pair<String, Any>) {
            val p = init()
            params[p.first] = p.second
        }
    
        fun contentType(init: () -> MediaType?) {
            contentType = init()
        }
    
        override fun toString(): String {
            return "api:$api \n params :$params"
        }
    
        fun reflectParameters(): MutableMap<String, Any> {
            return params
        }
        
    }
    

    repo请求类封装,这里的思路是将请求包裹成一个类,然后利用请求的各种参数进包裹后,使用固定的apiService进行请求。

    class Repo {
        var req: BaseRequest = object : BaseRequest() {}
    
        private fun injectApiService(): Api {
            return RetrofitClient.provideRetrofit().create(Api::class.java)
        }
    
        suspend fun execute() = withContext(Dispatchers.IO) {
            val request = req
            val params = request.reflectParameters()
            val apiService = injectApiService()
    
            val api = request.api ?: throw RuntimeException("repo没有传入地址")
            when (request.requestMode) {
                RequestMode.MULTIPART -> {
                    val requestBody: RequestBody =
                        request.file?.asRequestBody(request.contentType)
                            ?: throw RuntimeException("execute MULTIPART 时,file 不能为空")
                    apiService.uploadFile(
                        api,
                        MultipartBody.Part.createFormData(
                            "uploadFile",
                            request.file?.name,
                            requestBody
                        ),
                    )
                }
    
                RequestMode.POST -> {
                    apiService.post(
                        api,
                        buildBody(params)
                    )
                }
    
                RequestMode.GET -> {
                    apiService.get(
                        api,
                        buildBody(params)
                    )
                }
    
                RequestMode.PUT -> {
                    apiService.put(
                        api,
                        buildBody(params)
                    )
                }
    
                RequestMode.DELETE -> {
                    apiService.delete(
                        api,
                        buildBody(params)
                    )
                }
    
                RequestMode.DELETE_BODY -> {
                    apiService.deleteBody(
                        api,
                        buildBody(params)
                    )
                }
    
                else -> {
                    throw UnsupportedOperationException("不支持的requestMode")
                }
            }
        }
    
        private fun buildBody(body: MutableMap<String, Any>): MutableMap<String, Any> {
            return body
        }
    
    }
    

    上述常规封装完成,下面开始优化,我们使用kotlin的扩展方法让repo创建支持dsl,并且能够简化调用。
    首先来看repo创建

    inline fun repo(init: BaseRequest.() -> Unit): Repo {
        val repo = Repo()
        val req = BaseRequest()
        req.init()
        repo.req = req
        return repo
    }
    //通过扩展方法可以在任意类中创建repo,并且让repo初始化baseRequest属性类时支持dsl
    

    接下来,将retrofit请求结果转换为flow,便于操作

    //这里我们不处理异常,异常正常抛出即可,接下来通过flow的操作符进行异常处理
    inline fun <reified T : BaseResponse> Repo.toFlow() = flow {
        val jsonResult = execute()
        //接收String类型返回值,特殊处理
        if (T::class.java == StringBaseResponse::class.java) {
            emit(StringBaseResponse(jsonResult) as T)
        } else {
            val rsp: T = jsonResult.toObject<T>()
                ?: throw JsonSyntaxException("请求网络执行结果转化为object失败")
            emit(rsp)
        }
    }.flowOn(Dispatchers.IO)
    

    拿到flow后,我们通过flow操作符进行操作,并处理异常。

    inline fun <reified T : Any> Flow<T>.completedIn(
        scope: CoroutineScope,
        crossinline callback: (r: Result<T>) -> Unit
    ) {
        this@completedIn.catch {
            val pair = it.toPair()
            if (pair.first == NetworkExceptionConstantCode.CANCEL) {
                callback(Result.Cancel())
            } else {
                callback(Result.Failure(pair))
            }
        }.onEach {
            callback(Result.Success(it))
        }.catch {
            val pair = it.toPair()
            if (pair.first == NetworkExceptionConstantCode.CANCEL) {
                callback(Result.Cancel())
            } else {
                callback(Result.Failure(pair))
            }
        }.onCompletion {
            callback(Result.Completion())
        }.launchIn(scope).start()
    }
    

    将上述两个方法通过一个request方法关联,便可达到文章开头的使用效果

    inline fun <reified T : BaseResponse> CoroutineScope.request(
        repo: Repo, crossinline callback: (r: Result<T>) -> Unit
    ) {
        repo.toFlow<T>().completedIn(this, callback)
    }
    

    如此,我们便完成了retrofit的基础封装,本封装较为基础,如有高级操作请自行添加。
    完整代码:项目链接

    相关文章

      网友评论

        本文标题:手把手教你搭建android项目框架(四)network网络库封

        本文链接:https://www.haomeiwen.com/subject/uofmmdtx.html