【Retrofit】Android POST Raw JSON + HttpLoggingInterceptor 範例
Retrofit 是一個 Android 和 Java 的 HTTP 客戶端,旨在簡化 HTTP API 連接和請求建構。
它使用簡易的 API 格式(不需要 XML 或 JSON 序列化),同時提供迅速的加載時間和方便的庫櫃組織。有時可能需要使用序列化,Retrofit 也支持在 API 層面上定義編碼格式到 Java 類型,並支持常見的訪問控制信息(例如設備 ID)。
文章目錄
- Retrofit & okhttp 導入
- Retrofit Interceptor
- Retrofit RetrofitUtil
- Retrofit Model
- Retrofit Service
- Retrofit
- 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