Shine——更简单的Android网络请求库封装
作者:FreddyChen
写在前面
距离上一篇文章跟我一起开发商业级IM(3)—— 长连接稳定性之连接及重连发布的时间,大概已有一年多,先跟大家说声抱歉。主要是因为工作太忙,业务需求过多,没办法专心写博客。先立个Flag:IM系列文章一定会坚持写完,同时Github项目也会逐步完善,敬请期待。
这次就暂不更新IM系列相关的文章及项目了,先给大家带来一个稍微轻量级同时也比较实用的网络请求封装库:Shine,同时也希望自己借此机会重新拾起写博客和开源项目的激情,废话少说,我们直接开始吧。
Shine是什么?
基于Retrofit二次封装的网络请求库。通过统一封装、高内聚、低耦合、灵活配置、高度扩展等特性使Android网络请求更简单。
- 版本
- Java Retrofit+RxJava
- Kotlin Retrofit+Coroutine
Shine能做什么?
- 支持的请求
- GET
- POST
- PUT
- DELETE
- 支持动态BaseUrl
- 支持自定义Response Model(不同响应数据结构)
- 支持自定义Response Parser(响应数据解析器)
- 支持自定义Cipher(请求/响应数据加解密)
- 支持自定义Content Type
- 支持异步/同步请求
- 统一的IApiService,新增接口时无需改动IApiService
- 统一的异常处理,方便在接口请求失败时获取相关错误信息
为什么这样做?
- 不统一的Response Model
日常开发中,大家应该会经常遇到Response Model不统一的情况,例如服务端A返回的数据格式为:code、msg、data,服务端B返回的数据格式为:errCode、errMsg、result,服务端C返回的数据格式为status、message、data等,甚至即使是同一个服务端提供的接口,也可能存在不同接口返回不同数据格式,客户端兼容起来异常困难。在Shine中,通过自定义Response Model及Response Parser即可轻松解决此问题,同时支持配置全局Response Model及Response Parser,适应大多数单个服务器域名及返回数据格式的场景。
- 不同的BaseUrl
日常开发中,难免需要对接不同的服务器。Shine通过内部封装,使BaseUrl及Retrofit实例一一对应,应用层可配置全局BaseUrl或单个接口动态传递BaseUrl,使用灵活简单。
- 统一的IApiService
通常情况下,使用Retrofit请求接口的步骤为:
- 定义IApiService,声明接口;
- 在Model或Repository层调用接口;
- 在Presenter或ViewModel层调用Model实现的接口。
在Shine中,抽象为通用的IApiService,通过定义统一的get()
/post()
/put()
/delete()
/syncGet()
/syncPost()
/syncPut()
/syncDelete()
等接口,实现通用的IApiService,在新增接口或旧接口发生变动时,无需修改IApiService,降低开发成本并提升开发效率。
- 灵活的请求/响应Cipher(数据加解密器)
可配置全局Cipher或单个接口动态传递Cipher,灵活实现接口请求及响应数据加解密功能。例如接口A数据加密方式为AES,接口B数据加密方式为RSA等。
- 异步/同步请求支持
提供异步/同步请求方式支持。异步请求接口是我们平时请求的常用方式,但某些情况下,需要同步请求方式以实现某些需求,例如Ali OSS StsToken获取等。
- 统一的异常处理
通过封装RequestException实现统一异常处理,调用方仅需在自定义Response Model时构造对应的RequestException并传入错误码、错误信息等参数,使用Shine在接口请求失败时,通过RequestException提供的错误信息对业务做异常处理即可。
设计、封装思路及原理
项目结构 com.freddy.shine.kotlin
- cipher(数据加解密器相关)
- config(配置相关)
- exception(异常相关)
- interf(抽象接口相关)
- model(Response Model相关)
- parser(数据解析器相关)
- retrofit(Retrofit相关)
- utils(工具类相关)
- AbstractRequestManager.kt(RequestManager抽象类,自定义RequestManager需继承此类)
- RequestManagerFactory.kt(RequestManager工厂,提供获取RequestManager方法,应用层直接调用[getRequestManager]即可,无需关心内部实现逻辑)
- ShineKit.kt(Shine核心类)
设计及封装
Shine内部封装请求逻辑,同时提供以下方案使Shine更易用、更具扩展性:- 暴露ICipher接口使调用方灵活自定义相关数据加解密器实现,并可配置全局/单个接口请求使用;
- 暴露IParser接口使调用方灵活自定义相关数据解析器实现,并可配置全局/单个接口请求使用;
- 抽象统一的IApiService,支持异步/同步请求,并统一请求方式使Shine支持各项目使用;
- 内部多Retrofit实例管理使Shine支持动态BaseUrl;
- 通过构建者模式使Shine请求调用参数传递更灵活等。
原理
- Retrofit多实例管理:采用Map保存多个Retrofit实例,
key: BaseUrl, value: Retrofit Instance
。当然有些同学可能觉得多个Retrofit会造成性能浪费、不好管理之类的,这个就见仁见智了。我觉得在一个项目中BaseUrl并不会过多,并且如果是统一的OkHttpClient的话,多个Retrofit实例并不会造成多大的性能浪费,并且多个Retrofit反而会更灵活。当然,后续我会增加移除Retrofit实例的接口,大家如果觉得在某个时刻(大概率不再请求该BaseUrl)可以适当移除该Retrofit实例的话直接移除即可,即使会重新请求,那也就是重新创建一个Retrofit实例而已(详见RetrofitManager.kt
)。 - 动态请求头:通过自定义OkHttp Interceptor获取请求Url实现Request Headers传递(详见
OkHttpRequestHeaderInterceptor.kt
)。 - 自定义数据加解密器:通过自定义OkHttp Interceptor同时暴露ICipher接口使调用方灵活自定义请求/响应数据加解密器(详见
OkHttpRequestEncryptInterceptor.kt
、OkHttpResponseDecryptInterceptor.kt
、DefaultCipher.kt
)。 - 自定义数据解析器:通过反射获取Parser实例,获取到Parser实例后会保存到Map方便下一次获取。同时暴露IParser接口使调用方灵活自定义数据解析器(详见
AbstractRequestManager.kt
、DefaultParser.kt
)。
- Retrofit多实例管理:采用Map保存多个Retrofit实例,
Java泛型擦除问题
大家应该有遇到过,在Java中无法传递ArrayList.class。在Kotlin中,可以通过inline及reified关键字获取泛型T class,但在Java中会存在泛型擦除的问题(关于Java泛型擦除大家可自行了解,在此不再展开),为了解决此问题,通过自定义ParameterizedTypeImpl
实现ParameterizedType
接口即可(详见TypeUtil.java
及Demo中BaseRepository.java
调用)。
参数及API说明
- RequestOptions
参数名称 | 说明 | 类型 | 示例 | 默认值 | 备注 |
---|---|---|---|---|---|
requestMethod | 请求方式 | RequestMethod | RequestMethod.GET | RequestMethod.GET | / |
baseUrl | 服务器域名 | String | api.oick.cn/ | / | / |
function | 接口地址 | String | article/list/0/json | / | / |
headers | 请求头 | ArrayMap<String, Any> | / | / | / |
params | 请求参数 | ArrayMap<String, Any> | / | / | / |
contentType | 内容类型 | String | application/json; charset=utf-8 | application/json; charset=utf-8 | / |
- ShineOptions
参数名称 | 说明 | 类型 | 示例 | 默认值 | 备注 |
---|---|---|---|---|---|
logEnable | Shine日志开关 | Boolean | true | true | / |
logTag | Shine日志TAG | String | Custom | Shine | / |
baseUrl | Shine默认服务器域名 | String | / | / | 配置后,当某个接口没有动态设置BaseUrl时,将会用此默认BaseUrl |
parserCls | Shine默认数据解析器 | KClass | DefaultParser::class | DefaultParser::class | 配置后,当某个接口没有动态设置Parser时,将会用此默认Parser |
- IRequest
/*** 抽象的接口请求封装,自定义RequestManager实现此接口即可** @author: FreddyChen* @date : 2022/01/07 13:47* @email : freddychencsc@gmail.com*/
interface IRequest {/*** 异步请求* @param options 请求参数* @param type 数据类型映射* @param parserCls 数据解析器* @param cipherCls 数据加解密器*/suspend fun <T> request(options: RequestOptions,type: Type,parserCls: KClass<out IParser>,cipherCls: KClass<out ICipher>? = null): T/*** 同步请求* @param options 请求参数* @param type 数据类型映射* @param parserCls 数据解析器* @param cipherCls 数据加解密器*/fun <T> syncRequest(options: RequestOptions,type: Type,parserCls: KClass<out IParser>,cipherCls: KClass<out ICipher>? = null): T
}
- ICipher
/*** 加解密器抽象接口** @see [DefaultCipher]* @author: FreddyChen* @date : 2022/01/13 16:07* @email : freddychencsc@gmail.com*/
interface ICipher {/*** 加密数据*/fun encrypt(original: String?): String?/*** 解密数据*/fun decrypt(original: String?): String?/*** 获取加解密字段名称*/fun getParamName(): String
}
- IParser
/*** 数据解析器抽象接口** @see [DefaultParser]* @author: FreddyChen* @date : 2022/01/06 17:53* @email : freddychencsc@gmail.com*/
interface IParser {fun<T> parse(url: String, data: String, type: Type): T
}
- IApiService
/*** 统一的请求方式* @author: FreddyChen* @date : 2022/01/07 11:08* @email : freddychencsc@gmail.com*/
internal interface IApiService {/*** 异步GET请求* 无参*/@GETsuspend fun get(@Url function: String): String/*** 异步GET请求* 带参*/@GETsuspend fun get(@Url function: String, @QueryMap params: ArrayMap<String, Any?>): String/*** 异步POST请求* 无参*/@POSTsuspend fun post(@Url function: String): String/*** 异步POST请求* 带参*/@POSTsuspend fun post(@Url function: String, @Body body: RequestBody): String/*** 异步PUT请求* 无参*/@PUTsuspend fun put(@Url function: String): String/*** 异步PUT请求* 带参*/@PUTsuspend fun put(@Url function: String, @Body body: RequestBody): String/*** 异步DELETE请求* 无参*/@DELETEsuspend fun delete(@Url function: String): String/*** 异步DELETE请求* 带参*/@DELETEsuspend fun delete(@Url function: String, @QueryMap params: ArrayMap<String, Any?>): String/*** 同步GET请求* 无参*/@GETfun syncGet(@Url function: String): Call<String>/*** 同步GET请求* 带参*/@GETfun syncGet(@Url function: String, @QueryMap params: ArrayMap<String, Any?>): Call<String>/*** 同步POST请求* 无参*/@POSTfun syncPost(@Url function: String): Call<String>/*** 同步POST请求* 带参*/@POSTfun syncPost(@Url function: String, @Body body: RequestBody): Call<String>/*** 同步PUT请求* 无参*/@PUTfun syncPut(@Url function: String): Call<String>/*** 同步PUT请求* 带参*/@PUTfun syncPut(@Url function: String, @Body body: RequestBody): Call<String>/*** 同步DELETE请求* 无参*/@DELETEfun syncDelete(@Url function: String): Call<String>/*** 同步DELETE请求* 带参*/@DELETEfun syncDelete(@Url function: String, @QueryMap params: ArrayMap<String, Any?>): Call<String>
}
使用方式
- 添加依赖
- Java
implementation "io.github.freddychen:shine-java:$lastest_version"
- Kotlin
implementation "io.github.freddychen:shine-kotlin:$lastest_version"
Note:最新版本可在maven central shine中找到。
- 初始化
使用Shine前进行初始化,建议放到Application#onCreate()。
val options = ShineOptions.Builder().setLogEnable(true).setLogTag("FreddyChen").setBaseUrl("https://api.oick.cn/").setParserCls(CustomParser1::class).build()
ShineKit.init(options)
当然,初始化不是强制的,ShineOptions会有对应的默认值,默认值可参考参数及API说明#ShineOptions
- 使用
suspend fun fetchCatList(): ArrayList<Cat> {val options = RequestOptions.Builder().setRequestMethod(RequestMethod.GET).setBaseUrl("https://cat-fact.herokuapp.com/").setFunction("facts/random?amount=2&animal_type=cat").build()val type = object : TypeToken<ArrayList<Cat>>() {}.typereturn ShineKit.getRequestManager().request(options = options,type = type,parserCls = CustomParser1::class)
}
当然,Type及Parser参数传递我们可以利用Kotlin特性封装一个通用的请求方法,这些大家根据自己的业务情况来选择就好,下面提供一个示例:
/*** 异步请求*/
suspend inline fun <reified T> request(requestMethod: RequestMethod,baseUrl: String = "https://api.oick.cn/",function: String,headers: ArrayMap<String, Any?>? = null,params: ArrayMap<String, Any?>? = null,contentType: String = NetworkConfig.DEFAULT_CONTENT_TYPE,parserCls: KClass<out IParser> = CustomParser1::class,cipherCls: KClass<out ICipher>? = null): T {val optionsBuilder = RequestOptions.Builder().setRequestMethod(requestMethod).setBaseUrl(baseUrl).setFunction(function).setContentType(contentType)if (!headers.isNullOrEmpty()) {optionsBuilder.setHeaders(headers)}if (!params.isNullOrEmpty()) {optionsBuilder.setParams(params)}return ShineKit.getRequestManager().request(optionsBuilder.build(), object : TypeToken<T>() {}.type, parserCls, cipherCls)}
这样的话,上面的请求可以简化为:
suspend fun fetchCatList(): ArrayList<Cat> {return request(requestMethod = RequestMethod.GET,baseUrl = "https://cat-fact.herokuapp.com/",function = "facts/random?amount=2&animal_type=cat",)
}
- 示例
- 获取历史列表数据
服务器域名 | 接口地址 | 参数 | 返回数据结构 | 备注 |
---|---|---|---|---|
api.oick.cn/ | lishi/api.php | / | code、day、result | / |
例:
{"code":"1","day":"01/ 17","result":[{"date":"395年01月17日","title":"罗马帝国分裂为西罗马帝国和东罗马帝国"}]
}
调用方式:
suspend fun fetchHistoryList(): ArrayList<History> {return request(requestMethod = RequestMethod.POST,function = "lishi/api.php",)
}
- 获取新闻列表数据
服务器域名 | 接口地址 | 参数 | 返回数据结构 | 备注 |
---|---|---|---|---|
is.snssdk.com/ | api/news/feed/v51/ | / | message、data | / |
例:
{"message":"success","data":[{"content":"test"}]
}
调用方式:
suspend fun fetchJournalismList(): ArrayList<Journalism> {return request(requestMethod = RequestMethod.GET,baseUrl = "https://is.snssdk.com/",function = "api/news/feed/v51/",parserCls = CustomParser2::class,)
}
Note:如有业务需求使用同步请求方式,只需要把request()
方法改成syncRequest()
方法即可。
版本记录
版本号 | 修改时间 | 版本说明 |
---|---|---|
0.0.7 | 2022.01.16 | 首次提交 |
0.0.8 | 2022.02.15 | 修改minSdkVersion为19 |
免费开放的Api
提供两个免费开放Api平台给大家,方便测试:
- 红花会 / 免费的api接口
- public-apis
写在最后
终于写完了,网络请求基本是每个Android应用必须用到的组件,Shine为平时工作中的积累,也算是一种总结,希望对大家有所帮助。由于水平有限,也许Shine并不是最好的封装方式,开源这个项目,旨在起到抛砖引玉的作用,欢迎大家star和fork,让我们为Android开发共同贡献一份力量。
Shine——更简单的Android网络请求库封装相关推荐
- Android 网络请求库Retrofit简单使用
载请标明出处: http://blog.csdn.net/u011974987/article/details/50895633: 什么是 Retrofit ? Retrofit 是一套 RESTfu ...
- android网络请求库volley方法详解
使用volley进行网络请求:需先将volley包导入androidstudio中 File下的Project Structrue,点加号导包 volley网络请求步骤: 1. 创建请求队列 ...
- android网络请求框架汇总
网络 使用网络库不要忘记添加网络权限 2.1网络_Volley · 简介: Volley的中文翻译为"齐射.并发",是在2013年的Google大会上发布的一款Android平台网 ...
- android post请求添加公共参数_XHttp2 一个功能强悍的网络请求库
XHttp2 一个功能强悍的网络请求库,使用RxJava2 + Retrofit2 + OKHttp组合进行封装.还不赶紧点击使用说明文档,体验一下吧! 项目地址 关于我 https://github ...
- Android -- 网络请求
一. HttpURLConnection 二. HttpClient 三.Volley 四.OkHttp 五. Retrofit ----------------------------------- ...
- 流行框架(二)网络请求库 OKhttp
文章目录 概述 HttpURLConnection GET和POST获取文本数据 GET POST OKHttp 基本使用 依赖与权限 发起一个get请求 重要概念 OkHttpClient Requ ...
- 「Python 编程」编码实现网络请求库中的 URL 解析器
相信各位 Python 开发者都用过 Requests 库,有些朋友还用过 WebSockets 库.这里回顾一下它们的基本用法,例如使用 Requests 库向目标网站发出 GET 请求: impo ...
- Android网络请求框架之Retrofit(二)
前面一篇文章介绍了Retrofit的基本用法,没有看过的童鞋可以移步:Android网络请求框架之Retrofit(一),现在我们来继续介绍Retrofit配合RxJava.RxAndroid的用法. ...
- Python网络请求库Requests,妈妈再也不会担心我的网络请求了(一)
本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 极客导航 即可关注,每个工作日都有文章更新. 一.概况 网络请求可能是每门语言比较重要的一部分了,在Python语言中,虽然有urll ...
- python的网络请求库urllib、urllib2、urllib3、request的联系
文章目录 1. 简介 2. urllib 3. urllib2 4. urllib3 5. requests 6. 相关文章 1. 简介 urllib.urllib2.urllib3.request均 ...
最新文章
- powershell获取linux文件,powershell如何读取文件名并赋值到变量?
- Spring学习笔记八--Bean生命周期和后置处理器
- spring + shiro + cas 实现sso单点登录
- Linkis计算中间件部署过程记录
- 读书笔记 Effective C++: 02 构造析构赋值运算
- C# 实验三 判断一个字符、判断三角形、千名学生、a+aa+aaa+aaaa、求数列相加、约瑟夫环
- ASP.NET Core快速入门(第5章:认证与授权)--学习笔记
- java 实现中文排序,Java自定义比较器实现中文排序
- uml类图例子_五分钟带你读懂UML类图
- linux+vi+注掉代码,VI编辑器之删除操作(示例代码)
- 在GPU上部署Bert模型
- ajax:前后端json传值写法
- 万兆网文件服务器,万兆以太网网卡网吧服务器中的应用
- intellij idea 插件 开发 新加的mainmenu不显示
- spring jpa Specification in 查询
- 图片太大如何压缩?学会这个方法轻松压缩
- getline()与cin.getline()函数用法详解
- OA电子表单设计-年假申请单-数据验证
- 计蒜客python答案Top50
- No.4-VulnHub-Tr0ll:1-Walkthrough渗透学习
热门文章
- 开源 免费 java CMS - FreeCMS1.5-职位管理
- PHP实现队列之双向队列
- 使用计算机键盘的基本步骤,用键盘怎样关机(win7电脑键盘关机的操作方法)...
- MPB:湖南师大尹佳组-抑菌圈和药敏实验研究益生菌拮抗病原菌和抗生素敏感性的方法...
- idea运行web项目光标乱跳
- 【正则表达式】网页上敏感词过滤背后的原理你知道吗?
- 【四二学堂】基于uni-app开发的跨平台井字游戏(App+H5 web+微信小程序)
- 经纬度转换器_FME应用小实例:线面经纬度集合快速转几何图形
- 山东大学软件学院操作系统实验的准备
- qlv视频怎么转换成mp4格式工厂?如何用格式工厂将qlv格式转换成mp4格式?