美文网首页
用Kotlin实现HttpURLConnection的网络请求

用Kotlin实现HttpURLConnection的网络请求

作者: 超级绿茶 | 来源:发表于2019-11-07 11:25 被阅读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

    相关文章

      网友评论

          本文标题:用Kotlin实现HttpURLConnection的网络请求

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