用Kotlin实现HttpURLConnection的网络请求

2019-11-07  本文已影响0人  超级绿茶

现在Android的网络请求框架已经非常成熟完善了,但在极个别的情况下仍需要我们手动去实现一个网络请求。

说到网络请求这块Android原生的SDK无非就是HttpClient和HttpURLConnection这两个。

两者的共同点是都能以流的形式对数据进行上传或下载。
不同点在于HttpURLConnection更易于扩展且速度和性能更优于HttpClient,所以从6.0开始谷歌就淘汰HttpClient了。

因此我们也直接从HttpURLConnection入手,看一下用Kotlin来实现一个简单的框架会怎么样的。在开始之间我们需要在module:app的build.gradle引入对协程的依赖:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'

用HttpUrlConnection实现的网络请求大致分为如下几个步骤:

  1. 实例化一个URL类并把接口地址作为参数传入。
val url = URL("http://www.test.com")
  1. 通过URL实例的openConnection方法获取到HttpURLConnection对象。
val connection = url.openConnection() as HttpURLConnection
  1. 对获取到的HttpURLConnection实例进行设置,例如超时时长和请求方法等。
connection.let {
            it.requestMethod = "POST"
            it.connectTimeout = CONNECT_TIME_OUT
            it.readTimeout = READ_TIME_OUT
            it.doInput = true
            it.doOutput = true
            it.useCaches = false // POST请求不能使用缓存
            it.instanceFollowRedirects = true // 是否允许HTTP的重定向
            // 设置请求参数的格式
            // application/json;charset=UTF-8  为JSON格式
            // application/x-www-form-urlencoded  为表单格式
            it.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
        }
  1. 通过HttpURLConnection实例的connect方法建立连接,注意:网络请求必须运行在子线程或协程上,不然会报异常。
connection.connect()
  1. 如果是POST带参请求的话需要将参数拼接成字符串后传给HttpURLConnection的OutputStream输出流。如果参数中带中文或特殊字符的话需要用URLEncoder.encode方法转换。
  2. 判断HttpURLConnection的responseCode的值,如果是200说明请求成功。
connection.responseCode // 返回200表示连接成功,出错就可能是400或500等
  1. 连接完成后用disconnect方法断开连接。
connection.disconnect()

为了便于组织代码,我把网络请求的代码封装到SimpleHttpUtils文件

object SimpleHttpUtils {
    private const val TAG = "SimpleHttpUtils"
    private const val CONNECT_TIME_OUT = 10000
    private const val READ_TIME_OUT = 10000

    /**
     * GET请求
     */
    fun get(url: String, mapParam: Map<String, String>?): String {
        // 如果有参数的话就拼接参数到url后面
        val urlParam = if (mapParam == null) url else "${url}?${converMap2String(mapParam)}"
        Log.i(TAG, urlParam)
        // 构建URLConnection实例
        val connection = buildURLConnection(urlParam)
        connection.setContentType()
        connection.connect() // 建立连接
        // 从服务端获取响应码,连接成功是200
        val code = connection.responseCode
        Log.i(TAG, code.toString())
        // 根据响应码获取不同输入流
        val inStream = if (code == 200)
            connection.inputStream
        else
            connection.errorStream
        // 输入流转换成字符串
        val result = inStream.bufferedReader().lineSequence().joinToString()
        Log.i(TAG, result)
        connection.disconnect() //断开连接
        return result
    }

    /**
     * POST请求 - 参数为JSON格式
     */
    fun post(url: String, jsonParam: String?): String {
        val connection = buildURLConnection(url, false)
        connection.setContentType()
        connection.connect() // 建立连接
        // 向服务端发送请求参数
        if (!jsonParam.isNullOrEmpty()) {
            connection.outputStream.let {
                it.write(jsonParam.toByteArray(Charsets.UTF_8))
                it.flush()
                it.close()
            }
        }
        // 从服务端获取响应码,连接成功是200
        val code = connection.responseCode
        Log.i(TAG, code.toString())
        // 根据响应码获取不同输入流
        val inStream = if (code == 200)
            connection.inputStream
        else
            connection.errorStream
        // 输入流转换成字符串
        val result = inStream.bufferedReader().lineSequence().joinToString()
        Log.i(TAG, result)
        connection.disconnect() //断开连接
        return result
    }

    /**
     * POST请求 - 参数为表格格式
     */
    fun post(url: String, mapParam: Map<String, String>): String {
        val connection = buildURLConnection(url, false)
        connection.setContentType(1)
        connection.connect() // 建立连接
        connection.outputStream.let {
            it.write(converMap2String(mapParam).toByteArray())
            it.flush()
            it.close()
        }
        // 从服务端获取响应码,连接成功是200
        val code = connection.responseCode
        Log.i(TAG, code.toString())
        // 根据响应码获取不同输入流
        val inStream = if (code == 200)
            connection.inputStream
        else
            connection.errorStream
        // 输入流转换成字符串
        val result = inStream.bufferedReader().lineSequence().joinToString()
        Log.i(TAG, result)
        connection.disconnect() //断开连接
        return result
    }

    /**
     * 创建一个基于URLConnection类的对象用于
     * @param url 接口地址
     * @param isGet GET请求还是POST
     */
    private fun buildURLConnection(url: String, isGet: Boolean = true): HttpURLConnection {
        val url = URL(url)
        val connection = url.openConnection() as HttpURLConnection
        connection.let {
            it.requestMethod = if (isGet) "GET" else "POST"
            it.connectTimeout = CONNECT_TIME_OUT
            it.readTimeout = READ_TIME_OUT
            it.doInput = true
            it.doOutput = true
            it.useCaches = isGet // POST请求不能使用缓存
            it.instanceFollowRedirects = true // 是否允许HTTP的重定向
        }
        return connection
    }


    /**
     * 设置请求编码格式
     * @param 0用于JSON格式,1用于表单格式,3用于传输JAVA序列化对象
     */
    private fun URLConnection.setContentType(type: Int = 0) {
        val typeString = when (type) {
            1 -> "application/x-www-form-urlencoded"
            2 -> "application/x-java-serialized-object"
            else -> "application/json;charset=UTF-8"
        }
        this.setRequestProperty("Content-Type", typeString)
    }

    /**
     * 把map集合的参数拼接成字符串
     * @param isEncode 是否需要转换码
     */
    private fun converMap2String(mapParam: Map<String, String>, isEncode: Boolean = true): String {
        return mapParam.keys.joinToString(separator = "&") { key ->
            val value = if (isEncode) URLEncoder.encode(mapParam[key], "utf-8") else mapParam[key]
            "$key=$value"
        }
    }
}

然后在MainActvitiy中调用

class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btnGetRequest.setOnClickListener { onGetRequest() }
        btnPostRequest.setOnClickListener { onPostRequest() }
    }

    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }

    /**
     * Get请求
     */
    private fun onGetRequest() = launch {
        val dlg = indeterminateProgressDialog("loading...")
        // 接名地址
        val url = "http://v.juhe.cn/weather/index"
        // 接口参数
        val mapParam = mapOf(
            "format" to "1",
            "cityname" to "上海",
            "key" to "3bc829216bb4ede1e846fe91b3df5543"
        )
        // 挂起当前的协程并运行自定义的get方法,方法返回后再恢复协程
        val result = withContext(Dispatchers.IO) {
            SimpleHttpUtils.get(url, mapParam) // 发起GET请求
        }
        tvResult.text = result
        dlg.dismiss()
    }

    /**
     * Post请求
     */
    private fun onPostRequest() = launch {
        val dlg = indeterminateProgressDialog("loading...")
        // 接名地址
        val url = "https://www.pgyer.com/apiv2/app/check"
        // 接名参数
        val mapParam = mapOf(
            "_api_key" to "38bb2810d62311dcfa573930b367c180",
            "appKey" to "b6e389f007a01631e7024c6908846e62"
        )
        // 挂起当前的协程并运行自定义的post方法,方法返回后再恢复协程
        val result = withContext(Dispatchers.IO) {
            SimpleHttpUtils.post(url, mapParam) // 发起POST请求
        }
        tvResult.text = result
        dlg.dismiss()
    }
}

熟悉Java的朋友可以发现同样的调用方式在Kotlin中的确少了许多代码量。

下载源代码:https://t00y.com/file/22686471-408583233

点击链接加入群聊【口袋里的安卓】:https://jq.qq.com/?_wv=1027&k=5z4fzdT

上一篇下一篇

猜你喜欢

热点阅读