美文网首页androidAndroid
OkHttp3 超级简单实用封装

OkHttp3 超级简单实用封装

作者: 英勇的骑士_d175 | 来源:发表于2019-08-03 10:18 被阅读0次

本人分成了四个类,其实可以更加精简,两个类足以

  1. 首先引入依赖 okhttp github地址
    implementation("com.squareup.okhttp3:okhttp:4.9.0")

  2. 上代码,首先展示一下请求代码(Kotlin)

  DLHttp.post("url")  //请求地址和请求方式,get和post方式切换
            .add(map)   //参数可以直接传入map集合
            .add("key","value")  //参数也可以传入键值对
            .add(Object)  //参数也可以类对象
            .build(object : HttpCallBack<T>() { //传入返回的实体类
                override fun success(t: T?) {
                          //得到返回的实体类
                }
                override fun error(code:Int,err: String?) {
                          //得到错误信息提醒
                }
            })

以上就是请求示例代码,够简单了吧,当然这都是表面的,下面来看一下背后做的工作

  1. 上关键类 DLHttp :
package com.toune.dltools.http

import android.util.Log
import com.tamsiree.rxkit.RxFileTool
import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.URLConnection
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
import kotlin.collections.HashMap

/**
 * @Author Dong Lei
 * @Date 2020/12/16 0016-下午 12:35
 * @Info 描述:网络请求
 */
class DLHttp {
    companion object {
        private var dlHttp: DLHttp? = null

        /**
         * 初始化
         */
        fun instance() {
            dlHttp = DLHttp()
        }

        /**
         * 获取client
         */
        private fun getHttpClient() {
            if (dlHttp != null) {
                if (dlHttp!!.dlHttpClient == null) {
                    dlHttp!!.dlHttpClient = OkHttpClient.Builder()
                        .connectTimeout(30, TimeUnit.MINUTES)
                        .readTimeout(30, TimeUnit.MINUTES)
                        .callTimeout(30, TimeUnit.MINUTES)
                        .build()
                }
            }
        }

        /**
         * post请求
         * @param url String? 地址
         * @return DLHttp
         */
        fun post(url: String?): DLHttp {
            instance()
            getHttpClient()
            dlHttp!!.REQUEST_METHOD = dlHttp!!.REQUEST_POST
            dlHttp!!.requestUrl = url
            return dlHttp!!
        }

        /**
         * put请求
         * @param url String? 地址
         * @return DLHttp
         */
        fun put(url: String?): DLHttp {
            instance()
            getHttpClient()
            dlHttp!!.REQUEST_METHOD = dlHttp!!.REQUEST_PUT
            dlHttp!!.requestUrl = url
            return dlHttp!!
        }

        /**
         * get请求
         * @param url String?
         * @return DLHttp
         */
        fun get(url: String?): DLHttp {
            instance()
            getHttpClient()
            dlHttp!!.REQUEST_METHOD = dlHttp!!.REQUEST_GET
            dlHttp!!.requestUrl = url
            return dlHttp!!
        }


        /**
         * 下载文件
         * @param realURL String?
         * @param destFileDir String?
         * @return DLHttp
         */
        fun downFile(realURL: String?, destFileDir: String?): DLHttp {
            instance()
            getHttpClient()
            dlHttp!!.downUrl = realURL
            dlHttp!!.filrDir = destFileDir
            return dlHttp!!
        }
    }

    private val REQUEST_POST = 1001 //post请求
    private val REQUEST_GET = 1002  //get请求
    private val REQUEST_PUT = 1003  //put请求
    private var REQUEST_METHOD = REQUEST_GET //默认请求方式GET


    private val JSON_TYPE = 301  //JSON格式提交
    private val FILE_TYPE = 302  //提交文件
    private val FORM_TYPE = 303  //表单提交
    private var BUILD_TYPE = FORM_TYPE //默认表单提交


    private var dlHttpClient: OkHttpClient? = null
    private var requestUrl: String? = null

    private var downUrl: String? = null
    private var filrDir: String? = null
    private var downFileName = ""


    private val JSONType: MediaType =
        "application/json; charset=utf-8".toMediaTypeOrNull()!! //JSON的mediaType表头
    private var requestBody: RequestBody? = null
    private var request: Request? = null

    /**
     * 请求header
     */
    private var headers = Headers.Builder()

    /**
     * json实体类
     */
    private var jsonObj: Any? = null

    /**
     * 参数
     */
    private var params: MutableMap<String, Any?> = HashMap()

    /**
     * 文件参数
     */
    private val files: MutableList<File> = ArrayList()

    fun addHeader(map: Map<String, Any?>): DLHttp {
        for (entry in map) {
            headers.add(entry.key, entry.value.toString())
        }
        return this
    }

    fun addHeader(key: String, value: Any): DLHttp {
        headers.add(key, value.toString())
        return this
    }

    /**
     * 添加单一参数
     * @param key String
     * @param value Any
     * @return DLHttp
     */
    fun add(key: String, value: Any): DLHttp {
        params[key] = value.toString()
        return this
    }

    /**
     * 添加参数集合
     * @param map Map<String, Any?>
     * @return DLHttp
     */
    fun add(map: Map<String, Any?>): DLHttp {
        params.putAll(map)
        return this
    }

    /**
     * 添加参数类
     * @param map Map<String, Any?>
     * @return DLHttp
     */
    fun add(jsonObj: Any): DLHttp {
        this.jsonObj = jsonObj
        return this
    }


    /**
     * 添加文件参数
     * @param key String
     * @param file File
     * @return DLHttp
     */
    fun add(key: String, file: File): DLHttp {
        files.add(file)
        return this
    }

    /**
     * 以JSON的方式提交数据
     * @param dlhttpCallBack IDLHttpCallBack<T>
     */
    fun <T> buildByJson(dlhttpCallBack: IDLHttpCallBack<T>) {
        BUILD_TYPE = JSON_TYPE
        build<T>(dlhttpCallBack)
    }

    /**
     * 基于http的文件上传(传入文件数组和key)混合参数和文件请求
     * 通过addFormDataPart可以添加多个上传的文件
     */
    fun <T> buildByFile(myDataCallBack: IDLHttpCallBack<T>) {
        val multipartBody: MultipartBody.Builder = MultipartBody.Builder()
        multipartBody.setType(MultipartBody.FORM)
        for (key in params.keys) {
            multipartBody.addFormDataPart(key, params[key].toString())
        }
        if (jsonObj != null) {
            multipartBody.addPart(GsonBinder.toJsonStr(jsonObj).toRequestBody(JSONType))
        }
        var fileBody: RequestBody? = null
        for ((index, file) in files.withIndex()) {
            val fileName = file.name
            fileBody = file.asRequestBody(guessMimeType(fileName).toMediaTypeOrNull())
            multipartBody.addFormDataPart("file$index", fileName, fileBody)
        }
        requestBody = multipartBody.build()
        BUILD_TYPE = FILE_TYPE
        build<T>(myDataCallBack)
    }

    /**
     * 获取文件提交的guessMimeType
     * @param fileName String
     * @return String
     */
    private fun guessMimeType(fileName: String): String {
        val fileNameMap = URLConnection.getFileNameMap()
        var contentTypeFor = fileNameMap.getContentTypeFor(fileName)
        if (contentTypeFor == null) {
            contentTypeFor = "application/octet-stream"
        }
        return contentTypeFor
    }


    fun <T> build(dlHttpCallBack: IDLHttpCallBack<T>) {
        //使用默认header
        for (entry in IBaseCallBack.headerMap) {
            headers.add(entry.key, entry.value.toString())
        }
        //开始请求
        dlHttpCallBack.Builder().startH()
        //数据上传方式
        when (BUILD_TYPE) {
            JSON_TYPE -> {
                if (jsonObj != null) {
                    requestBody = GsonBinder.toJsonStr(jsonObj).toRequestBody(JSONType)
                } else if (params.isNotEmpty()) {
                    requestBody = GsonBinder.toJsonStr(params).toRequestBody(JSONType)
                }
            }
            FILE_TYPE -> {
            }
            FORM_TYPE -> {
                //form表单提交
                val builder: FormBody.Builder = FormBody.Builder()
                for (key in params.keys) {
                    builder.add(key, params[key].toString())
                }
                requestBody = builder.build()
            }
        }
        //数据上传方式
        when (REQUEST_METHOD) {
            REQUEST_GET -> {
                val urlBuilder: HttpUrl.Builder = requestUrl!!.toHttpUrlOrNull()!!.newBuilder()
                for (key in params.keys) {
                    urlBuilder.addQueryParameter(key, params[key].toString())
                }
                request = Request.Builder()
                    .url(urlBuilder.build())
                    .headers(headers.build())
                    .get()
                    .build()
            }
            REQUEST_POST -> {
                request = Request.Builder()
                    .url(requestUrl!!)
                    .headers(headers.build())
                    .post(requestBody!!)
                    .build()
            }
            REQUEST_PUT -> {
                request = Request.Builder()
                    .url(requestUrl!!)
                    .headers(headers.build())
                    .put(requestBody!!)
                    .build()
            }
        }
        val call: okhttp3.Call = dlHttpClient!!.newCall(request!!)
        call.enqueue(object : Callback {
            //请求错误
            override fun onFailure(call: Call, e: IOException) {
                dlHttpCallBack.Builder().errorH(-1, e.message)
            }

            //请求结果
            override fun onResponse(call: Call, response: Response) {
                try {
                    dlHttpCallBack.Builder().respenseH(response)
                    var jsonStr: String = response.body!!.string()
                    Log.e("urlAndJson:${response.request.url}", jsonStr)
                    if (response.code == 200) {
                        dlHttpCallBack.Builder()
                            .successH(GsonBinder.toObj(jsonStr, dlHttpCallBack.mType))
                        return
                    }
                    val baseObj = JSONObject(jsonStr)
                    if (baseObj != null) {
                        when (baseObj!!.getInt(IBaseCallBack.codeStr)) {
                            IBaseCallBack.SUCCESS_CODE -> {
                                if (DLHttpConfig.useDefaultStr) {
                                    val iBaseCallBack = GsonBinder.toObj(
                                        baseObj.getString(IBaseCallBack.dataStr),
                                        IBaseCallBack::class.java
                                    )
                                    dlHttpCallBack.Builder().successH(iBaseCallBack!!.data as T)
                                } else {
                                    dlHttpCallBack.Builder()
                                        .successH(GsonBinder.toObj(jsonStr, dlHttpCallBack.mType))
                                }
                            }
                            IBaseCallBack.ERROR_CODE -> {
                                dlHttpCallBack.Builder()
                                    .errorH(
                                        baseObj.getInt(IBaseCallBack.codeStr),
                                        baseObj.getString(IBaseCallBack.msgStr)
                                    )
                            }
                            IBaseCallBack.TOKEN_ERROR_CODE -> {
                                dlHttpCallBack.Builder()
                                    .tokenErrorH(baseObj.getString(IBaseCallBack.msgStr))
                            }
                            else -> {
                                dlHttpCallBack.Builder()
                                    .errorH(
                                        baseObj.getInt(IBaseCallBack.codeStr),
                                        baseObj.getString(IBaseCallBack.msgStr)
                                    )
                            }
                        }
                    } else {
                        dlHttpCallBack.Builder().successH(jsonStr as T)
                    }
                    dlHttpCallBack.Builder().infoH(baseObj.getString(IBaseCallBack.msgStr))
                    response.close()
                    dlHttpCallBack.Builder().endH()
                } catch (e: JSONException) {
                    e.printStackTrace()
                    Log.e("urlAndJson:", "$response.request.url,${e.message}")
                    dlHttpCallBack.Builder().errorH(-2, "网络开小差,请稍后重试")
                    dlHttpCallBack.Builder().endH()
                }
            }
        })
    }

    /**
     * 下载文件******************************************************************************************************************************************
     */
    /**
     * 文件下载
     *
     * @param url path路径
     * @param destFileDir 本地存储的文件夹路径
     * @param myDataCallBack 自定义回调接口
     */
    private var downCall: Call? = null
    private val totalSize = 0L //APK总大小

    private val downloadSize = 0L // 下载的大小

    private val count = 0f //下载百分比


    fun down(httpFileCallBack: IDLHttpFileCallBack) {
        val request: Request = Request.Builder()
            .url(downUrl!!)
            .build()
        downCall = dlHttpClient!!.newCall(request)
        downCall!!.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                when (response.code) {
                    200 -> {
                        var `is`: InputStream? = null
                        val buf = ByteArray(2048)
                        var len = 0
                        var fos: FileOutputStream? = null
                        // 储存下载文件的目录
                        val dir: File = File(filrDir)
                        if (!dir.exists()) {
                            dir.mkdirs()
                        }
                        RxFileTool.createFileByDeleteOldFile(dir.toString() + downFileName)
                        var updateFile = RxFileTool.getFileByPath("$dir/$downFileName")
                        try {
                            `is` = response.body!!.byteStream()
                            val total: Long = response.body!!.contentLength()
                            httpFileCallBack.Builder().startH(total)
                            fos = FileOutputStream(updateFile)
                            var sum: Long = 0
                            while (`is`.read(buf).also { len = it } != -1) {
                                fos!!.write(buf, 0, len)
                                sum += len.toLong()
                                httpFileCallBack.Builder().progressH(sum)
                            }
                            fos!!.flush()
                            fos.close()
                            httpFileCallBack.Builder().successH(updateFile)
                        } catch (e: Exception) {
                            httpFileCallBack.Builder().errorH("下载失败")
                        } finally {
                            try {
                                `is`?.close()
                            } catch (e: IOException) {
                            }
                            try {
                                fos?.close()
                            } catch (e: IOException) {
                                e.printStackTrace()
                            }
                        }
                    }
                    else -> {
                        httpFileCallBack.Builder().errorH("下载失败")
                    }
                }
            }

            override fun onFailure(call: Call, e: IOException) {
                httpFileCallBack.Builder().errorH(e.message)
            }
        })
    }

    private fun getFileName(url: String?): String? {
        val separatorIndex = url!!.lastIndexOf("/")
        return if (separatorIndex < 0) url else url.substring(separatorIndex + 1, url.length)
    }


    fun cancelDownload() {
        if (downCall != null) {
            downCall!!.cancel()
        }
    }

}
这个类中,我做了符合我们项目的深度配置,但是大部分项目都差不多,所以如果需要适配你们的项目只需要修改 TIM截图20190803101224.png

这个部分就可以了

  1. 上配置类 IBaseCallBack,根据你项目的结构进行成功code,错误code,已经token过期code的值的修改,还有字段名称的修改
/**
 * @Author Dong Lei
 * @Date 2020/12/16 0016-下午 14:35
 * @Info 描述:
 */
class IBaseCallBack<T> {
    companion object {
        val headerMap = HashMap<String, Any>()
        var SUCCESS_CODE = 1
        var ERROR_CODE = 0
        var TOKEN_ERROR_CODE = 2001
        var codeStr = "code"   //code对应的字段对应名
        var msgStr = "message" //Message对应的字段对应名
        var dataStr = "data"    //data对应的字段对应名
        var loginVoStr = ""     //登录返回的数据
    }

    var data: T? = null
    var msg: String? = null
    var code: Int = 0
}

5.返回接口类,可以在这里面修改需要实现的方法

package com.toune.dltools.http

import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import com.google.gson.internal.`$Gson$Types`
import com.tamsiree.rxkit.view.RxToast
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

abstract class IDLHttpCallBack<T> {
    var mType: Type? = null
   open fun start() {}
    abstract fun success(t: T)
    open fun error(code: Int, err: String?) {
        RxToast.error(err!!)
    }

    open fun tokenError(err: String?) {
    }

    open fun info(info: String) {}
    open fun end() {}

    init {
        //Type是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
        val superclass = javaClass.genericSuperclass
        mType = if (superclass is Class<*>) {
            null
        } else {
            //ParameterizedType参数化类型,即泛型
            val parameterized = superclass as ParameterizedType?
            //getActualTypeArguments获取参数化类型的数组,泛型可能有多个
            //将Java 中的Type实现,转化为自己内部的数据实现,得到gson解析需要的泛型
            `$Gson$Types`.canonicalize(parameterized!!.actualTypeArguments[0])
        }
    }


    public inner class Builder {

        fun startH() {
            handler.sendEmptyMessage(START)
        }

        fun errorH(code: Int, err: String?) {
            val message = Message()
            val bundle = Bundle()
            bundle.putInt("code", code)
            bundle.putString("msg", err)
            message.obj = bundle
            message.what = ERROR
            handler.sendMessage(message)
        }

        fun successH(data: T?) {
            var message = Message()
            message.obj = data
            message.what = SUCCESS
            handler.sendMessage(message)
        }

        fun endH() {
            handler.sendEmptyMessage(END)
        }

        fun tokenErrorH(string: String) {
            val message = Message()
            message.obj = string
            message.what = TOKEN_ERROR
            handler.sendMessage(message)
        }

        fun infoH(string: String) {
            val message = Message()
            message.obj = string
            message.what = INFO
            handler.sendMessage(message)
        }

        @SuppressLint("HandlerLeak")
        var handler: Handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                when (msg.what) {
                    START -> start()
                    SUCCESS -> success(msg.obj as T)
                    ERROR -> {
                        if (msg.obj != null) {
                            val bundle = msg.obj as Bundle
                            error(bundle.getInt("code"), bundle.getString("msg"))
                        }
                        end()
                    }
                    TOKEN_ERROR -> {
                        //token出错
                        if (msg.obj != null) {
                            tokenError(msg.obj.toString())
                        }
                        end()
                    }
                    END -> end()
                    INFO -> info(msg.obj as String)
                }
            }
        }
        val START = 10001 //请求开始
        val SUCCESS = 10002 //请求成功
        val ERROR = 10003   //请求出错
        val END = 10004 //请求结束
        val INFO = 10005 //token出错
        val TOKEN_ERROR = 10006 //token出错
    }
}
  1. 为了更清晰,我把文件下载做了单独的返回
package com.toune.dltools.http

import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import android.os.Message
import java.io.File

/**
 * 下载文件返回
 */
abstract class IDLHttpFileCallBack {
    abstract fun start(toatleSize: Int)
    abstract fun progress(size: Int)
    abstract fun success(file: File?)
    abstract fun error(err: String?)
   open fun end() {}

    inner class Builder{
        fun errorH(err: String?) {
            val message = Message()
            message.obj = err
            message.what = FILE_ERROR
            fileHandler.sendMessage(message)
        }

        fun startH(total: Long) {
            var message = Message()
            message.obj = total.toInt()
            message.what = FILE_START
            fileHandler.sendMessage(message)
        }

        fun progressH(sum: Long) {
            // 下载中更新进度条
            var message = Message()
            message = Message()
            message.obj = sum.toInt()
            message.what = FILE_PROGRESS
            fileHandler.sendMessage(message)
        }

        fun successH(file: File?) {
            // 下载完成
            var message = Message()
            message = Message()
            message.obj = file
            message.what = FILE_SUCCESS
            fileHandler.sendMessage(message)
        }

        var fileHandler: Handler = @SuppressLint("HandlerLeak")
        object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                when (msg.what) {
                    FILE_START -> start(msg.obj as Int)
                    FILE_PROGRESS -> progress(msg.obj as Int)
                    FILE_SUCCESS -> success(msg.obj as File)
                    FILE_ERROR -> {
                        if (msg.obj != null) {
                            error(msg.obj.toString())
                        }
                        end()
                    }
                    FILE_END -> end()
                }
            }
        }
        private val FILE_START = 100001
        private val FILE_SUCCESS = 100002
        private val FILE_ERROR = 100003
        private val FILE_END = 100004
        private val FILE_PROGRESS = 100005
    }
}

GsonBinder类

 package com.toune.dltools.http

import android.text.TextUtils
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.*
import kotlin.jvm.Throws

/**
 * Created by Administrator on 2018/1/11 0011.
 */
object GsonBinder {
    //定义并配置gson
    private val gson: Gson = GsonBuilder() //建造者模式设置不同的配置
        .serializeNulls() //序列化为null对象
        .setDateFormat("yyyy-MM-dd HH:mm:ss") //设置日期的格式
        .disableHtmlEscaping() //防止对网址乱码 忽略对特殊字符的转换
        .registerTypeAdapter(String::class.java, StringConverter()) //对为null的字段进行转换
        .create()

    /**
     * 对解析数据的形式进行转换
     *
     * @param obj 解析的对象
     * @return 转化结果为json字符串
     */
    fun toJsonStr(obj: Any?): String {
        return if (obj == null) {
            ""
        } else try {
            gson.toJson(obj)
        } catch (e: Exception) {
            ""
        }
    }

    /**
     * 解析为一个具体的对象
     *
     * @param json 要解析的字符串
     * @param obj  要解析的对象
     * @param <T>  将json字符串解析成obj类型的对象
     * @return
    </T> */
    fun <T> toObj(json: String?, obj: Class<T>?): T? {
        //如果为null直接返回为null
        return if (obj == null || TextUtils.isEmpty(json)) {
            null
        } else try {
            gson.fromJson(json, obj)
        } catch (e: Exception) {
            null
        }
    }

    /**
     * @return 不区分类型 传什么解析什么
     */
    fun <T> toObj(jsonStr: String?, type: Type?): T? {
        var t: T? = null
        try {
            t = gson.fromJson(jsonStr, type)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return t
    }

    /**
     * 将Json数组解析成相应的映射对象列表
     * 解决类型擦除的问题
     */
    fun <T> toList(jsonStr: String?, clz: Class<T>): List<T> {
        var list: List<T> = gson.fromJson(jsonStr, type(clz))
        if (list == null) list = ArrayList()
        return list
    }

    fun <T> toMap(jsonStr: String?, clz: Class<T>): Map<String?, T> {
        var map: Map<String?, T> = gson.fromJson(jsonStr, type(clz))
        if (map == null) map = HashMap()
        return map
    }

    fun toMap(jsonStr: String?): Map<String, Any> {
        val type: Type = object : TypeToken<Map<String?, Any?>?>() {}.type
        return gson.fromJson(jsonStr, type)
    }

    private class type(private val type: Type) : ParameterizedType {
        override fun getActualTypeArguments(): Array<Type> {
            return arrayOf(type)
        }

        override fun getRawType(): Type {
            return ArrayList::class.java
        }

        override fun getOwnerType(): Type? {
            return null
        }
    }

    /**
     * 实现了 序列化 接口    对为null的字段进行转换
     */
    internal class StringConverter : JsonSerializer<String?>, JsonDeserializer<String?> {
        //字符串为null 转换成"",否则为字符串类型
        @Throws(JsonParseException::class)
        override fun deserialize(
            json: JsonElement,
            typeOfT: Type?,
            context: JsonDeserializationContext?
        ): String {
            return json.getAsJsonPrimitive().getAsString()
        }

        override fun serialize(
            src: String?,
            typeOfSrc: Type?,
            context: JsonSerializationContext?
        ): JsonElement {
            return if (src == null || src == "null") JsonPrimitive("") else JsonPrimitive(src.toString())
        }
    }
}

GsonBinder类需要引用 implementation 'com.google.code.gson:gson:2.8.5'
以上就是全部代码了,第一次写文章,文字叙述和表达有不到位的地方,还请见谅,有问题可以留言

相关文章

网友评论

    本文标题:OkHttp3 超级简单实用封装

    本文链接:https://www.haomeiwen.com/subject/ttnddctx.html