Android用kotlin搭建MVVM框架(二)

继上篇文章我们继续来搭建我们的MVVM框架

我们上篇文章封装了我们的Base本章我们来封装一下我们的网络框架,网络请求是每一个项目必不可缺的东西,好的网络请求会让我们在项目开发中事半功倍,在封装之前我们先来看一下效果
在这里插入图片描述
这个就是网络请求成功后返回的数据,我们用的请求接口是来自鸿洋大佬的玩Android 的接口
https://www.wanandroid.com/blog/show/2
现在我们正式开始封装我们的网络请求

kotlin+协程+retrofit+rxjava2

创建一个module来放置我们的网络请求命名为NetworkModule

在这里插入图片描述
首先我们要创建一个RetrofitClient然后初始化我们的Retrofit的一些操作

package com.kt.network.net

import android.annotation.SuppressLint
import android.content.Context
import com.kt.NetworkModel.net.interceptor.Level
import com.kt.NetworkModel.net.interceptor.LoggingInterceptor
import com.kt.ktmvvm.lib.BuildConfig
import com.kt.ktmvvm.net.event.OkHttpEventListener
import com.kt.network.net.dns.OkHttpDNS
import com.kt.network.net.interceptor.HTTPDNSInterceptor
import com.kt.network.net.interceptor.NoNetworkInterceptor
import okhttp3.Cache
import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
import okhttp3.internal.platform.Platform
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

class RetrofitClient
/**
 * retrofit 初始化build
 */(var context: Context?) {


    companion object {

        @SuppressLint("StaticFieldLeak")
        private var retrofitClient: RetrofitClient? = null
        private const val DEFAULT_TIME_OUT = 15
        private val sRetrofitManager: MutableMap<Int, Retrofit> = HashMap()
        fun getInstance(context: Context?): RetrofitClient {
            if (retrofitClient == null) {
                synchronized(RetrofitClient::class.java) {
                    retrofitClient = RetrofitClient(context)
                    return retrofitClient as RetrofitClient
                }
            }
            return retrofitClient as RetrofitClient
        }
    }


    /**
     * 创建连接客户端
     */
    private fun createOkHttpClient(optimization: Boolean): OkHttpClient {
        //根据需求添加不同的拦截器
        if (optimization) {
            //DNS 优化以及 开启缓存、无网拦截
            return OkHttpClient.Builder()
                .connectTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
                .connectionPool(ConnectionPool(8, 10, TimeUnit.SECONDS)) //添加这两行代码
                .sslSocketFactory(TrustAllCerts.createSSLSocketFactory()!!, TrustAllCerts())
                .hostnameVerifier(TrustAllCerts.TrustAllHostnameVerifier())
//            .protocols(Collections.unmodifiableList(listOf(Protocol.HTTP_1_1)))
                //alibaba dns优化
                .dns(OkHttpDNS.get(context))
                .addInterceptor(HTTPDNSInterceptor(context)) //不建议用这种方式,因为大型APP 域名会比较多,假设HTTPS 的话,证书会认证失败
                .cache(context?.cacheDir?.let { Cache(it, 50 * 1024 * 1024L) })//缓存目录
                .addInterceptor(NoNetworkInterceptor(context))//无网拦截器
//                .addInterceptor(httpLoggingInterceptor)
                .addNetworkInterceptor(LoggingInterceptor().apply {
                    isDebug = BuildConfig.DEBUG
                    level = Level.BASIC
                    type = Platform.INFO
                    requestTag = "Request"
                    requestTag = "Response"
                })
//                .eventListenerFactory(OkHttpEventListener.FACTORY)
                .build()
        } else {
            //无优化版本
            return OkHttpClient.Builder()
                .connectTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS)
                .connectionPool(ConnectionPool(8, 10, TimeUnit.SECONDS)) //添加这两行代码
                .sslSocketFactory(TrustAllCerts.createSSLSocketFactory()!!, TrustAllCerts())
                .hostnameVerifier(TrustAllCerts.TrustAllHostnameVerifier())
//                .addInterceptor(httpLoggingInterceptor)
                .addNetworkInterceptor(LoggingInterceptor().apply {
                    isDebug = BuildConfig.DEBUG
                    level = Level.BASIC
                    type = Platform.INFO
                    requestTag = "Request"
                    requestTag = "Response"
                })
//                .eventListenerFactory(OkHttpEventListener.FACTORY)
                .build()
        }

    }


    /**
     * 根据host 类型判断是否需要重新创建Client,因为一个app 有不同的BaseUrl,切换BaseUrl 就需要重新创建Client
     * 所以,就根据类型来从map中取出对应的client
     */
    fun <T> getDefault(interfaceServer: Class<T>?, hostType: Int): T {
        val retrofitManager = sRetrofitManager[hostType]
        return if (retrofitManager == null) {
            create(interfaceServer, hostType)
        } else retrofitManager.create(interfaceServer!!)
    }


    /**
     *
     */
    private fun <T> create(interfaceServer: Class<T>?, hostType: Int): T {
        val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(BaseUrlConstants.getHost(hostType))
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(createOkHttpClient(true))
            .build()
        sRetrofitManager[hostType] = retrofit
        if (interfaceServer == null) {
            throw RuntimeException("The Api InterfaceServer is null!")
        }
        return retrofit.create(interfaceServer)
    }

}

然后我们的文件报错,但是先别着急,我们需在继续创建一个URL的一个管理类,方便我们在同一个项目中调用多个不用的域名或者是ip地址的url,所有,我们需要再创建一个BaseUrlConstants

package com.kt.network.net

class BaseUrlConstants {


    companion object {
        private const val wanandroid: String = "https://www.wanandroid.com"
        fun getHost(host: Int): String {
            when (host) {
                1 -> return wanandroid
            }
            return wanandroid;
        }
    }
}

然后我们继续进行封装,接下来我们需要封装一个DNS 优化

package com.kt.network.net.dns


import android.content.Context
import android.util.Log
import com.alibaba.sdk.android.httpdns.HttpDns
import com.alibaba.sdk.android.httpdns.HttpDnsService
import okhttp3.Dns
import java.net.InetAddress


/**
 * DNS 优化
 */
class OkHttpDNS(context: Context?) : Dns {
    private val SYSTEM = Dns.SYSTEM
    private var httpDns: HttpDnsService? = null

    init {
        httpDns = HttpDns.getService(context)
    }


    companion object {
        private var instance: OkHttpDNS? = null
        fun get(context: Context?): OkHttpDNS {
            if (instance == null) {
                synchronized(OkHttpDNS::class.java) {
                    if (instance == null) {
                        instance = OkHttpDNS(context)
                    }
                }
            }
            return instance!!
        }
    }

    override fun lookup(hostname: String): MutableList<InetAddress> {
        //通过异步解析接⼝获取ip
        val ip = httpDns?.getIpByHostAsync(hostname)
        ip?.let {

            val inetAddresses = listOf(InetAddress.getAllByName(ip)) as MutableList<InetAddress>
            Log.e("OkHttpDns", "inetAddresses:$inetAddresses")
            return inetAddresses
        } ?: let {
            return Dns.SYSTEM.lookup(hostname)
        }
    }


}

网络请求肯定是少不了拦截器的,这里我们自己封装一个日志拦截器和请求头拦截器

package com.kt.network.net.interceptor

import android.content.Context
import com.alibaba.sdk.android.httpdns.HttpDns
import okhttp3.Interceptor
import okhttp3.Response

/**
 * 拦截请求头
 */
class HTTPDNSInterceptor(private var context: Context?) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originRequest = chain.request()
        val httpUrl = originRequest.url()
        val url = httpUrl.toString()
        val host = httpUrl.host()
        val service = HttpDns.getService(context)
        val hostIP = service.getIpByHostAsync(host)
        val builder = originRequest.newBuilder()
        if (hostIP != null) {
            builder.url(url.replaceFirst(url, hostIP))
            builder.header("host", hostIP)
        }
        val newRequest = builder.build()
        return chain.proceed(newRequest)
    }
}

封装日志拦截器需要以下几步
首先我们需要创建一个LoggingInterceptor

package com.kt.NetworkModel.net.interceptor

import com.blankj.utilcode.util.JsonUtils
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.internal.platform.Platform
import okhttp3.internal.platform.Platform.INFO
import java.io.IOException
import java.util.concurrent.TimeUnit

/**
 * @author 浩楠
 *
 * @date 2023/5/12-13:35.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * @Description: TODO 创建一个LoggingInterceptor观察、修改并可能短路发出的请求和返回的相应响应。通常,拦截器会在请求或响应中添加、删除或转换报头。
 */
class LoggingInterceptor : Interceptor {

    private var tag: String = "HttpLogging"
    var isDebug: Boolean = false
    var type = INFO
    var requestTag: String = tag
    var responseTag: String = tag
    var level = Level.BASIC
    private val headers = Headers.Builder()
    var logger: Logger? = null


    interface Logger {
        fun log(level: Int, tag: String, msg: String)

        companion object {
            val DEFAULT: Logger = object : Logger {
                override fun log(level: Int, tag: String, msg: String) {
                    Platform.get().log(level, msg, null)
                }
            }
        }
    }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        if (getHeaders().size() > 0) {
            val headers = request.headers()
            val names = headers.names()
            val iterator = names.iterator()
            val requestBuilder = request.newBuilder()
            requestBuilder.headers(getHeaders())
            while (iterator.hasNext()) {
                val name = iterator.next()
                requestBuilder.addHeader(name, headers.get(name)!!)
            }
            request = requestBuilder.build()
        }

        if (!isDebug || level == Level.NONE) {
            return chain.proceed(request)
        }
        val requestBody = request.body()

        var rContentType: MediaType? = null
        if (requestBody != null) {
            rContentType = request.body()!!.contentType()
        }

        var rSubtype: String? = null
        if (rContentType != null) {
            rSubtype = rContentType.subtype()
        }

        if (rSubtype != null && (rSubtype.contains("json")
                    || rSubtype.contains("xml")
                    || rSubtype.contains("plain")
                    || rSubtype.contains("html"))
        ) {
            Printer.printJsonRequest(this, request)
        } else {
            Printer.printFileRequest(this, request)
        }

        val st = System.nanoTime()
        val response = chain.proceed(request)

        val segmentList = request.url().encodedPathSegments()
        val chainMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - st)
        val header = response.headers().toString()
        val code = response.code()
        val isSuccessful = response.isSuccessful
        val responseBody = response.body()
        val contentType = responseBody!!.contentType()

        var subtype: String? = null
        val body: ResponseBody

        if (contentType != null) {
            subtype = contentType.subtype()
        }

        if (subtype != null && (subtype.contains("json")
                    || subtype.contains("xml")
                    || subtype.contains("plain")
                    || subtype.contains("html"))
        ) {
            val bodyString = responseBody.string()
            val bodyJson = JsonUtils.formatJson(bodyString)
            Printer.printJsonResponse(
                this,
                chainMs,
                isSuccessful,
                code,
                header,
                bodyJson,
                segmentList
            )
            body = ResponseBody.create(contentType, bodyString)
        } else {
            Printer.printFileResponse(this, chainMs, isSuccessful, code, header, segmentList)
            return response
        }
        return response.newBuilder().body(body).build()
    }

    private fun getHeaders(): Headers = headers.build()
}

然后需要创建一个Level

package com.kt.NetworkModel.net.interceptor

/**
 * @author 浩楠
 *
 * @date 2023/5/12-13:36.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * @Description: TODO 创建一个枚举类,用来表示几种不同形式的日志类型
 */
enum class Level {
    /**
     * No logs.
     */
    NONE,

    /**
     *
     * Example:
     * <pre>`- URL
     * - Method
     * - Headers
     * - Body
    `</pre> *
     */
    BASIC,

    /**
     *
     * Example:
     * <pre>`- URL
     * - Method
     * - Headers
    `</pre> *
     */
    HEADERS,

    /**
     *
     * Example:
     * <pre>`- URL
     * - Method
     * - Body
    `</pre> *
     */
    BODY
}

最后一步就是Printer

package com.kt.NetworkModel.net.interceptor

import com.blankj.utilcode.util.JsonUtils
import okhttp3.FormBody
import okhttp3.Request
import okio.Buffer
import java.io.IOException

/**
 * @author 浩楠
 *
 * @date 2023/5/12-13:37.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * @Description: TODO 对日志拦截器的格式进行处理
 */
object Printer {
    private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
    private val DOUBLE_SEPARATOR = LINE_SEPARATOR + LINE_SEPARATOR

    private val OMITTED_RESPONSE = arrayOf(LINE_SEPARATOR, "Omitted response body")
    private val OMITTED_REQUEST = arrayOf(LINE_SEPARATOR, "Omitted request body")

    private const val N = "\n"
    private const val T = "\t"
    private const val REQUEST_UP_LINE =
        "┌────── Request ────────────────────────────────────────────────────────────────────────"
    private const val END_LINE =
        "└───────────────────────────────────────────────────────────────────────────────────────"
    private const val RESPONSE_UP_LINE =
        "┌────── Response ───────────────────────────────────────────────────────────────────────"
    private const val BODY_TAG = "Body:"
    private const val URL_TAG = "URL: "
    private const val METHOD_TAG = "Method: @"
    private const val HEADERS_TAG = "Headers:"
    private const val STATUS_CODE_TAG = "Status Code: "
    private const val RECEIVED_TAG = "Received in: "
    private const val CORNER_UP = "┌ "
    private const val CORNER_BOTTOM = "└ "
    private const val CENTER_LINE = "├ "
    private const val DEFAULT_LINE = "│ "

    private fun isEmpty(line: String) =
        line.isEmpty() || N == line || T == line || line.trim().isEmpty()

    internal fun printJsonRequest(builder: LoggingInterceptor, request: Request) {
        val requestBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyToString(request)
        val tag = builder.requestTag
        if (builder.logger == null)
            log(builder.type, tag, REQUEST_UP_LINE)
        logLines(builder.type, tag, arrayOf(URL_TAG + request.url()), builder.logger, false)
        logLines(builder.type, tag, getRequest(request, builder.level), builder.logger, true)
        if (request.body() is FormBody) {
            val formBody = StringBuilder()
            val body = request.body() as FormBody?
            if (body != null && body.size() != 0) {
                for (i in 0 until body.size()) {
                    formBody.append(body.encodedName(i) + "=" + body.encodedValue(i) + "&")
                }
                formBody.delete(formBody.length - 1, formBody.length)
                logLines(builder.type, tag, arrayOf(formBody.toString()), builder.logger, true)
            }
        }
        if (builder.level == Level.BASIC || builder.level == Level.BODY) {
            logLines(
                builder.type,
                tag,
                requestBody.split(LINE_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray(),
                builder.logger,
                true
            )
        }
        if (builder.logger == null)
            log(builder.type, tag, END_LINE)
    }

    internal fun printJsonResponse(
        builder: LoggingInterceptor, chainMs: Long, isSuccessful: Boolean,
        code: Int, headers: String, bodyString: String, segments: List<String>
    ) {
        val responseBody =
            LINE_SEPARATOR!! + BODY_TAG + LINE_SEPARATOR + JsonUtils.formatJson(bodyString)
        val tag = builder.responseTag
        if (builder.logger == null)
            log(builder.type, tag, RESPONSE_UP_LINE)

        logLines(
            builder.type, tag, getResponse(
                headers, chainMs, code, isSuccessful,
                builder.level, segments
            ), builder.logger, true
        )
        if (builder.level == Level.BASIC || builder.level == Level.BODY) {
            logLines(
                builder.type,
                tag,
                responseBody.split(LINE_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray(),
                builder.logger,
                true
            )
        }
        if (builder.logger == null)
            log(builder.type, tag, END_LINE)
    }

    internal fun printFileRequest(builder: LoggingInterceptor, request: Request) {
        val tag = builder.responseTag
        if (builder.logger == null)
            log(builder.type, tag, REQUEST_UP_LINE)
        logLines(builder.type, tag, arrayOf(URL_TAG + request.url()), builder.logger, false)
        logLines(builder.type, tag, getRequest(request, builder.level), builder.logger, true)
        if (request.body() is FormBody) {
            val formBody = StringBuilder()
            val body = request.body() as FormBody?
            if (body != null && body.size() != 0) {
                for (i in 0 until body.size()) {
                    formBody.append(body.encodedName(i) + "=" + body.encodedValue(i) + "&")
                }
                formBody.delete(formBody.length - 1, formBody.length)
                logLines(builder.type, tag, arrayOf(formBody.toString()), builder.logger, true)
            }
        }
        if (builder.level == Level.BASIC || builder.level == Level.BODY) {
            logLines(builder.type, tag, OMITTED_REQUEST, builder.logger, true)
        }
        if (builder.logger == null)
            log(builder.type, tag, END_LINE)
    }

    internal fun printFileResponse(
        builder: LoggingInterceptor, chainMs: Long, isSuccessful: Boolean,
        code: Int, headers: String, segments: List<String>
    ) {
        val tag = builder.responseTag
        if (builder.logger == null)
            log(builder.type, tag, RESPONSE_UP_LINE)

        logLines(
            builder.type, tag, getResponse(
                headers, chainMs, code, isSuccessful,
                builder.level, segments
            ), builder.logger, true
        )
        logLines(builder.type, tag, OMITTED_RESPONSE, builder.logger, true)
        if (builder.logger == null)
            log(builder.type, tag, END_LINE)
    }

    private fun getRequest(request: Request, level: Level): Array<String> {
        val message: String
        val header = request.headers().toString()
        val loggableHeader = level == Level.HEADERS || level == Level.BASIC
        message = METHOD_TAG + request.method() + DOUBLE_SEPARATOR +
                when {
                    loggableHeader -> "${HEADERS_TAG}${LINE_SEPARATOR}${dotHeaders(header)}"
                    else -> ""
                }
        return message.split(LINE_SEPARATOR!!.toRegex()).dropLastWhile { it.isEmpty() }
            .toTypedArray()
    }

    private fun getResponse(
        header: String, tookMs: Long, code: Int, isSuccessful: Boolean,
        level: Level, segments: List<String>
    ): Array<String> {
        val message: String
        val loggableHeader = level == Level.HEADERS || level == Level.BASIC
        val segmentString = slashSegments(segments)
        message =
            "${if (segmentString.isNotEmpty()) "$segmentString - " else ""}is success : $isSuccessful - $RECEIVED_TAG$tookMs ms $DOUBLE_SEPARATOR $STATUS_CODE_TAG " +
                    "$code $DOUBLE_SEPARATOR ${if (loggableHeader) HEADERS_TAG + LINE_SEPARATOR + dotHeaders(
                        header
                    ) else ""}"
        return message.split(LINE_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() }
            .toTypedArray()
    }

    private fun slashSegments(segments: List<String>): String {
        val segmentString = StringBuilder()
        for (segment in segments) {
            segmentString.append("/").append(segment)
        }
        return segmentString.toString()
    }

    private fun dotHeaders(header: String): String {
        if (isEmpty(header)) return ""
        val headers =
            header.split(LINE_SEPARATOR!!.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val builder = StringBuilder()
        var tag = "─ "
        if (headers.size > 1) {
            for (i in headers.indices) {
                tag = when (i) {
                    0 -> CORNER_UP
                    headers.size - 1 -> CORNER_BOTTOM
                    else -> CENTER_LINE
                }
                builder.append(tag).append(headers[i]).append("\n")
            }
        } else {
            for (item in headers) {
                builder.append(tag).append(item).append("\n")
            }
        }
        return builder.toString()
    }

    private fun logLines(
        type: Int,
        tag: String,
        lines: Array<String>,
        logger: LoggingInterceptor.Logger?,
        withLineSize: Boolean
    ) {
        for (line in lines) {
            val lineLength = line.length
            val maxSize = if (withLineSize) 110 else lineLength
            for (i in 0..lineLength / maxSize) {
                val start = i * maxSize
                var end = (i + 1) * maxSize
                end = if (end > line.length) line.length else end
                if (logger == null) {
                    log(type, tag, DEFAULT_LINE + line.substring(start, end))
                } else {
                    logger.log(type, tag, line.substring(start, end))
                }
            }
        }
    }

    private fun bodyToString(request: Request): String {
        try {
            val copy = request.newBuilder().build()
            val buffer = Buffer()
            if (copy.body() == null)
                return ""
            copy.body()!!.writeTo(buffer)
            return JsonUtils.formatJson(buffer.readUtf8())
        } catch (e: IOException) {
            return "{\"err\": \"${e.message}\"}"
        }

    }

    private fun log(type: Int, tag: String, msg: String) {
        LoggingInterceptor.Logger.DEFAULT.log(type, tag, msg)
    }
}

这样我们一个自定义的日志拦截器就制作完成了
在这里插入图片描述
接下来我们就该对网络的状态进行一个判断

package com.kt.network.net.interceptor

import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import okhttp3.CacheControl
import okhttp3.Interceptor
import okhttp3.Response

/**
 * 网络状态判断
 */
class NoNetworkInterceptor(private var context: Context?) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val newBuilder = request.newBuilder()

        if (getNetworkStatus(context) == -1) {
            //无网时,只从缓存中取
            newBuilder.cacheControl(CacheControl.FORCE_CACHE)
        } else {
            //有网时,只从服务器取
            newBuilder.cacheControl(CacheControl.FORCE_NETWORK)
        }
        return chain.proceed(newBuilder.build())
    }

    companion object {
        @SuppressLint("MissingPermission")
        fun getNetworkStatus(context: Context?): Int {
            try {
                if (context == null) {
                    return -1
                }
                val conMan = context.getSystemService(
                    Context.CONNECTIVITY_SERVICE
                ) as ConnectivityManager
                val info = conMan.activeNetworkInfo
                if (null != info && info.isConnected) {
                    if (info.type == ConnectivityManager.TYPE_MOBILE) {
                        return when (info.subtype) {
                            1, 2, 4 ->                             // 2G网络
                                2
                            else ->                             // 3G及其以上网络
                                3
                        }
                    } else if (info.type == ConnectivityManager.TYPE_WIFI) {
                        // wifi网络
                        return 1
                    }
                }
            } catch (e: Exception) {
                //报错的就当有
                return 1
            }
            // 无网络
            return -1
        }
    }
}

还需要做一个对服务器x509Certificates的一个判断

package com.kt.network.net

import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.*

/**
 * 用来检查服务器中的x509Certificates
 */
class TrustAllCerts : X509TrustManager {
    override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {

    }

    override fun checkServerTrusted(chain: Array<out X509Certificate>?, p1: String?) {
        requireNotNull(chain) { "  Check Server x509Certificates is null" }
    }

    override fun getAcceptedIssuers(): Array<X509Certificate?> {
        return arrayOfNulls(0)
    }


    companion object {
        fun createSSLSocketFactory(): SSLSocketFactory? {
            var ssfFactory: SSLSocketFactory? = null
            try {
                val sc = SSLContext.getInstance("TLS")
                sc.init(
                    null, arrayOf(TrustAllCerts()), SecureRandom()
                )
                ssfFactory = sc.socketFactory
            } catch (e: Exception) {
            }
            return ssfFactory
        }
    }


    class TrustAllHostnameVerifier : HostnameVerifier {
        override fun verify(hostname: String, session: SSLSession): Boolean {
            return true
        }
    }
}

截至到这里我们的网络请求成功时候的都已经封装好了,但是为什么说是成功时候呢,因为网络请求有成功,就有失败,各种各样的error,所有还得继续封装异常,因为我们的网络请求是在ViewModel中调用,所以我们的异常也是在BaseViewModel中调用的。首先创建一个ExceptionHandle

package com.kt.network.net

import android.app.Application
import android.content.Context
import android.net.ParseException
import android.widget.TextView
import com.blankj.utilcode.util.ToastUtils
import com.google.gson.JsonParseException
import com.google.gson.stream.MalformedJsonException
import com.hjq.xtoast.XToast
import com.kt.NetworkModel.App
import com.kt.ktmvvm.lib.R
import org.json.JSONException
import retrofit2.HttpException
import java.net.ConnectException

/**
 * @author 浩楠
 *
 * @date 2023/3/10-10:56.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * @Description: TODO 根据不同的错误给出相应的提示
 */
object ExceptionHandle {
    fun handleException(e: Throwable): ResponseThrowable {
        var ex: ResponseThrowable
        if (e is ResponseThrowable) {
            ex = e
        } else if (e is HttpException) {
            ex = ResponseThrowable(ERROR.HTTP_ERROR, e)
            when (e.code()) {
                404 -> {
//                    ToastUtils.showShort(R.string.net_erro)
                    XToast<XToast<*>>(App.get()).apply {
                        setContentView(R.layout.layout_toast)
                        setDuration(3000)
                        findViewById<TextView>(R.id.txtToastMessage).text="网络地址错误。请稍后再试"
                    }.show()
                    ex = ResponseThrowable(ERROR.NOT_FOUND, e)
                }
                400 -> {
                    ex = ResponseThrowable(ERROR.TOKEN_EMPTY, e)
                }
            }
        } else if (e is JsonParseException
            || e is JSONException
            || e is ParseException || e is MalformedJsonException
        ) {
            ex = ResponseThrowable(ERROR.PARSE_ERROR, e)
        } else if (e is ConnectException) {
//            ToastUtils.showShort(R.string.net_erro)
            XToast<XToast<*>>(App.get()).apply {
                setContentView(R.layout.layout_toast)
                setDuration(3000)
                findViewById<TextView>(R.id.txtToastMessage).text="网络连接失败。请稍后再试"
            }.show()

            ex = ResponseThrowable(ERROR.NETWORD_ERROR, e)
        } else if (e is javax.net.ssl.SSLException) {
            ex = ResponseThrowable(ERROR.SSL_ERROR, e)
        } else if (e is java.net.SocketTimeoutException) {
//            ToastUtils.showShort(R.string.net_erro)
            XToast<XToast<*>>(App.get()).apply {
                setContentView(R.layout.layout_toast)
                setDuration(3000)
                findViewById<TextView>(R.id.txtToastMessage).text="网络连接失败。请稍后再试"
            }.show()
            ex = ResponseThrowable(ERROR.TIMEOUT_ERROR, e)

        } else if (e is java.net.UnknownHostException) {
//            ToastUtils.showShort(R.string.net_erro)
            XToast<XToast<*>>(App.get()).apply {
                setContentView(R.layout.layout_toast)
                setDuration(3000)
                findViewById<TextView>(R.id.txtToastMessage).text="网络连接失败。请稍后再试"
            }.show()
            ex = ResponseThrowable(ERROR.TIMEOUT_ERROR, e)
        } else {
            ex = if (!e.message.isNullOrEmpty()) ResponseThrowable(1000, e.message!!, e)
            else ResponseThrowable(ERROR.UNKNOWN, e)
        }
        return ex
    }
}

然后再创建一个错误的枚举类

package com.kt.network.net

/**
 * @author 浩楠
 *
 * @date 2023/3/8   15:12.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * @Description: TODO 错误的枚举类
 */
enum class ERROR(private val code: Int, private val err: String) {

    /**
     * 未知错误
     */
    UNKNOWN(1000, "未知错误"),
    /**
     * 解析错误
     */
    PARSE_ERROR(1001, "解析错误"),
    /**
     * 网络错误
     */
    NETWORD_ERROR(1002, "网络错误"),
    /**
     * 协议出错
     */
    HTTP_ERROR(1003, "协议出错"),
    TOKEN_EMPTY(-2, "No message available"),

    /**
     * 证书出错
     */
    SSL_ERROR(1004, "证书出错"),
    NOT_FOUND(404, "not found"),

    /**
     * 连接超时
     */
    TIMEOUT_ERROR(1006, "连接超时");

    fun getValue(): String {
        return err
    }

    fun getKey(): Int {
        return code
    }

}

ExceptionHandle中会有一个XToast是报错的,先别急,需要我们再自定义一个Application

package com.kt.NetworkModel

import android.app.Application
import android.app.Person
import android.content.Context
import com.blankj.utilcode.util.ToastUtils
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import me.jessyan.autosize.AutoSize
import me.jessyan.autosize.AutoSizeConfig
import me.jessyan.autosize.unit.Subunits

/**
 * @author 浩楠
 *
 * @date 2023/1/9   18:00.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * 描述:
 */
class App : Application() {


    override fun onCreate() {
        super.onCreate()
        instance = this
        //用于屏幕适配
        /*AutoSize.initCompatMultiProcess(this);
        AutoSize.checkAndInit(this);
        AutoSizeConfig.getInstance().setCustomFragment(true).setExcludeFontScale(true)
            .setPrivateFontScale(0.8f).setLog(false).setBaseOnWidth(true).setUseDeviceSize(true)
            .getUnitsManager().setSupportDP(true).setDesignSize(2160F, 3840F).setSupportSP(true)
            .setSupportSubunits(Subunits.MM);*/

    }

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        instance = this
    }


    companion object {
        private var instance: Application? = null
        fun get(): App {
            return instance as App
        }

    }
}

这里我们通过自定义Application后就可以是使用在ExceptionHandle中报错的XToast了
我们现在整体的异常已经封装完成了,将Exception进行封装就可以完成整体的异常的封装了

package com.kt.network.net

/**
 * @author 浩楠
 *
 * @date 2023/3/8   15:11.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * 描述: TODO 构建一个异常的构造类
 */
class ResponseThrowable : Exception {
    var code: Int
    var errMsg: String

    constructor(error: ERROR, e: Throwable? = null) : super(e) {
        code = error.getKey()
        errMsg = error.getValue()
    }

    constructor(code: Int, msg: String, e: Throwable? = null) : super(e) {
        this.code = code
        this.errMsg = msg
    }

    constructor(base: IBaseResponse<*>, e: Throwable? = null) : super(e) {
        this.code = base.errorCode()
        this.errMsg = base.errorMsg()
    }
}

现在我们的整个网络请求就可以完成了。我们就可以进行调用了。
但是在调用之前我们还需要进行一个最后的封装,因为我们的网络请求后的JSON数据的格式都后端来定义的,这里我们就针对玩Android中的JSON格式来进行一个数据格式封装
首先我们需要创建一个IBaseResponse

package com.kt.network.net
/**
 * @author 浩楠
 *
 * @date 2023/3/9   9:32.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * @Description: TODO 定义一个JSON数据格式的接口类
 */
abstract class IBaseResponse<T>  {

    //后端返回的code
    abstract fun errorCode():Int
    //后端返回的信息
    abstract fun errorMsg():String
    abstract fun data():T?
    //接口请求是否成功
    abstract fun isSuccess():Boolean
}

然后再创建一个实体类data

package com.kt.network.bean

import com.kt.network.net.IBaseResponse
import java.util.ArrayList

/**
 * @author 浩楠
 *
 * @date 2023/1/16   14:44.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * 描述:  TODO 实体类data
 */
open class BaseResult<T> : IBaseResponse<T>() {
   var success:Boolean = false
   var errorCode:Int=0
   var errorMsg:String =""
   var data:T? = null
   override fun errorCode(): Int {
      return errorCode
   }

   override fun errorMsg(): String {
      return errorMsg
   }


   override fun data():T? {
      return data
   }


   override fun isSuccess(): Boolean {
      return errorCode == 0
   }

}
data class FontDataNew(
   val curPage :Double,
   val datas :ArrayList<Datas>,
   val offset :Double,
   val over :Boolean,
   val pageCount:Double,
   val size :Double,
   val total :Double,
)
data class Datas(
   val adminAdd :Boolean,
   val apkLink :String,
   val audit:Double,
   val author:String,
   val canEdit:Boolean,
   val chapterId:Double,
   val chapterName:String,
   val collect :Boolean,
   val courseId :Double,
   val desc:String,
   val descMd :String,
   val envelopePic:String,
   val fresh :Boolean,
   val host :String,
   val id :Double,
   val isAdminAdd :Boolean,
   val link :String,
   val niceDate:String,
   val niceShareDate :String,
   val origin :String,
   val prefix  :String,
   val projectLink :String,
   val publishTime :Double,
   val realSuperChapterId :Double,
   val route :Boolean,
   val selfVisible :Double,
   val shareDate :Double,
   val shareUser :String,
   val superChapterId :Double,
   val superChapterName:String,
   val tags :Any,
   val title :String,
   val type :Double,
   val userId :Double,
   val visible:Double,
   val zan :Double,
)

这样我们就可以把项目中的所有实体类都写到这一个data下了
接下来就是我们如何调用了
我们用的接口是这个:https://www.wanandroid.com/article/list/1/json
我们要创建一个ApiService来写我们的retrofit的网络请求的中的请求类型和请求接口,已经我们的请求参数

package com.kt.network.net
import com.kt.network.bean.BaseResult
import com.kt.network.bean.FontDataNew
import retrofit2.http.GET
import retrofit2.http.Query

interface ApiService {

    /**
     * 获取导航数据
     */
    @GET(ApiAddress.PROJECT)
    suspend fun callback(): BaseResult<FontDataNew>
} 

我们还需要创建一个ApiAddress,当然你也可以不创建,直接把ApiAddress.PROJECT改成"article/list/1/json"也是一样的

package com.kt.network.net

class ApiAddress {

    companion object {
        /**
         * 导航
         */
        const val PROJECT="article/list/1/json"
    }
}

接下来就是在ViewModel中调用了
我们需要创建一个请求管理器来进行网络请求的一个管理

package com.ghn.cocknovel.net

import com.kt.NetworkModel.App
import com.kt.network.bean.BaseResult
import com.kt.network.bean.FontDataNew
import com.kt.network.net.ApiService
import com.kt.network.net.RetrofitClient


/**
 * @author 浩楠
 *
 * @date 2023/1/9   17:08.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * 描述: TODO 请求管理器
 */
class DataService {
    companion object{
        /**
         * 测试网络请求框架
         */
        suspend fun callback(host: Int): BaseResult<FontDataNew> {
            return RetrofitClient.getInstance(App.get()).getDefault(ApiService::class.java, host)
                .callback()
        }

    }
}

然后再创建一个RecommendViewModel来进行网络请求

package com.ghn.cocknovel.viewmodel

import android.app.Application
import android.util.Log
import androidx.lifecycle.MutableLiveData
import com.example.basemodel.base.BaseViewModel
import com.ghn.cocknovel.net.DataService
import com.kt.network.bean.FontDataNew

/**
 * @author 浩楠
 *
 * @date 2023/4/6-17:16.
 *
 *      _              _           _     _   ____  _             _ _
 *     / \   _ __   __| |_ __ ___ (_) __| | / ___|| |_ _   _  __| (_) ___
 *    / _ \ | '_ \ / _` | '__/ _ \| |/ _` | \___ \| __| | | |/ _` | |/ _ \
 *   / ___ \| | | | (_| | | | (_) | | (_| |  ___) | |_| |_| | (_| | | (_) |
 *  /_/   \_\_| |_|\__,_|_|  \___/|_|\__,_| |____/ \__|\__,_|\__,_|_|\___/
 * @Description: TODO 
 */
open class RecommendViewModel(application: Application) : BaseViewModel(application) {
    companion object {
        val TAG: String? = BookStoreViewModel::class.simpleName
    }
    val loginStatus = MutableLiveData<String>()
    open fun getwan(){
        launchOnlyresult({
            DataService.callback(1)
        },{
            loginStatus.value= it.toString()
        })
    }
}

最后在我们的activity或者是fragment中进行一个网络请求的调用就完成了

package com.ghn.cocknovel.ui.activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.basemodel.base.BaseActivity
import com.example.basemodel.base.BaseViewModel
import com.ghn.cocknovel.BR
import com.ghn.cocknovel.R
import com.ghn.cocknovel.databinding.ActivityHomeBinding
import com.ghn.cocknovel.viewmodel.RecommendViewModel

class HomeActivity : BaseActivity<ActivityHomeBinding, RecommendViewModel>(){
    override fun initVariableId(): Int {
        return BR.mode
    }

    override fun initContentView(savedInstanceState: Bundle?): Int {
        return R.layout.activity_home
    }

    override fun initParam() {
        viewModel?.getwan()
    }
}

然后就是双向绑定

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="mode"
            type="com.ghn.cocknovel.viewmodel.RecommendViewModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context=".ui.activity.HomeActivity">

        <TextView
            android:id="@+id/HomeRecyclerview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:text="@={mode.loginStatus}"
             />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

这样我们的整个MVVM的项目框架就搭建完成了
还是那句话
本章节到这里也就结束了,在封装的过程中,遇到问题的可以留言,我看到后会第一时间回复
如有大佬指点一二,小弟在此抱拳了
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值