自研一套带双向认证的Android通用网络库

2024-06-14 02:36

本文主要是介绍自研一套带双向认证的Android通用网络库,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

当前,许多网络库基于Retrofit或OkHttp开发,但实际项目中常需要定制化,并且需要添加类似双向认证等安全功能。这意味着每个项目都可能需要二次开发。那么,有没有一种通用的封装方式,可以满足大多数项目需求?本文将介绍一种通用化的封装方法,帮助你用最少的代码量开发出自己的网络库

框架简介

FlexNet 网络库是基于 Square 公司开源的 Retrofit 网络框架进行封装的。Retrofit 底层采用 OkHttp 实现,但相比于 OkHttp,Retrofit 更加便捷易用,尤其适合用于 RESTful API 格式的请求。

在网络库内部,我们实现了双向认证功能。在初始化时,您可以选择是否开启双向认证,框架会自动切换相应的 URL,而业务方无需关注与服务端认证的具体细节。

接入方式

1. 本地aar依赖

下载aar到本地(下载地址见文末),copy到app的libs目录下,如图:

image.png

implementation(files("libs/flex-net.aar"))

然后sync只会即可

2. 通过Maven远程依赖

FlexNet目前已上传Maven,可通过Maven的方式引入,在app的build.gradle中加入以下依赖:

implementation("com.max.android:flex-net:3.0.0")

sync之后即可拉到Flex-Net

快速上手

网络库中默认打开了双向认证,并根据双向认证开关配置了相应的 baseUrl,大多数场景下只需要控制双向认证开关,其余配置走默认即可。

  1. 初始化

在发起网络请求之前(建议在ApplicationonCreate()中),调用:

fun initialize(app: Application,logEnable: Boolean = BuildConfig.LOG_DEBUG,sslParams: SSLParams? = null,
)
  • application: Application类型,传入当前App的Application实例;
  • logEnable: Boolean类型,网络日志开关,会发打印Http的Request和Resonpse信息,可能涉及敏感数据,release包慎用;(仅限网络请求日志,和双向认证的日志不同)
  • sslParams: 双向认证相关参数,可选,为空则关闭双向认证。具体描述见下文。

当App需要双向认证功能时,需要在initialize()方法中传递sslParams参数,所有双向认证相关的参数都放在sslParams当中,传此参数默认打开双向认证。

SSLParams的定义如下:

data class SSLParams(/** App 是否在白名单之中。默认不在 */val inWhiteList: Boolean = false,/** 双向认证日志开关,可能涉及隐私,release版本慎开。默认关 */val logSwitch: Boolean = true,/** 是否开启双向认证。默认开 */val enable: Boolean = true,/** 双向认证回调。默认null */val callback: MutualAuthCallback = null,
)
  • inWhiteList: App是否在白名单中,默认不在
  • logSwitch: 双向认证日志开关,可能涉及隐私,release版本慎开。默认关,注意这里仅针对双向认证日志,与initialize()方法中的logEnable不同
  • callback 监听初始化结果回调,true表示成功,反之失败。可选参数,默认为null,仅enableMutualAuth为true时有效

在调用了initialize之后就完成了初始化工作,内部包含了双向认证、网络状态、本地网络缓存等等功能,所有的网络请求都需要在初始化之后发起。

初始化示例代码:

LotusNetManger.initialize(this,logEnable = true,SSLParams {Timber.i("Mutual auth result : $it")})

PS * *部分App在启动的时候获取不到证书,所以这里会失败。如果失败了后续可以在合适的时机通过MutualAuthenticate.isSSLReady()来检查是否认证成功,然后通过MutualAuthenticate.suspendBuildSSL()来主动触发双向认证,成功之后方可开始网络请求。具体可参见文档“配置项”的内容。

双向认证失败及其相关问题,可参考双向认证文档 双向认证

  1. 定义数据 Model

在请求之前需要根据接口协议的字段定义对应的数据Model,用来做Request或者Response的body。

比如我们需要通过UserId获取对应用户的UserName

  1. 定义 Request 数据 Model

后端请求接口参数如下:

{"userId" : "123456"
}

那么根据参数定义一个UserNameReq类:

data class UserNameReq(/** 用户id */
var userId: String
)
  1. 定义 Response 数据 Model

后端返回数据如下:

{"userName" : "MC"
}

对应定义一个UserNameRsp:

data class UserNameRsp(/** 用户id */
var userId: String
)
  1. 编写 Http 接口

接口类必须继承自IServerAPI:

interface UserApi: IServerApi

然后在IServerApi的实现类中,每个接口需要用注解的形式标注 Http 方法,通过参数传入 http 请求的 url:

interface UserApi: IServerApi {/** 获取用户ID */@POST("api/cloudxcar/atmos/v1/getName")suspend fun getUserName(@Body request: UserNameReq): ResponseEntity<UserNameRsp>
}

这里需要注意的是,我们的UserNameRsp需要用ResponseEntity封装一层,看一下ResponseEntity的内容:

sealed class ResponseEntity<T>(val body: T?, val code: Int, val msg: String)

有3个参数:

  • body: 消息体,即UserNameReq。仅成功时有效

  • code 返回码,这里要分多种情况描述。

    • Http错误:此时code为Http错误码
    • 其他异常:code对应错误原因,后面会附上映射表
    • 请求成功:区分网络数据和缓存数据
  • msg 错误信息

可调用ResponseEntity.isSuccessful()来判断是否请求成功,然后通过ResponseEntity.body获取数据,返回的是一个根据服务端返回的 Json 解析而来的UserNameRsp实体类。

如果请求失败,则从ResponseEntity.msgResponseEntity.code中获取失败ma失败码和失败提示

  1. 创建网络请求Repo

继承自BaseRepo,泛型参数为步骤3中创建的IserverApi实现类:

class VersionRepo : BaseRepo<VersionAPI>
  1. 其中需要有1个必覆写的变量:

    1. baseUrl: 网络接口的baseUrl
  2. 两个可选项:

    1. mutualAuthSwitch: 双向认证开关,此开关仅针对当前 baseUrl 生效。默认开
    2. interceptorList: 需要设置的拦截器列表
  3. 一个必覆写的方法:

    1. createRepository(): 创建当前网络仓库

完整的Repo类内容如下:

class UserRepo: BaseRepo<UserApi>() {// 必填override val baseUrl = "https://lum1.lotuscars.com.cn/"// 必填override fun createRepository(): VersionAPI =MutualAuthenticate.getServerApi(baseUrl, mutualAuthSwitch, interceptorList)// 可选:双向认证开关,仅针对当前repo生效override val mutualAuthSwitch = true// 可选:Http拦截器override val interceptorList: List<Interceptor>? = listOf(HeaderInterceptor())// 请求接口suspend fun getUserName(): ResponseEntity<UserNameRsp>{return mRepo.upgradeVersion(UserNameReq("123456"))}
}

注: 其中拦截器的设置interceptorList,如果声明的时候提示错误,可以尝试加上完整的类型声明:

interceptorList: List<Interceptor>?

5 发起网络请求

最后就可以在业务代码中通过Repo类完成网络请求的调用了:

lifecycleScope.launch {val entity= UserRepo().getUserName()Timber.i("Get responseEntity: $entity")if (entity.isSuccessful()) {val result = entity.bodyTimber.i("Get user name result: $result")} else {val code = entity.codeval msg = entity.msgTimber.i("Get user name failed: code->$code; msg->$msg")}
}

到这里,就可以发起一次基础的网络请求接口了。

依赖项

  1. 双向认证

implementation("com.lotus.ssl:ssllib:$sslVersion")

目前引入的双向认证版本为1.6.0,如果需要切换版本,或者编译出现依赖冲突,可以尝试使用exclude的方式自行依赖。当然也请自行确保功能正常。

  1. 日志库

api(project(":lotus-tool-timber:timber"))

组件库中的日志库。LotusNet推荐宿主使用Timber进行日志输出,但是需要宿主App在初始化LotusNet之前对Timber做plant操作。

  1. 网络请求内核

// Net
api(core.network.okhttp)

底层网络请求目前依赖OkHttp完成。

  1. 本地持久化

implementation("com.tencent:mmkv:1.2.14")

网络库中的本地存储,主要用于保存网络缓存,目前采用MMKV-1.2.14版本,同样如果有冲突,或者需要另换版本,可通过exclude实现。

  1. Gson

api(core.network.retrofit.gson) {exclude(module = "okio")exclude(module = "okhttp")
}

依赖Gson,用于做数据结构和Json的相互转化

错误码对照表

CODE_SUCCESS10000请求成功,数据来源网络
CODE_SUCCESS_CACHE10001返回成功,数据来源于本地缓存
CODE_SUCCESS_BODY_NULL10002请求成功,但消息体为空
CODE_ERROR_UNKNOWN-200未知错误
CODE_ERROR_UNKNOWN_HOST-201host解析失败,无网络也属于其中
CODE_ERROR_NO_NETWORK-202无网络

日志管理

从LotusNet 2.0.5开始,对接入方使用的日志库不再限制(2.0.5以下必须用Timber,否则无日志输出)。可以通过以下接口来设置日志监视器:

setLogMonitor(log: ILog)

设置之后所有的网络日志都会回调给ILog,即可由接入方自行决定如何处理日志数据。

如果没有设置LogMonitor,则会使用Timber或者Android原生Log来进行日志输出。当宿主App的Timber挂载优先于LotusNet的初始化,则会采用Timber做日志输出,反之使用Android Log。

文件下载

网络库内置了下载功能,可配置下载链接和下载目录。注意外部存储地址需要自行申请系统权限。

1 构建下载器

使用Downloader.builder()来构建你的下载器,Builder需要传入以下参数:

  • url:待下载文件的url
  • filePath:下载文件路径
  • listener:下载状态回调。可选参数,空则无回调

示例代码如下:

Downloader.Builder("https://user-manual.lotuscars.com.cn/lotusResource/2023-06-14/appTis/99daf0ca-8eee-412c-bf69-134f36e61dba.zip",File(requireContext().filesDir, "MC").absolutePath)

2 回调监听

builder()最后一个参数,可传入下载监听器接口DownloadListener,内部有3个方法需要实现:

  • onFinish(file: File): 下载完成,返回下载完成的文件对象
  • onProgress( progress : Int, downloadedLengthKb: Long, totalLengthKb: Long): 下载进度回调,回传进度百分比、已下载的大小、总大小
  • onFailed(errMsg: String?): 下载失败,回调失败信息

示例代码如下:

val downloader = Downloader.Builder("https://user-manual.lotuscars.com.cn/","lotusResource/2023-06-14/appTis/99daf0ca-8eee-412c-bf69-134f36e61dba.zip",File(Environment.getExternalStorageDirectory(), "MC").absolutePath,object : DownloadListener {override fun onFinish(file: File) {Timber.e("下载的文件地址为:${file.absolutePath}".trimIndent())}override fun onProgress(progress: Int,downloadedLengthKb: Long,totalLengthKb: Long,) {runOnUiThread {textView.text ="文件文件下载进度:${progress}% \n\n已下载:%${downloadedLengthKb}KB | 总长:${totalLengthKb}KB"}}override fun onFailed(errMsg: String?) {Timber.e("Download Failed: $errMsg")}}).build()

PS 这里要注意,Lotus-Net会在业务方调用下载的线程返回下载回调,所以绝大部分时候回调是发生在子线程,此时如果有线程敏感的功能(比如刷新UI),需要自行处理线程切换。

3 触发下载

通过Builder.build()创建 Downloader 下载器,最后调用Downloader.download()方法即可开始下载。

和Http Request一样,download()是一个suspend方法,需要在协程中使用:

lifecycleScope.launch(Dispatchers.IO) {downloader.download()
}

整体架构

设置配置项

  1. 设置双向认证开关

在初始化的时候控制双向认证开关:

fun init(context: Application, needMutualAuth: Boolean = true)

方法内部会根据开关值来切换不同的后端服务器,但是有些App不能过早的获取证书,这样会有双向认证失败的风险,Lotus-Net同时支持懒汉式的主动双向认证

  1. 主动双向认证接口

在确定拿到证书,或者确定可以双向认证的时机,可随时发起双向认证请求:

MutualAuthenticate.suspendBuildSSL()

可通过

MutualAuthenticate.isSSLReady()

接口来检查当前双向认证是否成功。

主动触发示例代码如下:

MutualAuthenticate.suspendBuildSSL {if (it) {Toast.makeText(context, "双向认证成功,可以开始访问加密资源", Toast.LENGTH_SHORT).show()} else {Toast.makeText(context, "双向认证失败", Toast.LENGTH_SHORT).show()}
}
  1. 替换 BaseUrl

在网络库内部预置了 Lotus 的后端Host地址:

https://scc-api-uat.lotuscars.com.cn/

https://scc-uat.lotuscars.com.cn

两个分别对应带双向认证的接口和不带双向认证的接口,大多数App可以直接使用这两个 host 作为BaseUrl 完成网络请求。

网络库内部封装了一个 OKHttp client,默认使用了以上 url 作为 BaseUrl,同时网络库支持配置自定义的 BaseUrl,可使用以下接口重新获取一个新的Client:

fun obtainClient(baseUrl: String, mutualAuthenticate: Boolean = true): ServerApi 

传入自定义 BaseUrl 及双向认证开关即可,后续网络请求使用新的Client即可。

  1. 数据缓存

在前面发起请求调用httpRequest顶层函数的时候,可以传入一个可选参数cacheKey,这个key不为空则网络库会在本地保存当前请求的返回数据。Key作为缓存的唯一标识,在无网络或请求失败的时候,会通知调用方错误,并返回缓存的数据。

缓存部分流程如下:

  1. 错误及异常处理

在发起请求的顶层函数 httpRequest 中,有两个参数用来提供给调用方处理错误和异常。

首先区分一下错误和异常:

错误通常是发起了网络请求,且网络请求有响应,只是由于接口地址或者参数等等原因导致服务端解析失败,最终返回错误码及错误信息。

而异常是指在发起网络请求的过程中出现了 Exception,导致整个网络请求流程被中断,所以当异常发生的时候,网络库是不会返回错误码和错误信息的,只能返回异常信息供调用方定位问题。

回调的使用方式很简单,只需要在httpRequest中传入两个回调:failerror,下面分别看看二者的处理方式:

  1. 错误处理

fai的定义如下:

fail: (response: ResponseEntity<T>) -> Unit = {onFail(it)
}

传入的回调有一个 ResponseEntity 参数,这是网络请求返回的响应实体,内部包含errorCodeerrorMessage,不传则默认打印这两个字段,可以在 Logcat 中通过Tag:Http Request **过滤出来。

  1. 异常处理

error的定义如下:

error: (e: Exception) -> Unit = {onError(it)
} ,

回调函数只有一个 Exeption 对象,和前面的定义相符,在异常的时候将异常返回供调用方定位问题。不传网络库默认打印异常,可以在 Logcat 中通过Tag:Http Request **过滤出来。

扩展接口:发起请求并处理返回结果

网络库定义了一个顶层函数用来发起请求并接收返回结果或处理异常:

fun <reified T> httpRequest(block, fail, error, cacheKey): T?

  • block: 实际请求体,必填。可以传入步骤 4 中实现的接口
  • fail: 请求错误回调,非必填。用来处理服务端返回的请求错误,会携带错误码及错误信息
  • error: 请求异常回调,非必填。用来处理请求中发生的异常,此时没有response返回
  • cacheKey: 数据缓存唯一标识,非必填

httpRequest 中的泛型 T 就是接入步骤2定义的 Response 实体,正常返回会在方法内部自动解析出 UserNameRsp,到此就完成了一次网络请求。

以上是基本的使用方式,涵盖了安全、数据请求、缓存、异常处理等功能,可以适应于多种项目场景。应大家的建议,后续会完善几篇文章拆解具体的原理及开发思路,从源码的角度教你如何从0开发一套完善的网络库

这篇关于自研一套带双向认证的Android通用网络库的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1059134

相关文章

springboot security使用jwt认证方式

《springbootsecurity使用jwt认证方式》:本文主要介绍springbootsecurity使用jwt认证方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录前言代码示例依赖定义mapper定义用户信息的实体beansecurity相关的类提供登录接口测试提供一

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

使用Python高效获取网络数据的操作指南

《使用Python高效获取网络数据的操作指南》网络爬虫是一种自动化程序,用于访问和提取网站上的数据,Python是进行网络爬虫开发的理想语言,拥有丰富的库和工具,使得编写和维护爬虫变得简单高效,本文将... 目录网络爬虫的基本概念常用库介绍安装库Requests和BeautifulSoup爬虫开发发送请求解

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

SpringSecurity 认证、注销、权限控制功能(注销、记住密码、自定义登入页)

《SpringSecurity认证、注销、权限控制功能(注销、记住密码、自定义登入页)》SpringSecurity是一个强大的Java框架,用于保护应用程序的安全性,它提供了一套全面的安全解决方案... 目录简介认识Spring Security“认证”(Authentication)“授权” (Auth

Android WebView无法加载H5页面的常见问题和解决方法

《AndroidWebView无法加载H5页面的常见问题和解决方法》AndroidWebView是一种视图组件,使得Android应用能够显示网页内容,它基于Chromium,具备现代浏览器的许多功... 目录1. WebView 简介2. 常见问题3. 网络权限设置4. 启用 JavaScript5. D