Retrofit

【Retrofit】Android POST Raw JSON + HttpLoggingInterceptor 範例

【Retrofit】Android POST Raw JSON + HttpLoggingInterceptor 範例

Retrofit 是一個 Android 和 Java 的 HTTP 客戶端,旨在簡化 HTTP API 連接和請求建構。

它使用簡易的 API 格式(不需要 XML 或 JSON 序列化),同時提供迅速的加載時間和方便的庫櫃組織。有時可能需要使用序列化,Retrofit 也支持在 API 層面上定義編碼格式到 Java 類型,並支持常見的訪問控制信息(例如設備 ID)。


文章目錄

  1. Retrofit & okhttp 導入
  2. Retrofit Interceptor
  3. Retrofit RetrofitUtil
  4. Retrofit Model
  5. Retrofit Service
  6. Retrofit
  7. Developer Documents Retrofit

1.Paging3 & Retrofit & Fragment KTX 導入

AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
build.gradle
dependencies {
    implementation "androidx.fragment:fragment-ktx:1.5.5"
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}

2.Retrofit Interceptor

HttpLoggingInterceptor.kt
class HttpLoggingInterceptor : Interceptor {
    private val logger = Logger.DEFAULT
    private var level = Level.NONE

    enum class Level {
        NONE,
        HEADERS,
        BODY
    }

    interface Logger {
        fun log(message: String)

        companion object {
            val DEFAULT: Logger = object : Logger {
                override fun log(message: String) {
                    Log.w("RDClient", "logger >> \nmessage")
                }
            }
        }
    }

    fun setLevel(level: Level): HttpLoggingInterceptor {
        this.level = level
        return this
    }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val level = level
        val request = chain.request()
        if (level ==== Level.NONE) {
            return chain.proceed(request)
        }
        val logBody = level == Level.BODY
        val logHeaders = logBody || level == Level.HEADERS
        val requestBody = request.body
        val hasRequestBody = requestBody != null
        val connection = chain.connection()
        val protocol = connection?.protocol() ?: Protocol.HTTP_1_1
        var requestStartMessage = "--> " + request.method + ' ' + request.url + ' ' + protocol
        if (!logHeaders && hasRequestBody) {
            requestStartMessage += " (" + requestBody!!.contentLength() + "-byte body)"
        }
        logger.log(requestStartMessage)

        if (logHeaders) {
            if (hasRequestBody) {
                if (requestBody!!.contentType() != null) {
                    logger.log("Content-Type: " + requestBody.contentType())
                }
                if (requestBody.contentLength() != -1L) {
                    logger.log("Content-Length: " + requestBody.contentLength())
                }
            }
            val headers = request.headers
            var i = 0
            val count = headers.size

            while(i<count) {
                val name = headers.name(i)
                if (!"Content-Type".equals(name, ignoreCase = true) && !"Content-Length".equals(name, ignoreCase = true)) {
                    logger.log(name + ": " + headers.value(i))
                }
                i++
            }

            if (!logBody || !hasRequestBody) {
                logger.log("--> END " + request.method)
            } else if (bodyEncoded(request.headers)) {
                logger.log("--> END " + request.method + " (encoded body omitted)")
            } else {
                val buffer = Buffer()
                requestBody!!.writeTo(buffer)
                var charset = UTF8
                val contentType = requestBody.contentType()
                if (contentType != null) {
                    charset = contentType.charset(UTF8)
                }
                logger.log("")
                if (isPlaintext(buffer)) {
                    logger.log(
                        buffer.readString(
                            Objects.requireNonNull(
                                charset
                            )
                        )
                    )
                    logger.log("--> END " + request.method + " (" + requestBody.contentLength() + "-byte body)")
                } else {
                    logger.log("--> END " + request.method + " (binary " + requestBody.contentLength() + "-byte body omitted)")
                }
            }
        }
        val startNs = System.nanoTime()
        val response = chain.proceed(request)
        val tookMs =
            TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
        val responseBody = response.body
        val contentLength =
            responseBody?.contentLength()
        val bodySize =
            if (contentLength != -1L) "contentLength-byte" else "unknown-length"
        logger.log(
            "<-- " + response.code + ' ' + response.message + ' ' + response.request.url + " (" + tookMs + "ms" + (if (!logHeaders) ", " +
                    bodySize + " body" else "") + ')'
        )
        if (logHeaders) {
            val headers = response.headers
            var i = 0
            val count = headers.size
            while (i < count) {
                logger.log(headers.name(i) + ": " + headers.value(i))
                i++
            }
            if (bodyEncoded(response.headers)) {
                logger.log("<-- END HTTP (encoded body omitted)")
            } else {
                val source = responseBody!!.source()
                source.request(Long.MAX_VALUE) // Buffer the entire body.
                val buffer = source.buffer()
                var charset = UTF8
                val contentType = responseBody.contentType()
                if (contentType != null) {
                    charset = try {
                        contentType.charset(UTF8)
                    } catch (e: UnsupportedCharsetException) {
                        logger.log("")
                        logger.log("Couldn't decode the response body; charset is likely malformed.")
                        logger.log("<-- END HTTP")
                        return response
                    }
                }
                if (!isPlaintext(buffer)) {
                    logger.log("")
                    logger.log("<-- END HTTP (binary " + buffer.size + "-byte body omitted)")
                    return response
                }
                if (contentLength != 0L) {
                    logger.log("")
                    logger.log(
                        buffer.clone().readString(
                            Objects.requireNonNull(charset)
                        )
                    )
                }
                logger.log("<-- END HTTP (" + buffer.size + "-byte body)")
            }
        }
        return response
    }

    private fun bodyEncoded(headers: Headers): Boolean {
        val contentEncoding = headers["Content-Encoding"]
        return contentEncoding != null && !contentEncoding.equals("identity", ignoreCase = true)
    }

    companion object {
        private val UTF8 = StandardCharsets.UTF_8
        fun isPlaintext(buffer: Buffer): Boolean {
            return try {
                val prefix = Buffer()
                val byteCount = if (buffer.size < 64) buffer.size else 64
                buffer.copyTo(prefix, 0, byteCount)
                for (i in 0..15) {
                    if (prefix.exhausted()) {
                        break
                    }
                    if (Character.isISOControl(prefix.readUtf8CodePoint())) {
                        return false
                    }
                }
                true
            } catch (e: EOFException) {
                false
            }
        }
    }
}

3.Retrofit RetrofitUtil

RetrofitUtils.kt
class RetrofitUtils {

    companion object {
        private const val DEFAULT_TIMEOUT = 30L
        val instance: RetrofitUtils by lazy { RetrofitUtils() }
    }

    fun <T> getService(clazz: Class<T>): T {
        return retrofit.create(clazz)
    }

    private fun getClient(): OkHttpClient {
        val logInterceptor = HttpLoggingInterceptor()

        if (BuildConfig.DEBUG) {
            logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
        } else {
            logInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE)
        }

        return OkHttpClient.Builder()
            .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
            .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
            .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
            .addInterceptor(logInterceptor)
            .retryOnConnectionFailure(true).build()
    }

    private val retrofit = Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl("https://api.openai.com/v1/")
        .client(getClient())
        .build()
}

4.Retrofit Model

CompletionsSub.kt
data class CompletionsSub(
    val model: String,
    val prompt: String,
    val max_tokens: Int
)
CompletionsRec.kt
data class CompletionsRec(
    val created: Int,
    val model: String,
    val choices: List<Choices>,
    val usage: Usage
)

data class Choices(
    val text: String,
    val index: Int,
    val finish_reason: String
)

data class Usage(
    val prompt_tokens: Int,
    val completion_tokens: Int,
    val total_tokens: Int
)

5.Retrofit Service

OpenAIService.kt
interface OpenAIService {

    @Headers("Authorization: Bearer sk-8yqIPe85wc5YKZINDCT3BlbkFJrB")
    @POST("completions")
    suspend fun completions(@Body completionsSub: CompletionsSub) : CompletionsRec

    @FormUrlEncoded
    @POST("completions")
    suspend fun completions(@Field("model") phone: String, @Field("type") type: String): CompletionsRec

}

6.Retrofit

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        val service = RetrofitUtils.instance.getService(OpenAIService::class.java)

        lifecycleScope.launch {

            service.completions(CompletionsSub(
                "text-davinci-003",
                "哈囉",
                500
            ))

            service
                .completions(
                    "text-davinci-003",
                    "哈囉"
                )
        }

    }
}

7.Developer Documents Retrofit

Open in Documents Retrofit

發表迴響