网络请求回调的实现方式

  • 一个应用程序可能会在许多个地方用到网络功能,而发送HTTP请求的代码基本上是相同的,如果我们每一次都去编写一遍HTTP请求的代码,这显然是不太合理的做法.
  • 通常情况下我们应该将这些通用的网络操作提取到一个公共的类当中,并且提供一个通用的方法,当想要发起网络请求的时候,只需要简单调用一下这个方法即可.
  • 比如我们可以使用如下写法
object HttpUtil {
        private fun sendRequest(address: String) : String {
            //开启线程发起网络请求
            var connection: HttpURLConnection? = null
            try {
                val response = StringBuilder()
                val url = URL(address)
                connection = url.openConnection() as HttpURLConnection
                connection.connectTimeout = 8000
                connection.readTimeout = 8000
                val input = connection.inputStream
                //下面对获取到的输入流进行读取
                val reader = BufferedReader(InputStreamReader(input))
                reader.use {
                    reader.forEachLine {
                        response.append(it)
                    }
                }
                return response.toString()
            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                connection?.disconnect()
            }
        }
    }
  • 以后当想要发起一条HTTP请求的时候,就可以这样写了
val address = "https://www.baidu.com"
val response = HttpUtil.sendRequest(address)
  • 在获取到服务器响应的数据之后,我们就可以对他进行解析和处理了.
  • 但是需要注意的是,网络请求通常都是耗时的,而sendRequest()方法当中并没有开启线程,这就可能会造成主线程被堵塞
  • 但是如果单纯的在sendRequest()方法中开一个线程,服务器响应的数据是无法进行返回的,因为所有耗时的逻辑都是在子线程中进行的,sendRequest()方法会在服务器还没有来得及反应的时候就执行结束了,当然也就无法返回响应的数据了.
  • 对于这种情况只需要使用编程语言的回调机制就可以了
  • 首先需要定义一个接口,比如将他命名成为HttpCallbackListener,代码如下所示
interface HttpCallbackListener {
    fun onFinish(response: String)
    fun onError(e: Exception)
}
  • 可以看到在接口当中定义了两个方法,onFinish()和onError()
  • onFinish()方法表示服务器成功响应我们请求的时候进行调用
  • onError()方法表示当进行网络操作出现错误的时候进行调用
  • 这两个方法都有参数,onFinish()方法中的参数代表服务器返回的数据,而onError()方法中的参数记录着错误的详细信息
  • 接着修改HttpUtil当中的代码,如下所示
object HttpUtil {
        private fun sendRequest(address: String, listener: HttpCallbackListener) {
         thread {
               //开启线程发起网络请求
              var connection: HttpURLConnection? = null
              try {
                  val response = StringBuilder()
                  val url = URL(address)
                  connection = url.openConnection() as HttpURLConnection
                  connection.connectTimeout = 8000
                  connection.readTimeout = 8000
                  val input = connection.inputStream
                  //下面对获取到的输入流进行读取
                  val reader = BufferedReader(InputStreamReader(input))
                  reader.use {
                      reader.forEachLine {
                          response.append(it)
                      }
                  }
                  //回调onFinish()方法
                  listener.onFinish(response.toString())
              } catch (e: Exception) {
                  e.printStackTrace()
                  //回调onError()方法
                  listener.onError(e)
              } finally {
                  connection?.disconnect()
              }
          }   
        }
    }
  • 首先给sendRequest()方法中添加了一个HttpCallbackListener类型的参数,并在方法的内部开启了一个子线程,然后在子线程中执行具体的网络操作.
  • 现在sendRequest()方法中接收了两个参数,因此我们在调用它的时候,还需要将HttpCallbackListener的实例传入,如下所示
HttpUtil.sendRequest(address, object : HttpCallbackListener{
    override fun onFinish(response: String) {
        //得到服务器所需要的内容
    }
    
    override fun onError(e: Exception) {
        //在这里对异常情况进行处理
    }
})
  • 这样当服务器成功响应的时候,我们就可以在onFinish()方法里面对数据进行处理了,类似的,如果出现异常,就可以在onError()方法里对异常情况进行处理,如此一来,就巧妙的利用回调机制将响应的数据成功返回给调用方了.
  • 上述是使用了HttpURLConnection的写法,还是比较复杂的,如果使用OkHttp其实就会变得简单一些
  • 在HttpUtil当中加入一个sendOkHttpRequest()方法,如下所示
object HttpUtil {
    fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) {
        val client = OkHttpClient()
        val request = Request.Builder()
        	.url(address)
        	.build()
        client.newCall(request).enqueue(callback)
    }
}
  • 可以看到sendOkHttpRequest()方法中有一个okhttp3.Callback参数,这个是OkHttp库中自带的回调接口,类似于我们刚才自己编写的HttpCallbackListener,然后在client.newCall()之后没有像之前那样一直调用execute()方法,而是调用了一个enqueue()方法,并把okhttp3.Callback参数进行传入
  • OkHttp在enqueue()方法的内部已经帮我们开好了线程,然后会在子线程中执行HTTP请求,并将最终的请求结果回调到okhttp3.Callback当中
  • 那么我们在调用sendOkHttpRequest()方法的时候就可以这样写:
HttpUtil.sendOkHttpRequest(address: String, object : Callback {
    override fun onRequese(call: Call, response: Response) {
        //得到服务器返回的具体内容
        val responseData = response.body?.string()
    }
    override fun onFailure(call: Call, e: Exception) {
        //在这里进行异常情况处理
    }
})
  • 需要注意的是无论是HttpURLConnection还是OkHttp,最终回调的接口都还是在子线程当中运行的,因此我们不可以在这里执行任何UI操作,除非借助runOnUiThread()方法来进行线程更替.