白话文
- 我们肯定需要一个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
不要在意显示的内容,接口随便找的,然后这个返回数据很像get。
发起get
随便找的一个接口。
.
.
.