Retrofit 的基本用法

一、添加依賴和網絡權限

添加依賴

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()
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章