用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实现的网络请求大致分为如下几个步骤:
- 实例化一个URL类并把接口地址作为参数传入。
val url = URL("http://www.test.com")
- 通过URL实例的openConnection方法获取到HttpURLConnection对象。
val connection = url.openConnection() as HttpURLConnection
- 对获取到的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")
}
- 通过HttpURLConnection实例的connect方法建立连接,注意:网络请求必须运行在子线程或协程上,不然会报异常。
connection.connect()
- 如果是POST带参请求的话需要将参数拼接成字符串后传给HttpURLConnection的OutputStream输出流。如果参数中带中文或特殊字符的话需要用URLEncoder.encode方法转换。
- 判断HttpURLConnection的responseCode的值,如果是200说明请求成功。
connection.responseCode // 返回200表示连接成功,出错就可能是400或500等
- 连接完成后用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