您现在的位置是:首页 >技术教程 >网络模块封装网站首页技术教程
网络模块封装
网络模块封装
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()}
${originalRequest.headers()}")
val response = chain.proceed(originalRequest)
val t2 = System.nanoTime()
logV(TAG, "Received response for ${response.request().url()} in ${(t2 - t1) / 1e6} ms
${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
}
}