Kotlin Okhttp的安卓网络请求库封装,白话一下。

4,076 阅读5分钟

image.png

白话文

  • 我们肯定需要一个post和get之类的统一封装,所以定义了一个网络请求的统一接口类——HttpApi
  • post和get的既然有请求,那就必然需要有请求成功和失败的回调,所以这个时候我们需要定义 IHttpCallback,这个在HttpApi里面需要用到
  • 定义好了HttpApi,我们还需要一个具体的类,比如命名为 OkHttpApi,以后所有的请求,
  • OkHttpApi里面复写的get和post方法,需要做具体的事情。当然里面,避免不了Build一个Client实例,在Client里面,我们可以配置许多参数,比如超时,比如拦截器,比如Cookie

大白话来说,就是这么个意思。


(本例暂未结合Retrofit,更好的实践应该是结合Retrofit加Jetpack,后面会写一个)


上代码

  • HttpApi
package com.am.kttt

import com.am.kttt.support.IHttpCallback

/**
 * 网络请求的统一接口类
 */
interface HttpApi {

    /**
     * 抽象的http的get请求封装,异步
     */
    fun get(params: Map<String, Any>, urlStr: String, callback: IHttpCallback)

    /**
     * 抽象的http同步的 get请求
     */
    fun getSync(params: Map<String, Any>, urlStr: String): Any? {
        return Any()
    }

    /**
     * 抽象的http的post的请求 异步
     */
    fun post(body: Any, urlStr: String, callback: IHttpCallback)

    /**
     * 抽象的Http的post 同步请求
     */
    fun postSync(body: Any, urlStr: String): Any? = Any()


    fun cancelRequest(tag: Any)

    fun cancelAllRequest()
}

.
.

  • 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 核心操作类
 * 具体实现get和post的操作
 * 构建Client,添加具体配置,;拦截器,Cookie等
 */
class  OkHttpApi : HttpApi {

    var maxRetry = 0//最大重试 次数

    //存储请求,用于取消
    private val callMap = SimpleArrayMap<Any, Call>()

    //okHttpClient
    private val mClient = OkHttpClient.Builder()
        .callTimeout(10, TimeUnit.SECONDS)//完整请求超时时长,从发起到接收返回数据,默认值0,不限定,
        .connectTimeout(10, TimeUnit.SECONDS)//与服务器建立连接的时长,默认10s
        .readTimeout(10, TimeUnit.SECONDS)//读取服务器返回数据的时长
        .writeTimeout(10, TimeUnit.SECONDS)//向服务器写入数据的时长,默认10s
        .retryOnConnectionFailure(true)//重连
        .followRedirects(false)//重定向
        .cache(Cache(File("sdcard/cache", "okhttp"), 1024))
//        .cookieJar(CookieJar.NO_COOKIES)
        .cookieJar(LocalCookieJar())
        .addNetworkInterceptor(HeaderInterceptor())//公共header的拦截器
        .addNetworkInterceptor(KtHttpLogInterceptor {
            logLevel(KtHttpLogInterceptor.LogLevel.BODY)
        })//添加网络拦截器,可以对okHttp的网络请求做拦截处理,不同于应用拦截器,这里能感知所有网络状态,比如重定向。
        .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)
        //存储请求,用于取消
        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)
        //存储请求,用于取消
        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())
            }

        })
    }


    /**
     * 取消网络请求,tag就是每次请求的id 标记,也就是请求的传参
     */
    override fun cancelRequest(tag: Any) {
        callMap.get(tag)?.cancel()
    }


    /**
     * 取消所有网络请求
     */
    override fun cancelAllRequest() {
        for (i in 0 until callMap.size()) {
            callMap.get(callMap.keyAt(i))?.cancel()
        }
    }
}

.
.

  • 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<NetResponse>(
                                toString,
                                NetResponse::class.java
                            )
                            if(dataObj!=null){
                                binding.tvText.text = NetTranUtils.decodeData(dataObj.toString()?:"")
                            }else{
                                binding.tvText.text = "空数据"

                            }*/

                        }
                    }

                    override fun onFailed(error: Any?) {
                        LogUtils.d("failed msg : ${error.toString()}")

                        binding.tvText.text = "${error.toString()}"
                    }

                })
        })
    }
}

.

xml

<?xml version="1.0" encoding="utf-8"?>
<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="发起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="发起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>

.
.

  • 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


/**
 * 项目相关,添加公共header的拦截器
 */
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()

        //附加的公共headers,封装clientInfo,deviceInfo等。也可以在post请求中,自定义封装headers的字段体内容
        //注意这里,服务器用于校验的字段,只能是以下的字段内容,可以缺失,但不能额外添加,因为服务端未做处理
        val attachHeaders = mutableListOf<Pair<String, String>>(
            "appid" to NET_CONFIG_APPID,
            "platform" to "android",//如果重复请求,可能会报重复签名错误,yapi 平台标记则不会
            "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()

        )
        //token仅在有值的时候才传递,
        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的请求,参数
        if (originRequest.method == "GET") {
            originRequest.url.queryParameterNames.forEach { key ->
                signHeaders.add(key to (originRequest.url.queryParameter(key) ?: ""))
            }
        }
        //post的请求 formBody形式,或json形式的,都需要将内部的字段,遍历出来,参与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 需要将requestBody反序列化为json转为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 目前json单层级
                        signHeaders.add(entry.key to entry.value.toString())
                    }
                }
            }
        }

        //todo 算法:都必须是非空参数!!!  sign = MD5(ascii排序后的 headers及params的key=value拼接&后,最后拼接appkey和value)//32位的大写,
        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())
    }

}

.
.

  • 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.*

/**
 * 用于记录okHttp的网络日志的拦截器
 */
class KtHttpLogInterceptor(block: (KtHttpLogInterceptor.() -> Unit)? = null) : Interceptor {

    private var logLevel: LogLevel = LogLevel.NONE//打印日期的标记
    private var colorLevel: ColorLevel = ColorLevel.DEBUG//默认是debug级别的logcat
    private var logTag = TAG//日志的Logcat的Tag

    init {
        block?.invoke(this)
    }

    /**
     * 设置LogLevel
     */
    fun logLevel(level: LogLevel): KtHttpLogInterceptor {
        logLevel = level
        return this
    }

    /**
     * 设置colorLevel
     */
    fun colorLevel(level: ColorLevel): KtHttpLogInterceptor {
        colorLevel = level
        return this
    }

    /**
     * 设置Log的Tag
     */
    fun logTag(tag: String): KtHttpLogInterceptor {
        logTag = tag
        return this
    }


    override fun intercept(chain: Interceptor.Chain): Response {
        //请求
        val request = chain.request()
        //响应
        return kotlin.runCatching { chain.proceed(request) }
            .onFailure {
                it.printStackTrace()
                logIt(
                    it.message.toString(),
                    ColorLevel.ERROR
                )
            }.onSuccess { response ->
                if (logLevel == LogLevel.NONE) {
                    return response
                }
                //记录请求日志
                logRequest(request, chain.connection())
                //记录响应日志
                logResponse(response)
            }.getOrThrow()
    }

    /**
     * 记录请求日志
     */
    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)
        //读取出request Body的内容
        val req = request.newBuilder().build()
        val sink = 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 ->
            "请求 Header: {${header.first}=${header.second}}\n"
        }
        sb.appendln(headersStr)
    }

    private fun logBasicReq(
        sb: StringBuilder,
        request: Request,
        connection: Connection?
    ) {
        sb.appendln("请求 method: ${request.method} url: ${decodeUrlStr(request.url.toString())} tag: ${request.tag()} protocol: ${connection?.protocol() ?: Protocol.HTTP_1_1}")
    }

    //endregion

    /**
     * 记录响应日志
     * [response] 响应数据
     */
    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会抛IO异常
                kotlin.runCatching {
                    //peek类似于clone数据流,监视,窥探,不能直接用原来的body的string流数据作为日志,会消费掉io,所以这里是peek,监测
                    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 ->
            "响应 Header: {${header.first}=${header.second}}\n"
        }
        sb.appendln(headersStr)
    }

    private fun logBasicRsp(sb: StringBuffer, response: Response) {
        sb.appendln("响应 protocol: ${response.protocol} code: ${response.code} message: ${response.message}")
            .appendln("响应 request Url: ${decodeUrlStr(response.request.url.toString())}")
            .appendln(
                "响应 sentRequestTime: ${
                    toDateTimeStr(
                        response.sentRequestAtMillis,
                        MILLIS_PATTERN
                    )
                } receivedResponseTime: ${
                    toDateTimeStr(
                        response.receivedResponseAtMillis,
                        MILLIS_PATTERN
                    )
                }"
            )
    }

    //endregion

    /**
     * 对于url编码的string 解码
     */
    private fun decodeUrlStr(url: String): String? {
        return kotlin.runCatching {
            URLDecoder.decode(url, "utf-8")
        }.onFailure { it.printStackTrace() }.getOrNull()
    }

    /**
     * 打印日志
     * [any]需要打印的数据对象
     * [tempLevel],便于临时调整打印color等级
     */
    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>"//默认的TAG

        //时间格式化
        const val MILLIS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSSXXX"

        //转化为格式化的时间字符串
        fun toDateTimeStr(millis: Long, pattern: String): String {
            return SimpleDateFormat(pattern, Locale.getDefault()).format(millis)
        }
    }


    /**
     * 打印日志的范围
     */
    enum class LogLevel {
        NONE,//不打印
        BASIC,//纸打印行首,请求/响应
        HEADERS,//打印请求和响应的 所有 header
        BODY,//打印所有
    }

    /**
     * Log颜色等级,应用于Android Logcat分为 v、d、i、w、e
     */
    enum class ColorLevel {
        VERBOSE,
        DEBUG,
        INFO,
        WARN,
        ERROR
    }

}

.
.

  • LocalCookieJar
package com.am.kttt.config

import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl

/**
 * 用于持久化的cookieJar实现类
 */
internal class LocalCookieJar : CookieJar {

    //cookie的本地化存储
    private val cache = mutableListOf<Cookie>()

    override fun loadForRequest(url: HttpUrl): List<Cookie> {
        //过期的Cookie
        val invalidCookies: MutableList<Cookie> = ArrayList()
        //有效的Cookie
        val validCookies: MutableList<Cookie> = ArrayList()

        for (cookie in cache) {
            if (cookie.expiresAt < System.currentTimeMillis()) {
                //判断是否过期
                invalidCookies.add(cookie)
            } else if (cookie.matches(url)) {
                //匹配Cookie对应url
                validCookies.add(cookie)
            }
        }

        //缓存中移除过期的Cookie
        cache.removeAll(invalidCookies)

        //返回List<Cookie>让Request进行设置
        return validCookies
    }

    /**
     * 将cookie保存
     */
    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
        cache.addAll(cookies)
    }
}

.
.

  • NetPjConfig.kt
package com.am.kttt.config

/**
 * 项目App相关配置
 */


//region 网络交互配置参数

const val NET_CONFIG_APPID = "xxxxxx"//appid标记项目apk
const val NET_CONFIG_APPKEY = "xxxxxxH\$pHx\$!"//appkey用于解析server返回的加密的数据

//endregion


//region 本地缓存的数据字段key

const val SP_KEY_USER_TOKEN = "sp_key_user_token"//用户token的标记


//endregion

.
.

  • RetryInterceptor
package com.am.kttt.config

import android.util.Log
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response

/**
 * 重试请求的网络拦截器
 */
class RetryInterceptor(private val maxRetry: Int = 0) : Interceptor {

    private var retriedNum: Int = 0//已经重试的次数,注意,设置maxRetry重试次数,作用于重试,所以总的请求次数,可能就是原始的1,+ maxRetry

    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        Log.d("RetryInterceptor", "intercept 29行: 当前retriedNum=$retriedNum")
        var response = chain.proceed(request)
        while (!response.isSuccessful && retriedNum < maxRetry) {
            retriedNum++
            Log.d("RetryInterceptor", "intercept 33行: 当前retriedNum=$retriedNum")
            response = chain.proceed(request)
        }
        return response
    }
}

.
.

  • NetResponse
package com.am.kttt.model

/**
 * 基础的网络返回数据结构
 */
data class NetResponse(
    val code: Int,//响应码
    val data: Any?,//响应数据内容
    val message: String//响应数据的结果描述
)

.
.

  • IHttpCallback

package com.am.kttt.support

/**
 * 网络请求的接口回调
 */
interface IHttpCallback {

    /**
     * 网络请求成功的回调
     * [data] 返回回调的数据结果
     * @param data 返回回调的数据结果
     */
    fun onSuccess(data: Any?)


    /**
     * 接口回调失败
     * [error] 错误信息的数据类
     */
    fun onFailed(error: Any?)

}

.
.

  • 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;


/**
 * 网络转换工具类
 * 中文转 unicode  、 unicode 转中文
 * 加密解密
 */
public class NetTranUtils {

    private NetTranUtils() {
    }

    /**
     * 中文转 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 转中文
     *
     * @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;
    }

    /**
     * 解析返回的data数据
     *
     * @param dataStr 未解密的响应数据
     * @return 解密后的数据String
     */
    @Nullable
    public static String decodeData(@Nullable String dataStr) {
        //java代码,无自动null判断,需要自行处理
        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;
        }

    }
}

.
.
.
.


代码都在上面了

  • 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: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:2.4.1'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
    implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'


    // 本项目所需
    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"



}

.
.

  • 项目下的build.gradle
// 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
}

.
.

运行效果

发起post

image.png

不要在意显示的内容,接口随便找的,然后这个返回数据很像get。

发起get

image.png

随便找的一个接口。 .
.
.

pan.baidu.com/s/1NiqjMmAn…