网络模块封装

news/2025/2/12 20:53:36/

网络模块封装

  • library-network模块配置依赖
  • 一.自定义LiveDataCallAdapterFactory
    • 1.定义ApiResponse返回的数据类型
    • 2.LiveDataCallAdapter.kt
    • 3.LiveDataCallAdapter.kt
  • 二.自定义CustomGsonConverterFactory
  • 三.拦截器
    • 1.HeaderInterceptor请求头拦截器
    • 2.BasicParamsInterceptor参数拦截器
    • 3.LoggingInterceptor拦截器
    • 4.超时重连次数拦截器
  • 缓存相应
    • 1.第一步骤:ResponseCacheInterceptor
    • 2.第二步骤:ResponseCacheInterceptor
    • 3.第三步骤:okhttp配置缓存路径,注意动态获取读写SD卡权限权限
  • Token令牌以及失效问题相关

library-network模块配置依赖

config.gradle:

//retrofitlibRetrofit = 'com.squareup.retrofit2:retrofit:2.9.0'libGsonConvert = 'com.squareup.retrofit2:converter-gson:2.9.0'libRetrofitAdapter = 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'//rxlibRxjava = 'io.reactivex.rxjava2:rxjava:2.2.7'libRxAndroid =  'io.reactivex.rxjava2:rxandroid:2.1.1'//okhttplibOkHttp =  'com.squareup.okhttp3:okhttp:3.4.1'libLogging =  'com.squareup.okhttp3:logging-interceptor:3.9.1'//rxlifecycle异步线程生命周期管理libRxLifecycle =  'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1'//工具类libUtils =  'com.blankj:utilcodex:1.30.6'

library-network

dependencies {api libRetrofitapi libRetrofitAdapterapi libGsonConvertapi libOkHttpapi libLoggingapi libRxjavaapi libRxAndroidapi libRxLifecycleapi libUtils
}

一.自定义LiveDataCallAdapterFactory

LiveDataCallAdapterFactory作用将okhttp默认返回的call转换成我们想要的livedata。

1.定义ApiResponse返回的数据类型

data class ApiResponse<T>(val code:Int,val message:String?,val data:T)

2.LiveDataCallAdapter.kt

class LiveDataCallAdapter<R>(var responseType:Type): CallAdapter<R,LiveData<R>> {override fun responseType(): Type {return responseType}override fun adapt(call: Call<R>): LiveData<R> {return object : LiveData<R>() {//确保多线程情况下安全的运行,不会被其他线程打断,一直等到该方法执行完成,才由jvm从等待队列中选择其他线程进入private var started = AtomicBoolean(false)override fun onActive() {super.onActive()if (started.compareAndSet(false, true)) {call.enqueue(object : Callback<R>{override fun onResponse(call: Call<R>, response: Response<R>) {if(response.code() == 200){//成功postValue(response.body())}else{//失败var apiResponse = ApiResponse(response.code(),response.message(),"")postValue(apiResponse as R)//java中强转}}override fun onFailure(call: Call<R>, t: Throwable) {var apiResponse = ApiResponse(500,t.message,"")postValue(apiResponse as R)//java中强转}})}}}}
}

3.LiveDataCallAdapter.kt

class LiveDataCallAdapterFactory: CallAdapter.Factory() {companion object{fun create(): LiveDataCallAdapterFactory {return LiveDataCallAdapterFactory()}}override fun get(returnType: Type,annotations: Array<out Annotation>,retrofit: Retrofit): CallAdapter<*, *>? {//returnType:LiveData<ApiResponse<MutableList<GoodsType>>>//判断类型是否为LiveDataif(getRawType(returnType) != LiveData::class.java){return null}//获得里面的第一个泛型ApiResponsevar observerType = getParameterUpperBound(0, returnType as ParameterizedType)// 第一个泛型ApiResponse的具体类型var rawType =  getRawType(observerType)if(rawType != ApiResponse::class.java){throw IllegalArgumentException("type must be ApiResponse")}if(!ParameterizedType::class.java.isInstance(observerType)){throw IllegalArgumentException("resource must be Parameterized")}return LiveDataCallAdapter<Any>(observerType)}
}

二.自定义CustomGsonConverterFactory

自定义CustomGsonConverterFactory

三.拦截器

1.HeaderInterceptor请求头拦截器

 class HeaderInterceptor : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val originalRequest = chain.request()val request = originalRequest.newBuilder().apply {header("model", "Android")header("If-Modified-Since", "${Date()}")header("User-Agent", System.getProperty("http.agent") ?: "unknown")}.build()return chain.proceed(request)}}

2.BasicParamsInterceptor参数拦截器

class BasicParamsInterceptor : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val originalRequest = chain.request()val originalHttpUrl = originalRequest.url()val url = originalHttpUrl.newBuilder().apply {addQueryParameter("udid", GlobalUtil.getDeviceSerial())//针对开眼官方【首页推荐 】api 变动, 需要单独做处理。原因:附加 vc、vn 这两个字段后,请求接口无响应。if (!originalHttpUrl.toString().contains(MainPageService.HOMEPAGE_RECOMMEND_URL)) {addQueryParameter("vc", GlobalUtil.eyepetizerVersionCode.toString())addQueryParameter("vn", GlobalUtil.eyepetizerVersionName)}addQueryParameter("size", screenPixel())addQueryParameter("deviceModel", GlobalUtil.deviceModel)addQueryParameter("first_channel", GlobalUtil.deviceBrand)addQueryParameter("last_channel", GlobalUtil.deviceBrand)addQueryParameter("system_version_code", "${Build.VERSION.SDK_INT}")}.build()val request = originalRequest.newBuilder().url(url).build()return chain.proceed(request)}}

3.LoggingInterceptor拦截器

class LoggingInterceptor : Interceptor {@Throws(IOException::class)override fun intercept(chain: Interceptor.Chain): Response {val originalRequest = chain.request()val t1 = System.nanoTime()logV(TAG, "Sending request: ${originalRequest.url()} \n ${originalRequest.headers()}")val response = chain.proceed(originalRequest)val t2 = System.nanoTime()logV(TAG, "Received response for  ${response.request().url()} in ${(t2 - t1) / 1e6} ms\n${response.headers()}")return response}companion object {const val TAG = "LoggingInterceptor"}}

4.超时重连次数拦截器

 var client = OkHttpClient.Builder().retryOnConnectionFailure(true)//超时重连,默认重试一次,多次的话需要拦截器实现.readTimeout(60, TimeUnit.SECONDS).writeTimeout(60, TimeUnit.SECONDS).build()

超时重连,默认重试一次,多次的话需要拦截器实现

//超时重连拦截器
class RetryInterceptor(val maxRetry:Int): Interceptor {//maxRetry 最大重试次数private var  retryNum = 0 //已经重连多少次override fun intercept(chain: Interceptor.Chain): Response {val  request = chain.request()var response = chain.proceed(request)while (!response.isSuccessful && retryNum < maxRetry){retryNum++response = chain.proceed(request)}return response}
}

缓存相应

如果我们想缓存 API 调用的响应,这样如果我们再次调用 API,响应就会从 Cache 中出来。

假设我们有从客户端到服务器的 API 调用,并且从服务器启用了Cache-Control标头 ,那么 OkHttp Core 将尊重该标头并缓存从服务器发送的响应一段时间。

但是如果没有从服务器启用 Cache-Control 怎么办。我们仍然可以使用拦截器缓存来自 OkHttp 客户端的响应。
在这里插入图片描述
只需看上图。在这里,我们要做的是,在进入 OkHttp Core 之前,我们必须拦截 Response 并添加 header(Cache-Control),所以它会被视为响应(带有 Cache-Control header)已经到来来自服务器,OkHttp Core 会尊重并缓存响应。

1.第一步骤:ResponseCacheInterceptor

/*** @Author : ytx* @Time : On 2023/5/19 16:29* @Description : RequstCacheInterceptor 所有的api请求都会缓存 通过addNetWorkInterceptor??* 因为通过addNetWorkInterceptor发生网络请求之后,这样就会缓存数据*/
class ResponseCacheInterceptor:Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val response: Response = chain.proceed(chain.request())val cacheControl = CacheControl.Builder().maxAge(120, TimeUnit.SECONDS).build()return response.newBuilder().header("Cache-Control", cacheControl.toString()).build()}
}

在这里,我们有一个 CacheControl 用于为 Cache-Control 提供标头。
最后,我们可以添加如下:

.addNetworkInterceptor(ResponseCacheInterceptor())

在这里,如果我们看到,我们没有使用addInterceptor()而是使用addNetworkInterceptor()的用例。这是因为在这种情况下,操作发生在网络层。

但是,在构建离线优先应用程序时,我们需要考虑一些重要的事情。

只有当互联网可用时才会返回缓存的响应,因为 OkHttp 就是这样设计的。

  • 当 Internet 可用并且数据被缓存时,它会从缓存中返回数据。
  • 即使数据被缓存并且互联网不可用,它也会返回错误“没有互联网可用”。

现在要做什么?
除了上述之外,我们还可以在应用层使用以下RequestCacheInterceptor (ResponseCacheInterceptor,仅当未从服务器启用时)。要在代码中实现,我们将创建一个 RequestCacheInterceptor,如下所示:

2.第二步骤:ResponseCacheInterceptor

/*** @Author : ytx* @Time : On 2023/5/19 16:29* @Description : RequstCacheInterceptor 网络请求前判断是否有网,无网强制从缓存中读取数据,不发起网络请求* 通过addInterceptor添加此拦截器*/
class RequestCacheInterceptor:Interceptor {override fun intercept(chain: Interceptor.Chain): Response {var request = chain.request()//拦截请求头 判断哪些接口配置上了缓存(该配置是在retrofit上配置)var cacheControl = request.cacheControl().toString()//如果没有配置过 那么就是走正常的访问,这里直接return回去了,并没有对请求做处理if(cacheControl.isEmpty()){return chain.proceed(request)}//如果没有网络的情况下,并且配置了缓存,那么我们就设置强制读取缓存,没有读到就抛异常,但是并不会去服务器请求数据if(!NetworkUtils.isConnected()){request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE)//强制从缓存读取// .cacheControl(CacheControl.FORCE_NETWORK)//强制从网络读取,默认是网络读取.build();}return chain.proceed(request);}
}

我们可以在 OkHttpClient 中添加拦截器,如下所示:

.addNetworkInterceptor(ResponseCacheInterceptor()) // only if not enabled from the server
.addInterceptor(RequestCacheInterceptor())

在这里,我们使用 addInterceptor() 而不是 addNetworkInterceptor() 将 ForceCacheInterceptor 添加到 OkHttpClient,因为我们希望它在应用程序层上工作。

3.第三步骤:okhttp配置缓存路径,注意动态获取读写SD卡权限权限

requestPermissions(arrayOf("android.permission.WRITE_EXTERNAL_STORAGE","android.permission.READ_EXTERNAL_STORAGE","android.permission.ACCESS_NETWORK_STATE"),101)
class RetrofitManager {companion object{//缓存fun createCache():Cache{var file = File("/sdcard/Music")return Cache(file,10*1024*1024)}fun getRetrofit():Retrofit{var client = OkHttpClient.Builder()//.retryOnConnectionFailure(true)//超时重连,默认1次,多次使用拦截器.cache(createCache())//配置缓存路径.readTimeout(30,TimeUnit.SECONDS).writeTimeout(30,TimeUnit.SECONDS).connectTimeout(30,TimeUnit.SECONDS).addInterceptor(RequestCacheInterceptor())//网络请求前读取缓存.addNetworkInterceptor(ResponseCacheInterceptor())//网络请求后缓存数据.addInterceptor(RetryInterceptor(3))//最大重连次数.addInterceptor(HeaderInterceptor())//请求头拦截器.addInterceptor(BasicParamsInterceptor())//请求参数拦截器.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))//log拦截器.build()return Retrofit.Builder().client(client).baseUrl("http://43.143.146.165:8181").addConverterFactory(GsonConverterFactory.create())//自动解析.addCallAdapterFactory(LiveDataCallAdapterFactory.create())//call-->Livedata
//                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//call--》Observable.build()}

最后说明:

可以通过添加 @Headers(“Cache-Control: max-age=120”) 进行设置。添加了Cache-Control 的请求,retrofit 会默认缓存该请
求的返回数据一般来说,这种方法是针对特定的API进行设置,上面的ResponseCacheInterceptor统一处理所有的请求

interface ApiService {@Headers("Cache-Control:public ,max-age=120")@GET("/goods/info")fun getGoods(@Query("category_id") category_id:Int, @Query("currentPage") currentPage:Int, @Query("pageSize") pageSize:Int):LiveData<ApiResponse<MutableList<Goods>>>
}

2.okhttp配置缓存路径,注意动态获取读写SD卡权限权限

Token令牌以及失效问题相关

普通的token拦截器

//token拦截器
class TokenInterceptor: Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val token = SPUtils.getInstance().getString("token")var request = chain.request().newBuilder().addHeader("token",token).build()return chain.proceed(request)}
}

问题
一次面试遇到的一个问题,其实也是实际开发中很容易遇到的问题,特此记录一下。
当请求某个接口的时候,我们会在请求的header中携带token消息,但是发现token失效,接口请求报错,怎么马上刷新token,然后重复请求方才那个接口呢?这个过程应该说对用户来说是无感的。

access_token:有效期比较短,2小时,网络请求需要携带的token请求头
refresh_token:有效期比较长,15天,当access_token过期的时候,使用refresh_token访问api接口获得新的access_token,网络请求不需要携带,但是需要保存到移动端

1.客户端发起登陆请求,服务器返回access_token和refresh_token,access_token有效期2个小时,refresh_token有效期15天
2.网络请求数据携带access_token,服务器判断access_token若过期,就返回错误码给客户端,客户端通过一个特定的api接口,传入refresh_token参数获得新的access_token和refresh_token,本地并更新
3.如果连续15天未使用app或者用户修改了密码,则表示refresh_token过期了,则跳转到登陆页面,重新获得新的access_token和refresh_token,本地并更新

完整代码:

//token拦截器
class TokenInterceptor: Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val accessToken = SPUtils.getInstance().getString("access_token")val refreshToken = SPUtils.getInstance().getString("refresh_token")var request = chain.request().newBuilder().addHeader("access_token",accessToken).build()var response = chain.proceed(request)//判断accessToken是否过期if(isTokenExpired(response)){var newToken = getNewToken(refreshToken)//使用新的Token,创建新的请求request = chain.request().newBuilder().addHeader("access_token",newToken).build()}return chain.proceed(request)}//解决并发问题@Synchronizedprivate fun getNewToken(refresh_token:String):String{/*访问特定的api接口获得新的accessToken和refreshToken*//*成功,refresh_token没有过期:返回accessToken+更新本地的accessToken和refreshToken保存*//*失败,refresh_token过期:返回""*///        Retrofit retrofit= new Retrofit.Builder()
//            /*API的主机地址*/
//            .baseUrl("https://url")
//            .addConverterFactory(GsonConverterFactory.create())
//            .build();
//        retrofit2.Response<JsonObject> requestToken = retrofit.create(ApiProtocol.class).DriverLogin().execute();
//        /*生成新的token*/
//        String token=requestToken.body().get("Token").toString();
//        /*需要更新本地的token保存*/return "xindeaccessToken"}//判断token是否过期private fun isTokenExpired(response: Response): Boolean {try {val responseBody = response.body()val source = responseBody!!.source()source.request(Long.MAX_VALUE)val buffer = source.bufferval utf8 = Charset.forName("UTF-8")val string: String = buffer.clone().readString(utf8)val apiResponse = Gson().fromJson(string,ApiResponse::class.java)if (apiResponse.code == 1) return true} catch (e: IOException) {e.printStackTrace()}return false}
}

http://www.ppmy.cn/news/74030.html

相关文章

SSM框架-SpringMVC

1. SpringMVC 1.1 Spring与Web环境集成 ApplicationContext应用上下文获取方式 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的&#xff0c;但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) &…

利用Canal把MySQL数据同步到ES

Canal是阿里巴巴开源的一个数据库变更数据同步工具&#xff0c;主要用于 MySQL 数据库的增量数据到下游的同步&#xff0c;例如同步到 Elasticsearch、HBase、Hive 等。下面是一个基本的步骤来导入 MySQL 数据库到 Elasticsearch。 安装和配置 Canal 首先&#xff0c;需要在你的…

8.防火墙-SNAT和DNAT

文章目录 SNAT-内网客户访问外网服务原理操作实验 DNAT-外网客户访问内网服务原理操作实验 tcpdump SNAT-内网客户访问外网服务 原理 由内网到外网&#xff1a;从内网发到外网的数据包的源IP由私网IP转换成公网IP 由外网到内网&#xff1a;从外网发到内网的数据包的目的IP由公…

Springboot +spring security,自定义认证器实现验证码功能

一.简介 SpringSecurity 默认是不支持验证码功能的&#xff0c;但是可以自己扩展&#xff0c;这也是使用SpringSecurity的好处之一&#xff0c;原生不支持&#xff0c;我们就自己扩展。 二.思路分析 因为系统默认的有一个DaoAuthenticationProvider 认证处理器&#xff0c;但…

使用Python实现Rest API指南

在今天的数字化世界中&#xff0c;数据的获取、交换和使用已经成为几乎所有行业的核心部分。无论您正在为一个大型公司设计复杂的软件系统&#xff0c;还是只是为了个人项目尝试获得一些公开的数据&#xff0c;理解和利 用API——尤其是RESTful API——都是一项至关重要的技术。…

细说java动态代理及使用场景

一、定义 Java代理模式是一种结构型设计模式&#xff0c;它允许通过创建一个代理对象来间接访问另一个对象&#xff0c;从而控制对原始对象的访问。 1.1 作用 1、在访问原始对象时增加额外功能&#xff0c;如访问前或访问后添加一些额外的行为。 2、控制对原始对象的访问。 J…

ARM汇编 C语言数据存储 堆和栈的区别

ARM汇编 ARM汇编是一种用于编写针对ARM架构的汇编语言。它是ARM处理器的底层指令集的人类可读表示形式&#xff0c;用于编写底层的系统级代码或优化特定的程序。 ARM汇编语言使用助记符&#xff08;mnemonic&#xff09;来表示不同的指令操作&#xff0c;例如"ADD&quo…

apk 作为资源提供 aar 的过程

1&#xff1a;参考&#xff1a;Android将APK项目封装为SDK(AAR) https://blog.csdn.net/weixin_51522235/article/details/128216091 四大点&#xff1a;1: apply plugin:com.android.library 2:去掉&#xff1a;applicationId 3:去掉&#xff1a;applicationVariants.all…