网络模块封装

news2024/9/29 7:25:16

网络模块封装

  • library-network模块配置依赖
  • 一.自定义LiveDataCallAdapterFactory
    • 1.定义ApiResponse返回的数据类型
    • 2.LiveDataCallAdapter.kt
    • 3.LiveDataCallAdapter.kt
  • 二.自定义CustomGsonConverterFactory
  • 三.拦截器
    • 1.HeaderInterceptor请求头拦截器
    • 2.BasicParamsInterceptor参数拦截器
    • 3.LoggingInterceptor拦截器
    • 4.超时重连次数拦截器
  • 缓存相应
  • Token令牌以及失效问题相关

library-network模块配置依赖

config.gradle:

//retrofit
    libRetrofit = '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'
   //rx
    libRxjava = 'io.reactivex.rxjava2:rxjava:2.2.7'
    libRxAndroid =  'io.reactivex.rxjava2:rxandroid:2.1.1'
    //okhttp
    libOkHttp =  '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 libRetrofit
    api libRetrofitAdapter
    api libGsonConvert
    api libOkHttp
    api libLogging
    api libRxjava
    api libRxAndroid
    api libRxLifecycle
    api 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>>>
        //判断类型是否为LiveData
       if(getRawType(returnType) != LiveData::class.java){
           return null
       }
        //获得里面的第一个泛型ApiResponse
       var 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 会尊重并缓存响应。
我们将创建一个拦截器,如下所示:

class CacheInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val response: Response = chain.proceed(chain.request())
        val cacheControl = CacheControl.Builder()
            .maxAge(10, TimeUnit.DAYS)
            .build()
        return response.newBuilder()
            .header("Cache-Control", cacheControl.toString())
            .build()
    }
}

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

.addNetworkInterceptor(CacheInterceptor())

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

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

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

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

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

class ForceCacheInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val builder: Request.Builder = chain.request().newBuilder()
        if (!IsInternetAvailable()) {
            builder.cacheControl(CacheControl.FORCE_CACHE);

        }
        return chain.proceed(builder.build());
    }
}

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

.addNetworkInterceptor(CacheInterceptor()) // only if not enabled from the server
.addInterceptor(ForceCacheInterceptor())

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

最后:

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

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卡权限权限

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)//超时重连,默认重试一次,多次的话需要拦截器实现
                .readTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .cache(createCache())//配置缓存
                .addInterceptor(RequestCacheInterceptor())
                .addNetworkInterceptor(ResponseCacheInterceptor())
                .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                .build()
            return Retrofit.Builder()
                .baseUrl(Constant.BASE_URL)
                .client(client)
                .addConverterFactory(CustomGsonConverterFactory.create())//gson解析工厂
                .addCallAdapterFactory(LiveDataCallAdapterFactory.create())
                .build()

        }

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)
    }

    //解决并发问题
    @Synchronized
    private 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.buffer
            val 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.coloradmin.cn/o/545858.html

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

相关文章

Android - 内容提供者(Content Provider) 使用

Android - 内容提供者(Content Provider) 内容提供者组件通过请求从一个应用程序向其他的应用程序提供数据。这些请求由类 ContentResolver 的方法来处理。内容提供者可以使用不同的方式来存储数据。数据可以被存放在数据库&#xff0c;文件&#xff0c;甚至是网络。 有时候需…

Python如何进行性能测试?(Locust对接口进行压测)

python如何进行性能测试呢&#xff1f;其实原理就是对于接口进行加线程&#xff0c;打个比方就是当你有一个电梯&#xff0c;你同时可以搭载多少个人坐电梯那这个人数就是这部电梯的其中一个性能指标&#xff0c;那么对于接口来说每秒钟能有多少人成功发起请求后得到成功的响应…

QT 学习笔记2 信号与槽

上次做的界面&#xff0c;并没有逻辑。你点击按钮&#xff0c;并不会执行什么 要想其能作出反映&#xff0c;就不得不提到一个很重要的机制---信号与槽 当我们点击确定的时候&#xff0c;按钮会发出一个信号 点击确定的时候&#xff0c;会执行一段代码&#xff0c;这段程序就…

Ada 语言学习(3)复合类型数据——Array

文章目录 Array数据类型声明数组索引数组范围数组复制数组初始化直接赋值通过拷贝赋值不同索引范围但长度相等非指定类型边界收缩 多维数组数组遍历数组切片访问和动态检查直接访问动态检查 数组字面量 Array literal数组拼接两个数组拼接数组和单个值拼接 Array Equality&…

机器学习平台 PAI 支持抢占型实例,模型服务最高降本 90%

助力模型推理服务降本增效&#xff0c;适用于推理成本敏感场景&#xff0c;如&#xff1a;AIGC 内容生成异步推理、批量图像处理、批量音视频处理等。 在 AI 开发及服务不断追求效率的背景下&#xff0c;阿里云机器学习平台 PAI 宣布支持抢占型实例&#xff08;Spot Instance&a…

2023逆向分析代码渗透测试flag0072解析(超详细)

一、竞赛时间 180分钟 共计3小时 1.从靶机服务器的FTP上下载flag0072,分析该文件,请提交代码保护技术的类型。提交格式:XXXX。 2.提交被保护的代码所在地址。提交格式: 0xXXXX。 3.提交代码解密的密钥。提交格式: 0xXX。 4.请提交输入正确flag时的输出。提交格式: XXXX。…

Python入门(十二)while循环(二)

while循环&#xff08;二&#xff09; 1.使用while循环处理列表和字典2.在列表之间移动元素3.删除为特定值的所有列表元素4.使用用户输入来填充字典 作者&#xff1a;xiou 1.使用while循环处理列表和字典 到目前为止&#xff0c;我们每次都只处理了一项用户信息&#xff1a;获…

建站教程:腾讯云轻量服务器安装宝塔面板搭建网站流程

腾讯云轻量应用服务器镜像选择宝塔Linux面板&#xff0c;然后在宝塔面板上安装LNMP网站所需的Web环境&#xff0c;在宝塔面板上新建站点&#xff0c;上床网站程序安装包到根目录&#xff0c;并安装网站全流程。腾讯云百科来详细说下腾讯云轻量应用服务器搭建网站全流程&#xf…

百果园ESG:围绕“好吃”二字,勾勒水果行业未来蓝图

当一场可持续绿色变革开始&#xff0c;ESG&#xff08;环境、社会与治理&#xff09;已经成为企业发展战略的重要组成部分。 然而&#xff0c;如何实现ESG和企业发展的协同却是一大问题。根据毕马威《2022年中国首席执行官展望》&#xff0c;一些企业家也表示ESG投资对提升财务…

SpringBoot配置文件3种格式、配置文件读取方式、多环境配置、配置文件优先级分类

文章目录 1 配置文件格式1.1 环境准备1.2 不同配置文件演示1.3 三种配合文件的优先级 2 yaml格式2.1 语法规则 3 yaml配置文件数据读取3.1 环境准备3.2 读取配置数据方式1 使用 Value注解方式2 Environment对象方式3 自定义对象 4 多环境配置4.1 yaml文件4.2 properties文件4.3…

H.265/HEVC编码原理及其处理流程的分析

H.265/HEVC编码原理及其处理流程的分析 H.265/HEVC编码的框架图&#xff0c;查了很多资料都没搞明白&#xff0c;各个模块的处理的分析网上有很多&#xff0c;很少有把这个流程串起来的。本文的主要目的是讲清楚H.265/HEVC视频编码的处理流程&#xff0c;不涉及复杂的计算过程。…

自定义线程池 ThreadPoolExecutor

ThreadPoolExecutor 自定义线程池 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultT…

Arduino ESP8266+RC522+阿里云 物联网入户控制RFID门禁系统

前言 根据项目结课报告改编而成&#xff0c;可能更适合作为一份文档而不是一篇记录类型的博客&#xff0c;没有留存接线图和运行图片&#xff0c;感到抱歉。 使用的板子是YwRobot的ESP8266板子&#xff0c;使用Arduino IDE开发&#xff0c;用到了舵机、按钮、人体感应传感器、…

Unity UI -- (4)用图像创建菜单背景

添加一个基础的设置菜单背景 设置菜单的元素会安放在一个简单的矩形区域上。我们用一个Image对象来创建这个矩形。 1. 首先&#xff0c;我们暂时停用Title Text和Settings Button游戏物体。这样会让我们的Canvas看起来更清爽。 2. 在Hierarchy中&#xff0c;点击右键&#xff0…

探索云原生世界:当前最受欢迎的技术和趋势

文章目录 探索云原生世界&#xff1a;当前最受欢迎的技术和趋势引言&#xff1a;一、云原生概述&#xff1a;1. 什么是云原生&#xff1f;2. 为什么云原生重要&#xff1f;3. 云原生的核心原则和特征。4. 云原生的优势和挑战。 二、核心技术与工具&#xff1a;1. Kubernetes&am…

C语言qsort函数、活字印刷、cmd窗口

一、qsort函数 qsort函数就是快排&#xff0c;可以不用写那么一长串的代码了qvq&#xff0c;要用到stdlib.h库文件 那么具体用法就是 oid qsort(void* base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*)); 当然我们还要用一个比较函数来确定快排…

手机APP性能测试工具PerfDog性能狗安装教程及简单使用

一、前言 PerfDog是一个由腾讯研发的主流性能测试软件。可以提高软件和游戏的运行效率&#xff0c;支持iOS/安卓在移动平台上的性能测试和分析&#xff0c;快速定位和分析性能问题等。无需安装&#xff0c;即插即用&#xff0c;减少繁琐的测试障碍&#xff0c;安卓设备不需要RO…

PCB基础~PCB介质,Vias

PCB介质 • 一般的介质材料 – FR-4&#xff08;玻璃纤维和环氧基树脂交织而成&#xff09; • 最常和最广泛使用&#xff0c;相对成本较低 • 介电常数&#xff1a;最大4.7&#xff0c; 4.35500Mhz,4.341Ghz • 可承受的最高信号频率是2Ghz(超过这个值&#xff0c;损耗和串扰…

IDEA中怎么把jar包导入项目中

大作业让生成一个pdf&#xff0c;查找资料发现可以通过pdfbo相关函数调用&#xff0c;但本地缺少这个文件&#xff0c;以这个文件为例子。 一、下载 下载去Apache上下载&#xff0c;Apache PDFBox | Download&#xff0c;&#xff0c;结合自己的java版本啥的下载就行。 我是…

java中使用java8的stream报错java.lang.IllegalStateException: Duplicate key

一、java.lang.IllegalStateException: Duplicate key报错的原因 map的key重复导致的报错Duplicate key 二、java.lang.IllegalStateException: Duplicate key报错的解决方式 list.stream().collect()就是把一个List的查询数据集合转为一个Map&#xff0c;java8的stream方式…