The vernacular
- We definitely needed a unified wrapper around things like POST and GET, so we defined one
A unified interface class for network requests
–HttpApi
- Since post and GET have requests, there must be a callback for success and failure of the request, so we need to define this
IHttpCallback
This is needed in HttpApi - Now that We’ve defined HttpApi, we also need a concrete class named, for example, HttpApi
OkHttpApi
And all future requests, OkHttpApi
The get and POST methods, which are copied inside, need to do something specific. Of course, we can’t avoid building a Client instance, where we can configure many parameters, such as timeout, such as interceptor, such as Cookie
In plain English, that’s what it means.
(This example is not Retrofit yet, a better practice would be Retrofit plus Jetpack, I’ll write one later)
In the code
- HttpApi
package com.am.kttt
import com.am.kttt.support.IHttpCallback
/** * Unified interface class for network requests */
interface HttpApi {
/** * Abstract HTTP get request encapsulation, asynchronous */
fun get(params: Map<String, Any>, urlStr: String, callback: IHttpCallback)
/** * Abstract HTTP synchronous GET request */
fun getSync(params: Map<String, Any>, urlStr: String): Any? {
return Any()
}
/** * Abstract HTTP post request asynchronous */
fun post(body: Any, urlStr: String, callback: IHttpCallback)
/** * Abstract Http POST synchronization request */
fun postSync(body: Any, urlStr: String): Any? = Any()
fun cancelRequest(tag: Any)
fun cancelAllRequest(a)
}
Copy the code
.
- OkHttpApi
package com.am.kttt
import androidx.collection.SimpleArrayMap
import com.google.gson.Gson
import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import com.am.kttt.config.HeaderInterceptor
import com.am.kttt.config.KtHttpLogInterceptor
import com.am.kttt.config.LocalCookieJar
import com.am.kttt.config.RetryInterceptor
import com.am.kttt.support.IHttpCallback
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
/** * OkHttpApi core class * implement get and POST operations * build Client, add specific configuration; Interceptors, cookies, etc. */
class OkHttpApi : HttpApi {
var maxRetry = 0// Maximum number of retries
// Store the request for cancellation
private val callMap = SimpleArrayMap<Any, Call>()
//okHttpClient
private val mClient = OkHttpClient.Builder()
.callTimeout(10, TimeUnit.SECONDS)// Complete request timeout duration, from the time the request is initiated to the time the return data is received, default value 0, unlimited,
.connectTimeout(10, TimeUnit.SECONDS)// Duration of establishing a connection with the server. The default value is 10 seconds
.readTimeout(10, TimeUnit.SECONDS)// How long it takes to read the data returned by the server
.writeTimeout(10, TimeUnit.SECONDS)// Duration of writing data to the server. The default value is 10 seconds
.retryOnConnectionFailure(true)/ / reconnection
.followRedirects(false)/ / redirection
.cache(Cache(File("sdcard/cache"."okhttp"), 1024))
// .cookieJar(CookieJar.NO_COOKIES)
.cookieJar(LocalCookieJar())
.addNetworkInterceptor(HeaderInterceptor())// Public header interceptor
.addNetworkInterceptor(KtHttpLogInterceptor {
logLevel(KtHttpLogInterceptor.LogLevel.BODY)
})// Add a network interceptor that intercepts okHttp network requests. Unlike the application interceptor, this interceptor is aware of all network states, such as redirects.
.addNetworkInterceptor(RetryInterceptor(maxRetry))
// .hostnameVerifier(HostnameVerifier { p0, p1 -> true })
// .sslSocketFactory(sslSocketFactory = null,trustManager = null)
.build()
override fun get(params: Map<String, Any>, urlStr: String, callback: IHttpCallback) {
val urlBuilder = urlStr.toHttpUrl().newBuilder()
params.forEach { entry ->
urlBuilder.addEncodedQueryParameter(entry.key, entry.value.toString())
}
val request = Request.Builder()
.get()
.tag(params)
.url(urlBuilder.build())
.cacheControl(CacheControl.FORCE_NETWORK)
.build()
val newCall = mClient.newCall(request)
// Store the request for cancellation
callMap.put(request.tag(), newCall)
newCall.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback.onFailed(e.message)
}
override fun onResponse(call: Call, response: Response){ callback.onSuccess(response.body? .string()) } }) }override fun post(body: Any, urlStr: String, callback: IHttpCallback) {
val reqBody = Gson().toJson(body).toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.post(reqBody)
.url(urlStr)
.tag(body)
.build()
val newCall = mClient.newCall(request)
// Store the request for cancellation
callMap.put(request.tag(), newCall)
newCall.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback.onFailed(e.message)
}
override fun onResponse(call: Call, response: Response){ callback.onSuccess(response.body? .string()) } }) }/** * cancel the network request. Tag is the id of each request
override fun cancelRequest(tag: Any) {
callMap.get(tag)? .cancel() }/** * Cancel all network requests */
override fun cancelAllRequest(a) {
for (i in 0 until callMap.size()) {
callMap.get(callMap.keyAt(i))? .cancel() } } }Copy the code
.
- MainActivity
package com.am.kttt
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.am.kttt.databinding.ActivityMainBinding
import com.blankj.utilcode.util.GsonUtils
import com.blankj.utilcode.util.LogUtils
//import kotlinx.android.synthetic.main.activity_main.*
import com.am.kttt.model.NetResponse
import com.am.kttt.support.NetTranUtils
import com.am.kttt.support.IHttpCallback
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
val httpApi: HttpApi = OkHttpApi()
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btGet.setOnClickListener(View.OnClickListener {
httpApi.get(
emptyMap(),
"https://wanandroid.com/wxarticle/chapters/json".object : IHttpCallback {
override fun onSuccess(data: Any?). {
LogUtils.d("success result : ${data.toString()}")
runOnUiThread {
binding.tvText.text = data.toString()
}
}
override fun onFailed(error: Any?). {
LogUtils.d("failed msg : ${error.toString()}")
}
})
})
binding.btPost.setOnClickListener(View.OnClickListener {
val mapParams = mapOf("username" to "123456" ,"username" to "password" ,"repassword" to "123456" )
httpApi.post(
mapParams,
"https://www.wanandroid.com/user/register".object : IHttpCallback {
override fun onSuccess(data: Any?). {
LogUtils.d("success result : ${data.toString()}")
runOnUiThread {
val toString = data.toString()
binding.tvText.text = toString
/*val (code, dataObj, message) = GsonUtils.fromJson
( toString, NetResponse::class.java ) if(dataObj! DecodeData (dataobj.toString ()?:"")}else{binding.tvtext.text = "empty data"}*/
}}override fun onFailed(error: Any?). {
LogUtils.d("failed msg : ${error.toString()}")
binding.tvText.text = "${error.toString()}"}})})}}Copy the code
.
xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/bt_get"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="To launch the get"
android:layout_marginTop="10dp"
/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/bt_post"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="By Post"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
/>
<TextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code
.
- HeaderInterceptor
package com.am.kttt.config
import com.blankj.utilcode.util.*
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import okhttp3.CacheControl
import okhttp3.FormBody
import okhttp3.Interceptor
import okhttp3.Response
import okio.Buffer
/** * Project specific, add public header interceptor */
class HeaderInterceptor : Interceptor {
companion object {
private val gson = GsonBuilder()
.enableComplexMapKeySerialization()
.create()
private val mapType = object : TypeToken<Map<String, Any>>() {}.type
}
override fun intercept(chain: Interceptor.Chain): Response {
val originRequest = chain.request()
// Additional public headers, encapsulation clientInfo,deviceInfo, etc. You can also customize the headers field in a POST request
// Note that only the following fields can be used by the server for verification. The fields can be missing, but cannot be added because the server has not processed them
val attachHeaders = mutableListOf<Pair<String, String>>(
"appid" to NET_CONFIG_APPID,
"platform" to "android".The yAPI platform tag does not
"timestamp" to System.currentTimeMillis().toString(),
"brand" to DeviceUtils.getManufacturer(),
"model" to DeviceUtils.getModel(),
"uuid" to DeviceUtils.getUniqueDeviceId(),
"network" to NetworkUtils.getNetworkType().name,
"system" to DeviceUtils.getSDKVersionName(),
"version" to AppUtils.getAppVersionName()
)
// Tokens are passed only if they have a value,
val tokenstr = ""
val localToken = SPStaticUtils.getString(SP_KEY_USER_TOKEN, tokenstr)
if (localToken.isNotEmpty()) {
attachHeaders.add("token" to localToken)
}
val signHeaders = mutableListOf<Pair<String, String>>()
signHeaders.addAll(attachHeaders)
// Get request parameters
if (originRequest.method == "GET") { originRequest.url.queryParameterNames.forEach { key -> signHeaders.add(key to (originRequest.url.queryParameter(key) ? :""))}}// Post requests in formBody form, or json form, need to iterate through the internal fields to participate in the calculation of sign
val requestBody = originRequest.body
if (originRequest.method == "POST") {
//formBody
if (requestBody is FormBody) {
for (i in 0 until requestBody.size) {
signHeaders.add(requestBody.name(i) to requestBody.value(i))
}
}
// Json body needs to deserialize the requestBody into JSON to map Application /json
if(requestBody? .contentType()? .type =="application"&& requestBody.contentType()? .subtype =="json") {
kotlin.runCatching {
val buffer = Buffer()
requestBody.writeTo(buffer)
buffer.readByteString().utf8()
}.onSuccess {
val map = gson.fromJson<Map<String, Any>>(it, mapType)
map.forEach { entry ->
// FIXME:2020/8/25 value Current JSON layer level
signHeaders.add(entry.key to entry.value.toString())
}
}
}
}
// Todo algorithm: all must be non-null arguments!! Sign = MD5 (headers and params key=value &, appkey =value)
val signValue = signHeaders
.sortedBy { it.first }
.joinToString("&") { "${it.first}=${it.second}" }
.plus("&appkey=$NET_CONFIG_APPKEY")
val newBuilder = originRequest.newBuilder()
.cacheControl(CacheControl.FORCE_NETWORK)
attachHeaders.forEach { newBuilder.header(it.first, it.second) }
newBuilder.header("sign", EncryptUtils.encryptMD5ToString(signValue))
if (originRequest.method == "POST"&& requestBody ! =null) {
newBuilder.post(requestBody)
} else if (originRequest.method == "GET") {
newBuilder.get()}return chain.proceed(newBuilder.build())
}
}
Copy the code
.
- KtHttpLogInterceptor
package com.am.kttt.config
import android.util.Log
import okhttp3.*
import okio.Buffer
import com.am.kttt.support.NetTranUtils
import java.net.URLDecoder
import java.text.SimpleDateFormat
import java.util.*
/** * An interceptor for logging okHttp web logs */
class KtHttpLogInterceptor(block: (KtHttpLogInterceptor.() -> Unit)? = null) : Interceptor {
private var logLevel: LogLevel = LogLevel.NONE// Prints the date mark
private var colorLevel: ColorLevel = ColorLevel.DEBUG// The default is debug logcat
private var logTag = TAG// The Logcat Tag of the log
init{ block? .invoke(this)}/** * Set LogLevel */
fun logLevel(level: LogLevel): KtHttpLogInterceptor {
logLevel = level
return this
}
/** * Set colorLevel */
fun colorLevel(level: ColorLevel): KtHttpLogInterceptor {
colorLevel = level
return this
}
/** * sets the Log Tag */
fun logTag(tag: String): KtHttpLogInterceptor {
logTag = tag
return this
}
override fun intercept(chain: Interceptor.Chain): Response {
/ / request
val request = chain.request()
/ / response
return kotlin.runCatching { chain.proceed(request) }
.onFailure {
it.printStackTrace()
logIt(
it.message.toString(),
ColorLevel.ERROR
)
}.onSuccess { response ->
if (logLevel == LogLevel.NONE) {
return response
}
// Log the request
logRequest(request, chain.connection())
// Record the response log
logResponse(response)
}.getOrThrow()
}
/** * log the request */
private fun logRequest(request: Request, connection: Connection?). {
val sb = StringBuilder()
sb.appendln("\r\n")
sb.appendln("- > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - >")
when (logLevel) {
LogLevel.NONE -> {
/*do nothing*/
}
LogLevel.BASIC -> {
logBasicReq(sb, request, connection)
}
LogLevel.HEADERS -> {
logHeadersReq(sb, request, connection)
}
LogLevel.BODY -> {
logBodyReq(sb, request, connection)
}
}
sb.appendln("- > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - > - >")
logIt(sb)
}
//region log request
private fun logBodyReq(
sb: StringBuilder,
request: Request,
connection: Connection?). {
logHeadersReq(sb, request, connection)
// Read the contents of the request Body
val req = request.newBuilder().build()
valsink = Buffer() req.body? .writeTo(sink) sb.appendln("RequestBody: ${sink.readUtf8()}")}private fun logHeadersReq(
sb: StringBuilder,
request: Request,
connection: Connection?). {
logBasicReq(sb, request, connection)
val headersStr = request.headers.joinToString("") { header ->
"Request Header: {${header.first}=${header.second}}\n"
}
sb.appendln(headersStr)
}
private fun logBasicReq(
sb: StringBuilder,
request: Request,
connection: Connection?). {
sb.appendln("The request method:${request.method} url: ${decodeUrlStr(request.url.toString())} tag: ${request.tag()} protocol: ${connection? .protocol() ? : Protocol.HTTP_1_1}")}//endregion
/** * Record the response log * [response] Response data */
private fun logResponse(response: Response) {
val sb = StringBuffer()
sb.appendln("\r\n")
sb.appendln("< < < < < < - < < - < < < < < < - < < - < < < < < < - < < - < < < < - < < < < < < - < < - < < < < < < - < < - < < - < < < < - < < < < - < <")
when (logLevel) {
LogLevel.NONE -> {
/*do nothing*/
}
LogLevel.BASIC -> {
logBasicRsp(sb, response)
}
LogLevel.HEADERS -> {
logHeadersRsp(response, sb)
}
LogLevel.BODY -> {
logHeadersRsp(response, sb)
//body.string throws AN I/O exception
kotlin.runCatching {
// Peek is similar to clone stream data, monitoring, snooping, can not directly use the original body stream data as a log, will consume IO, so this is peek, monitoring
val peekBody = response.peekBody(1024 * 1024)
sb.appendln(NetTranUtils.unicodeDecode(peekBody.string()))
}.getOrNull()
}
}
sb.appendln("< < < < < < - < < - < < < < < < - < < - < < < < < < - < < - < < < < < < - < < - < < < < < < - < < - < < < < < < - < < - < < < < < < - < < - < < < < < < - < < - < < < < - < < < < - < < - < < < < - < < -- < < < < < < - < < < < - < < < < - < <")
logIt(sb, ColorLevel.INFO)
}
//region log response
private fun logHeadersRsp(response: Response, sb: StringBuffer) {
logBasicRsp(sb, response)
val headersStr = response.headers.joinToString(separator = "") { header ->
"Response Header: {${header.first}=${header.second}}\n"
}
sb.appendln(headersStr)
}
private fun logBasicRsp(sb: StringBuffer, response: Response) {
sb.appendln("The response protocol:${response.protocol} code: ${response.code} message: ${response.message}")
.appendln("Response request Url:${decodeUrlStr(response.request.url.toString())}")
.appendln(
"Response sentRequestTime:${ toDateTimeStr( response.sentRequestAtMillis, MILLIS_PATTERN ) } receivedResponseTime: ${ toDateTimeStr( response.receivedResponseAtMillis, MILLIS_PATTERN ) }")}//endregion
/** * For urL-encoded string decoding */
private fun decodeUrlStr(url: String): String? {
return kotlin.runCatching {
URLDecoder.decode(url, "utf-8")
}.onFailure { it.printStackTrace() }.getOrNull()
}
/** * Print logs * [any] Data object to print * [tempLevel] for temporary adjustment of print color level */
private fun logIt(any: Any, tempLevel: ColorLevel? = null) {
when(tempLevel ? : colorLevel) { ColorLevel.VERBOSE -> Log.v(logTag, any.toString()) ColorLevel.DEBUG -> Log.d(logTag, any.toString()) ColorLevel.INFO -> Log.i(logTag, any.toString()) ColorLevel.WARN -> Log.w(logTag, any.toString()) ColorLevel.ERROR -> Log.e(logTag, any.toString()) } }companion object {
private const val TAG = "<KtHttp>"// The default TAG
// Time formatting
const val MILLIS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSSXXX"
// Converts to a formatted time string
fun toDateTimeStr(millis: Long, pattern: String): String {
return SimpleDateFormat(pattern, Locale.getDefault()).format(millis)
}
}
/** * The range of logs to be printed */
enum class LogLevel {
NONE,/ / not print at all
BASIC,// Paper prints the beginning of the line, request/response
HEADERS,// Prints all headers for the request and response
BODY,// Print all
}
/** ** Logcat is divided into V, D, I, W, e */
enum class ColorLevel {
VERBOSE,
DEBUG,
INFO,
WARN,
ERROR
}
}
Copy the code
.
- LocalCookieJar
package com.am.kttt.config
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
/** * cookieJar implementation class for persistence */
internal class LocalCookieJar : CookieJar {
// Local storage of cookies
private val cache = mutableListOf<Cookie>()
override fun loadForRequest(url: HttpUrl): List<Cookie> {
// An expired Cookie
val invalidCookies: MutableList<Cookie> = ArrayList()
// A valid Cookie
val validCookies: MutableList<Cookie> = ArrayList()
for (cookie in cache) {
if (cookie.expiresAt < System.currentTimeMillis()) {
// Determine whether it expires
invalidCookies.add(cookie)
} else if (cookie.matches(url)) {
// Match Cookie to URL
validCookies.add(cookie)
}
}
// Remove expired cookies from the cache
cache.removeAll(invalidCookies)
// Return List
for Request to set
return validCookies
}
/** * Save the cookie */
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
cache.addAll(cookies)
}
}
Copy the code
.
- NetPjConfig.kt
package com.am.kttt.config
/** * App configuration */
// Region network interaction configuration parameters
const val NET_CONFIG_APPID = "xxxxxx"// appID marks the project APK
const val NET_CONFIG_APPKEY = "xxxxxxH\$pHx\$!"// AppKey is used to parse encrypted data returned by the server
//endregion
//region Data field key of the local cache
const val SP_KEY_USER_TOKEN = "sp_key_user_token"// Indicates the user's token
//endregion
Copy the code
.
- RetryInterceptor
package com.am.kttt.config
import android.util.Log
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
/** * Retries the request's network interceptor */
class RetryInterceptor(private val maxRetry: Int = 0) : Interceptor {
private var retriedNum: Int = 0// The number of retry times, so the total number of requests may be the original 1 + maxRetry
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
Log.d("RetryInterceptor"."Intercept Line 29: Current retriedNum=$retriedNum")
var response = chain.proceed(request)
while(! response.isSuccessful && retriedNum < maxRetry) { retriedNum++ Log.d("RetryInterceptor"."Intercept line 33: Current retriedNum=$retriedNum")
response = chain.proceed(request)
}
return response
}
}
Copy the code
.
- NetResponse
package com.am.kttt.model
/** * The underlying network returns a data structure */
data class NetResponse(
val code: Int./ / the response code
val data: Any? .// Response data content
val message: String// The result description of the response data
)
Copy the code
.
- IHttpCallback
package com.am.kttt.support
/** * Network request interface callback */
interface IHttpCallback {
/** * Network request successful callback * [data] returns the data result of the callback *@paramData returns the result of the callback */
fun onSuccess(data: Any?).
/** * Interface callback failed * [error] Data class of the error message */
fun onFailed(error: Any?).
}
Copy the code
.
- NetTranUtils
package com.am.kttt.support;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.EncryptUtils;
import com.am.kttt.config.NetPjConfigKt;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** * Network conversion tools * Chinese to Unicode, Unicode to Chinese * encryption and decryption */
public class NetTranUtils {
private NetTranUtils() {
}
/** * To unicode **@param string
* @return* /
public static String unicodeEncode(String string) {
char[] utfBytes = string.toCharArray();
String unicodeBytes = "";
for (int i = 0; i < utfBytes.length; i++) {
String hexB = Integer.toHexString(utfBytes[i]);
if (hexB.length() <= 2) {
hexB = "00" + hexB;
}
unicodeBytes = unicodeBytes + "\\u" + hexB;
}
return unicodeBytes;
}
/** * Unicode to Chinese **@param string
* @return* /
public static String unicodeDecode(String string) {
Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
Matcher matcher = pattern.matcher(string);
char ch;
while (matcher.find()) {
ch = (char) Integer.parseInt(matcher.group(2), 16);
// Integer.valueOf("", 16);
string = string.replace(matcher.group(1), ch + "");
}
return string;
}
/** * Parse the returned data **@paramDataStr Undecrypted response data *@returnDecrypted data String */
@Nullable
public static String decodeData(@Nullable String dataStr) {
// Java code, no automatic null judgment, need to handle their own
if(dataStr ! =null) {
System.out.println("DataStr = = = = = = = = :"+dataStr);
return new String(EncryptUtils.decryptBase64AES(
dataStr.getBytes(), NetPjConfigKt.NET_CONFIG_APPKEY.getBytes(),
"AES/CBC/PKCS7Padding"."J#y9sJesv*5HmqLq".getBytes()
));
} else {
return null; }}}Copy the code
.
The code is all up there
- Under the APP build. Gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.am.kttt"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true}}dependencies {
implementation 'androidx. Core: the core - KTX: 1.7.0'
implementation 'androidx. Appcompat: appcompat: 1.4.1'
implementation 'com. Google. Android. Material: material: 1.5.0'
implementation 'androidx. Constraintlayout: constraintlayout: 2.1.3'
implementation 'androidx. Lifecycle: lifecycle - livedata - KTX: against 2.4.1'
implementation 'androidx. Lifecycle: lifecycle - viewmodel - KTX: against 2.4.1'
implementation 'androidx. Navigation: navigation - fragments - KTX: against 2.4.1'
implementation 'androidx. Navigation: navigation - UI - KTX: against 2.4.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx. Test. Ext: junit: 1.1.3'
androidTestImplementation 'androidx. Test. Espresso: espresso - core: 3.4.0'
// Required for this project
implementation("Com. Squareup. Okhttp3: okhttp: 4.8.0")
implementation("Com. Squareup. Okhttp3: logging - interceptor: 4.8.0")
implementation("Com. Google. Code. Gson: gson: 2.8.6")
implementation 'com. Blankj: utilcodex: 1.29.0'
implementation "Androidx. Constraintlayout: constraintlayout: 2.1.0."
}
Copy the code
.
- Build. Gradle under the project
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "Com. Android. Tools. Build: gradle: 7.0.1"
classpath "Org. Jetbrains. Kotlin: kotlin - gradle - plugin: 1.5.20"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files}}task clean(type: Delete) {
delete rootProject.buildDir
}
Copy the code
.
Running effect
A post
Don’t worry about what’s displayed, the interface is just random, and then this returns data that looks a lot like GET.
Initiate the get
It’s a random interface. .
Pan.baidu.com/s/1NiqjMmAn…