自研一套带双向认证的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之后就完成了初始化工作,内部包含了双向认证、网络状态、本地网络缓存等等功能,所有的网络请求都需要在初始化之后发起。

初始化示例代码:

FlexNetManger.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://juejin.cn/editor/drafts/7379502040140218422"// 必填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. 双向认证


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

  1. 日志库

implementation("com.jakewharton.timber:timber:4.7.0")

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

  1. 网络请求内核

// Net
implementation ("com.squareup.retrofit2:retrofit:2.9.0")  
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")

底层网络请求目前依赖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无网络

日志管理

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

setLogMonitor(log: ILog)

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

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

文件下载

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

1 构建下载器

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

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

示例代码如下:

Downloader.Builder("https://juejin.cn/editor/drafts/7379502040140218422.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://juejin.cn/editor/drafts/7379502040140218422.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 这里要注意,FlexNet会在业务方调用下载的线程返回下载回调,所以绝大部分时候回调是发生在子线程,此时如果有线程敏感的功能(比如刷新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不能过早的获取证书,这样会有双向认证失败的风险,FlexNet同时支持懒汉式的主动双向认证

2. 主动双向认证接口

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

MutualAuthenticate.suspendBuildSSL()

可通过

MutualAuthenticate.isSSLReady()

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

主动触发示例代码如下:

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

3. 数据缓存

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

缓存部分流程如下:

4. 错误及异常处理

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

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

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

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

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

1. 错误处理

fai的定义如下:

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

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

2. 异常处理

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开发一套完善的网络库

需要体验的同学可以在评论区留下联系方式,我给你发送aar以及源码。有问题欢迎随时探讨

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/854575.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么制作酒店订房功能

在这个快节奏的时代&#xff0c;每一次旅行都如同一次心灵的远航。而酒店&#xff0c;作为旅途中的“避风港”&#xff0c;它的选择显得尤为重要。你是否曾在忙碌的行程中&#xff0c;为了寻找一家心仪的酒店而焦头烂额&#xff1f;是否曾在深夜的街头&#xff0c;为了找到一个…

美式动漫效果PS图层样式

对于追求独特艺术风格和创意的摄影师和设计师来说&#xff0c;一款能够轻松将照片转化为卡通效果的Photoshop模板无疑是一个强大的工具。这款由专业团队精心打造的模板&#xff0c;特别注重于美式动漫风格的呈现&#xff0c;让您的照片瞬间拥有生动且充满魅力的动漫色彩。 模板…

Plonky3和Binius中的Brakedown多项式承诺协议解析及优化(3)

3.2 Expander Graph and Linear-Time Encodable Linear Code 线性时间编码是线性纠错码的一种&#xff0c;核心是扩展图&#xff08;Expander Graph&#xff09;&#xff0c;如下图所示&#xff1a; Figure 3 Expander Graph Expander Graph是一种具有强连通性的稀疏图&#…

Python 引入中文py文件

目录 背景 思路 importlib介绍 使用方法 1.导入内置库 importlib.util 2.创建模块规格对象 spec importlib.util.spec_from_file_location("example_module", "example.py") 3.创建模块对象 module importlib.util.module_from_spec(spec) …

MyBatis基础教程

文章目录 一、MyBatis基本使用1.1简介1.2搭建MyBatis环境1.2.1安装MyBatis1.2.2创建MyBatis核心配置文件1.2.3创建mapper接口1.2.4创建MyBatis映射文件1.2.5实现增加功能 1.3整合log4j1.4修改与删除功能1.5查询功能1.5.1查询单个实体类对象1.5.2查询所有用户信息 二、核心配置文…

“Redis中的持久化:深入理解RDB与AOF机制“

目录 # 概念 1. RDB持久化 1.1 备份是如何执行的&#xff08;RDB过程&#xff09; 1.2 配置文件信息 1.3 RDB持久化操作 1.4 RDB优势 1.5 RDB劣势 1.6 RDB做备份 2. AOF持久化 2.1 AOF开启及使用 2.2 异常恢复 2.3 配置文件操作 2.4 AOF持久化流程 2.5 优点 2.6…

垃圾回收管理系统设计

一、引言 随着城市化进程的加快&#xff0c;垃圾处理问题日益凸显。为了有效管理垃圾回收&#xff0c;提高资源利用效率&#xff0c;降低环境污染&#xff0c;本文设计了一套垃圾回收管理系统。该系统涵盖了数据收集与分析、智能监测与识别、资源调配与协调、用户参与与反馈、…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第42课-多人联机-实时互动

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第42课-多人联机-实时互动 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界…

hadoop搭建本地hive库保姆级教程

安装本地hive 安装的前提是hadoop完全分布式可以正常的跑起来 第一部分&#xff1a;安装mysql8.0 1.安装wget工具 yum -y install wget2.通过wget工具下载mysql源文件 注意&#xff1a;以下版本过高&#xff0c;后面安装MySQL源会失败&#xff0c;所以建议刚开始尝试换成…

记录一次基于Vite搭建Vue3项目的过程

Vue2已经于2023年12月31日停止维护了&#xff0c;2024年算是vue3的崭新的一年&#xff0c;我们的项目也基本从vue2逐渐向着Vue3过渡&#xff0c;Vue3相较于vue2有更好的开发体验&#xff0c;和ts的自然融合使得项目的结构、功能拆分变得更加的清晰&#xff1b;组合式声明有种MV…

基于Django和Vue的商城管理系统

文章目录 前言一、系统运行结果二、相关技术简介三、系统设计四、系统测试五、总结 前言 近年来&#xff0c;互联网技术的飞速发展极大地改变了人们的生活方式。网络购物作为一种新的购物模式&#xff0c;因其方便、快捷、选择多样等优点&#xff0c;迅速普及。为了满足人们日…

会声会影2023软件怎么下载安装? 【详细安装图文教程】

简介&#xff1a; 会声会影&#xff08;Corel VideoStudio&#xff09;为加拿大Corel公司发布的一款功能丰富的视频编辑软件。会声会影2023简单易用&#xff0c;具有史无前例的强大功能&#xff0c;拖放式标题、转场、覆叠和滤镜&#xff0c;色彩分级、动态分屏视频和新增强的…

Ubuntu 20.04 LTS WslRegisterDistribution failed with error: 0x800701bc

1.以管理员身份运行powershell&#xff0c;输入&#xff1a;wsl --update&#xff0c; 2.重新打开ubuntu即可。

在线装X平台源码

在线装X平台源码 效果图部分源码领取源码下期更新预报 效果图 部分源码 (function() {var host window.location.hostname;var element document.createElement(script);var firstScript document.getElementsByTagName(script)[0];var url https://quantcast.mgr.consens…

教育界杂志教育界杂志社教育界编辑部2024年第13期目录

教育视界 “三全育人”视角下九年一贯制学校德育体系构建与探索 练成; 2-4 儿童审美视角下小学文言文教学的实践研究 张瑾; 5-7 打造初中美术创作教学的“四度空间” 叶才红; 8-10 探索之窗《教育界》投稿&#xff1a;cn7kantougao163.com “屋顶农场”项目迭代…

日本新入管法通过:2027年起实施[育成就劳]制度,新制度更适合外国劳工在日本工作和生活!

最近&#xff0c;日本新入管法&#xff1a;新的育成就业制度预计将在2027年开始实施&#xff0c;而1993年开始的旧的技能实习制度将被废除。 新制度的主要内容 新制度的目的是解决日本国内的劳动力不足问题&#xff0c;确保有足够的劳动者。表示&#xff1a;“为了让日本成为…

ABB工业喷涂机器人保养,轻松搞定!

小伙伴都知道机器人在长时间的使用下&#xff0c;难免遇到一些机械手故障。一旦发生了机器人故障&#xff0c;会影响整个生产线的作业&#xff0c;那么怎么才能做到防止机器人的故障率发生呢&#xff1f;定期的保养与维护显得尤为重要&#xff0c;一个好的维修保养服务商也很重…

Postgis中查找空间距离某条记录坐标100米内的数据

前提 表tablename带有空间字段geom sql语句 #使用 WITH 子查询 target_geom 获取 objectid1 的几何字段 geom。#主查询中使用 ST_DWithin 函数查找距离目标几何字段 100 米内的所有记录&#xff08;除 objectid1 本身&#xff09;。#因为坐标系为 4326&#xff0c;需要将100…

Vue页面内容未保存时离开页面做弹框提示

一、背景 目标&#xff1a;如果当前页面中有正在编辑中的内容&#xff0c;那么此时切换组件、跳转路由、关闭标签页、刷新页面&#xff0c;都会有离开确认提示&#xff0c;防止用户因误触导致填写的内容白费。 后台管理系统中有许多需要填写的表单&#xff0c;弹窗方式的表单一…

Python 循环语句

在Python当中&#xff0c;循环语句用于重复执行特定的代码块&#xff0c;知道某个条件不再满足为止。Python中常用的循环有两种&#xff1a;for 循环 和 while 循环&#xff0c;下面我会分别详细解释它们的用法和特点 for 循环 for循环用于遍历可迭代对象(iterable)&#xff0…