首页 > 其他分享 >Retrofit 的基本用法

Retrofit 的基本用法

时间:2023-04-08 16:45:38浏览次数:47  
标签:基本 return val private Retrofit fun import 用法 gson

一、添加依赖和网络权限

添加依赖

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

// 可选
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
  • 第一条依赖是下载Retrofit、OkHttp和Okio这几个库,我们就不需要手动引入OkHttp库了;
  • 第二条依赖是一个Retrofit的转换库,它是借助GSON来解析JSON数据的,所以也会将GSON库一起下载;
  • 第三条是 okHttp 的日志拦截器相关,可选。

添加网络权限

AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

二、Retrofit data api 部分封装

2.1 创建接收服务器返回数据类、异常类、异常码

通用返回数据类

import com.google.gson.annotations.SerializedName

data class CommonResponse<T>(
    @SerializedName("code") val code: Int = -1,

    @SerializedName("data") val data: T,

    @SerializedName("msg") val msg: String
) {
    fun isSuccess(): Boolean {
        return code == 0
    }
}

异常类

data class ResponseException(
    var code: Int, override var message: String?
) : RuntimeException(message)

异常码定义

import androidx.annotation.IntDef

@IntDef(
    ErrorCode.SUCCESS, // 成功
    ErrorCode.FAIL, // 失败
    ErrorCode.NETWORK_EXCEPTION,//无网络,网络连接异常
    ErrorCode.HOST_ERROR,//host异常
    ErrorCode.TIMEOUT,//超时
    ErrorCode.CANCEL,//取消
    ErrorCode.JSON_SYNTAX_EXCEPTION,//数据解析异常
    ErrorCode.OK,//请求正常
    ErrorCode.CREATED,
    ErrorCode.FORBIDDEN,
    ErrorCode.UNAUTHORIZED,//无授权
    ErrorCode.NOT_FOUND,
    ErrorCode.OTHER,//其他错误,目前还未关注和处理的
    ErrorCode.CUSTOM_FIRST,//自定义,可自行修改
    ErrorCode.VALUE_IS_NULL//空值
)
@Retention(AnnotationRetention.SOURCE)
annotation class ErrorCode {
    companion object {
        const val SUCCESS = 0
        const val FAIL = 1
        const val NETWORK_EXCEPTION = 2
        const val HOST_ERROR = 3
        const val TIMEOUT = 4
        const val CANCEL = 5
        const val JSON_SYNTAX_EXCEPTION = 6
        const val OK = 200
        const val CREATED = 201
        const val FORBIDDEN = 401
        const val UNAUTHORIZED = 402
        const val NOT_FOUND = 404
        const val OTHER = 509
        const val CUSTOM_FIRST = 600
        const val VALUE_IS_NULL = CUSTOM_FIRST + 1
    }
}

2.2 封装异常处理

import com.google.gson.JsonSyntaxException
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

private const val TAG = "getResultOrNull"

suspend fun <T> getResultOrNull(block: suspend () -> CommonResponse<T>): T? {
    runCatching {
        block()
    }.onSuccess {
        return it.data
    }.onFailure {
        when (it) {
            is ResponseException -> {
                logW(TAG, "getResult exception, code: ${it.code} message: ${it.message}")
            }
            is UnknownHostException,
            is HttpException,
            is ConnectException,
            is SocketTimeoutException,
            is SocketException,
            is NumberFormatException,
            is IllegalArgumentException,
            is IllegalStateException,
            is JsonSyntaxException -> {
                logW(TAG, "getResult exception: ${it.message}")
            }
            else -> {
                logW(TAG, "getResult other exception: ${it.message}")
            }
        }
        return null
    }
    return null
}

2.3 定义 SSLSocketClient

import java.security.KeyManagementException
import java.security.KeyStore
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.*

object SSLSocketClient {

    @Throws(NoSuchAlgorithmException::class, KeyManagementException::class)
    fun getSSLSocketFactory(): SSLSocketFactory {
        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(null, getTrustManager(), SecureRandom())
        return sslContext.socketFactory
    }

    private fun getTrustManager(): Array<TrustManager> {
        val trustManager: X509TrustManager = object : X509TrustManager {
            @Throws(CertificateException::class)
            override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
            }

            @Throws(CertificateException::class)
            override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
            }

            override fun getAcceptedIssuers(): Array<X509Certificate> {
                return arrayOf()
            }
        }
        return arrayOf(trustManager)
    }

    fun getHostnameVerifier(): HostnameVerifier {
        return HostnameVerifier { _, _ -> true }
    }

    @Throws(Exception::class)
    fun getX509TrustManager(): X509TrustManager {
        var trustManager: TrustManager? = null
        val trustManagerFactory =
            TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        trustManagerFactory.init(null as? KeyStore)
        val trustManagers = trustManagerFactory.trustManagers
        if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
            throw IllegalStateException("Unexpected default trust managers: $trustManagers")
        }
        return trustManagers[0] as X509TrustManager
    }
}

2.4 自定义 CustomGsonConverterFactory

import com.google.gson.Gson
import com.google.gson.JsonIOException
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonToken
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.ResponseBody
import okio.Buffer
import retrofit2.Converter
import retrofit2.Retrofit
import java.io.ByteArrayInputStream
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.lang.reflect.Type
import java.nio.charset.Charset
import kotlin.text.Charsets.UTF_8

class CustomGsonConverterFactory private constructor(val gson: Gson): Converter.Factory() {
    companion object {
        fun create(): CustomGsonConverterFactory {
            return create(Gson())
        }

        private fun create(gson: Gson?): CustomGsonConverterFactory {
            if (gson == null) throw NullPointerException("gson == null")
            return CustomGsonConverterFactory(gson)
        }
    }

    override fun responseBodyConverter(
        type: Type,
        annotations: Array<out Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *> {
        return CustomGsonResponseBodyConverter(gson, gson.getAdapter(TypeToken.get(type)))
    }

    override fun requestBodyConverter(
        type: Type,
        parameterAnnotations: Array<out Annotation>,
        methodAnnotations: Array<out Annotation>,
        retrofit: Retrofit
    ): Converter<*, RequestBody> {
        return CustomGsonRequestBodyConverter(gson, gson.getAdapter(TypeToken.get(type)))
    }
}

private class CustomGsonRequestBodyConverter<T>(private val gson: Gson, private val adapter: TypeAdapter<T>) : Converter<T, RequestBody> {
    private val MEDIA_TYPE = "application/json; charset=UTF-8".toMediaTypeOrNull()
    private val UTF_8 = Charset.forName("UTF-8")

    override fun convert(value: T): RequestBody {
        val buffer = Buffer()
        val writer = OutputStreamWriter(buffer.outputStream(), UTF_8)
        val jsonWriter = gson.newJsonWriter(writer)
        adapter.write(jsonWriter, value)
        jsonWriter.close()
        return buffer.readByteString().toRequestBody(MEDIA_TYPE)
    }
}

private class CustomGsonResponseBodyConverter<T>(private val gson: Gson, private val adapter: TypeAdapter<T>) : Converter<ResponseBody, T> {
    override fun convert(value: ResponseBody): T {
        val response = value.string()
        val commonResponse = gson.fromJson(response, CommonResponse::class.java)
        /** 先将code与msg解析出来,code非0的情况下直接抛ApiException异常,这样我们就将这种异常交给onFailure()处理了**/
        if (!commonResponse.isSuccess()) {
            value.close()
            throw ResponseException(commonResponse.code, commonResponse.msg)
        }
        val contentType = value.contentType()
        val charset = contentType?.charset(UTF_8) ?: UTF_8
        val inputStream = ByteArrayInputStream(response.toByteArray())
        val reader = InputStreamReader(inputStream, charset)
        val jsonReader = gson.newJsonReader(reader)

        value.use { _ ->
            val result = adapter.read(jsonReader)
            if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
                throw JsonIOException("JSON document was not fully consumed.")
            }
            return result
        }
    }
}

2.5 创建 RetrofitClient 以及业务接口

object RetrofitClient {
    private val instance: Retrofit by lazy {
        val logger = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }

        val client = OkHttpClient.Builder()
            .addInterceptor(logger)
            .addInterceptor(Interceptor { chain ->
                val originalRequest: Request = chain.request()
                val request = originalRequest.newBuilder()
                    .header("content-type", "application/json;charset:utf-8")
                    .build()
                chain.proceed(request)
            })
            .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager())
            .connectTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .build()

        Retrofit.Builder().baseUrl(HttpUrls.getBaseUrl())
            .client(client)
            .addConverterFactory(CustomGsonConverterFactory.create())
            .build()
    }


    // TestService 见下
    fun getTestService(): TestService {
        return instance.create(TestService::class.java)
    }
}

三、具体业务接口封装

3.1 data 部分定义

IDataSource

interface IDataSource {
    suspend fun getPrivacyList(): List<PrivacyInfo>
}

TestService

interface TestService {
    @POST("privacy/getPrivacyList")
    suspend fun getPrivacyList(@Body req: GetPrivacyReq): CommonResponse<List<PrivacyInfo>>
}

RemoteDataSource

class RemoteDataSource private constructor() : IDataSource {

    private lateinit var testService: TestService

    companion object {
        @Volatile
        private var instance: RemoteDataSource? = null

        fun getInstance(context: Context): RemoteDataSource {
            return instance ?: synchronized(this) {
                instance ?: RemoteDataSource().also {
                    it.testService = RetrofitClient.getTestService()
                    instance = it
                }
            }
        }
    }


    override suspend fun getPrivacyList(): List<PrivacyInfo> = withContext(Dispatchers.IO) {
        val req = buildGetPrivacyReq() // 创建入参
        val result = getResultOrNull {
            testService.getPrivacyList(req)
        }
        result?: mutableListOf()
    }

}

TestRepository

object TestRepository {
    private val localDataSource: LocalDataSource by lazy {
        LocalDataSource.getInstance(App.appContext)
    }

    private val remoteDataSource: RemoteDataSource by lazy {
        RemoteDataSource.getInstance(App.appContext)
    }


    suspend fun getPrivacyList(): List<PrivacyInfo> {
        return remoteDataSource.getPrivacyList()
    }
}

标签:基本,return,val,private,Retrofit,fun,import,用法,gson
From: https://www.cnblogs.com/joy99/p/17298754.html

相关文章

  • Promise基本用法
    JavaScript它的执行环境是单线程的,单线程就是任务只能一个一个的完成,这个任务完成之后才能执行下一个,它会阻塞其它任务。而异步模式可以一起执行多个任务。常见的异步模式有定时器,接口调用和事件函数,Promise就是接口调用里面的一种方式,它是es6提供的一种异步解决方案。简单来说......
  • 03. Jenkins - Groovy(基本语法)
    GroovyGroovy是一种基于Java平台的面向对象语言。在JenkinsPipeline中通常用它来增强Pipeline的功能。详细的语法可以参考:https://www.w3cschool.cn/groovy/groovy_overview.html字符串(String)//定义全局变量num=1job_name="Devops-demo-test"pipeline{......
  • python中shutil和shutil库的用法
    一、shutil目录和文件操作Pythonshutil库提供了对文件和目录复制、移动、删除、压缩、解压等操作。1.复制文件或目录shutil.copy(src,dst):复制文件或目录shutil.copyfile(src,dst):复制文件,src和dst只能是文件shutil.copytree(src,dst,dirs_exist_ok=False):复制目录,默......
  • ProtoBuf 基本使用
    一、是什么ProtocolBuffers,是Google公司开发的一种数据描述语言,是一种平台无关、语言无关、可扩展且类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。二、为什么更简单数据描述文件只需原来的1/10至1/3解析速度是原来的20倍至100倍减少了二义性生成了更......
  • 大型互联网系统技术架构设计的4个基本要素
    作为一名大厂SRE,对什么是好产品(技术架构角度)有深刻的感悟。一个好产品的技术架构不仅在优秀的代码本身,更体现在后期的易运维性、可扩展性、高可用性上。随着用户体量、产品功能、IaaS、PaaS的变化甚至员工的离职,随时需要动态调整架构改变策略来应对各种问题,而这些场景都是对技术架......
  • 逍遥自在学C语言 | 位运算符的基础用法
    前言一、人物简介第一位闪亮登场,有请今后会一直教我们C语言的老师——自在。第二位上场的是和我们一起学习的小白程序猿——逍遥。二、构成和表达方式位运算符是一组用于在二进制数之间进行操作的运算符运算符名称示例&位与a&&b|位或a|b......
  • Python 日期和时间用法超强总结
    Python中处理日期和时间的模块Python提供了time和datetime模块,可以帮助我们轻松获取和修改日期和时间,下面让我们来逐一了解一下。time模块该模块包括使用时间执行各种操作所需的所有与时间相关的功能,它还允许我们访问多种用途所需的时钟类型。内置函数:请看下表,它描述了时......
  • C# ?用法
    1、可空类型修饰符(?),如:int?x=null;//可空类型默认值都是null,而值类型如int默认值为0、bool默认值为false等bool?result=true;2、三元运算符(?,如:boolf=false;returnf==true?1:0;如果f为true则返回1,否则返回03、空合并运算符(??)如:a??b当a为null时则返回b,a不为null时......
  • Python中排序函数sorted的用法
    Python中有两个排序函数:sorted与sort其中,sorted的用法与c++中的sort是基本一样的本文只介绍sorted用法sorted返回的是一个新的迭代对象,一般默认返回一个list如:对tensor进行排序,返回了一个list我们一般建议直接对list进行排序这样得到的最终还是list如:自定义排序......
  • Markdown 基本使用
    目录一、软件Typora二、Markdown基本知识1.Markdown介绍2.Markdown常用语法标题字体序列引用代码框上、下划线和上、下标一、软件TyporaTypora是一款免费轻便简洁的Markdown编辑器,支持即时渲染技术,这也是与其他Markdown编辑器最显著的区别。即时渲染,也就是Markdown......