上一讲已经掌握了HttpURLConnection和OkHttp的用法,知道如何发起HTTP请求,以及解析服务器返回的数据,但是也许你还没发现,之前我们的写法其实是很有问题的,因为一个应用程序很可能会在许多地方使用到网络功能,而发送HTTP请求的代码基本都是相同的,如果我们每次都去编写一遍发送HTTP请求的代码,这显然是非常差劲的做法。
通常情况下我们都应该将这些通用的网络操作提取到一个公共的类里,并提供一个静态方法,当想要发起网络请求的时候,只需要简单的调用一下这个方法即可,比如使用如下的写法:
以后每当需要发起一条HTTP请求的时候就可以这样写:
在获取到服务器响应的数据后,我们就可以对它进行解析和处理了,但是需要注意的是,网络请求通常都是属于耗时操作,而:sendHttpRequest()方法的内部并没有开启线程,这样就可能导致在调用:sendHttpRequest()方法的时候使得主线程被阻塞。
如果我们在:sendHttpRequest()方法内部开启一个线程来发起HTTP请求,那么服务器响应的数据是无法进行返回的,所有的耗时逻辑都是在子线程里进行的,sendHttpRequest()方法会在服务器还没来得及响应的时候就执行结束了,当然就无法返回响应的数据了。
那么遇到这种情况时该怎么办呢?其实解决办法并不难,只需要使用Java的回调机制就可以了。下面就来学习一下回调机制到底是如何使用的:
首先需要定义一个接口,比如将它命名为HttpCallbackListener,代码如下:
可以看到,我们在接口中定义了两个方法,onFinish()方法表示当服务器成功响应我们请求的时候调用,onError()表示当进行网络操作出现错误的时候调用,这两个方法都带有参数,onFinish()方法中的参数代表着服务器返回的数据,而onError()方法中的参数记录着错误的详细信息。
接着修改HttpUtil中的代码:
我们首先给sendHttpRequest()方法添加了一个HttpCallbackListener参数,并在方法的内部开启了一个子线程,然后在子线程里去执行具体的网络操作,注意,子线程中是无法通过return语句来返回数据的,因此这里我们将服务器响应的数据传入了HttpCallbackListener的onFinish()方法中,如果出现了异常就将异常原因传入到onError()方法中。
现在sendHttpRequest()方法接收两个参数,因此我们在调用它的时候还需要将HttpCallbackListener的实例传入,如下所示:
这样的话,当服务器成功响应的时候,我们就可以在onFinish()方法里对响应数据进行处理了。类似的,如果出现了异常,就可以在onError()方法里对异常情况进行处理,如此一来,我们就巧妙地利用回调机制将响应数据成功返回给调用方了。
不过你会发现,上述使用HttpURLConnection的写法总体来说还是比较复杂的,那么使用OkHttp会变得简单吗?答案是肯定的,而且要简单得多,下面我们来具体看一下:在HttpUtil中加入一个sendOkHttpRequest()方法,如下所示:
可以看到,sendOkHttpRequest()方法中有一个okhttp3.Callback参数,这个是OkHttp库中自带的一个回调接口,类似于我们刚才自己编写的HttpCallbackListener,然后在client.newCall()之后没有像之前那样一直调用execute()方法,而是调用了一个enqueue()方法,并把okhttp3.Callback参数传入。这里可以猜到:OKHttp在enqueue()方法的内部已经帮我们开好了线材了,然后会在子线程中去执行HTTP请求,并将最终的请求结果回调到okhttp3.Callback当中。
那么我们在调用sendOkHttpRequest()方法的时候就可以这样写:
由此可以看出,OkHttp的接口设计的确实非常人性化,它将一些常用的功能进行了很好的封装,使得我们只需编写少量的代码就能完成较为复杂的网络操作,当然这并不是OKHttp的全部,后面我们还会继续学习它的相关知识
另外需要注意的是:不管是使用HttpURLConnection还是OkHttp,最终的回调接口都还是在子线程中运行的,因此我们不可以在这里执行任何的UI操作,除非借助runOnUiThread()方法来进行线程转换,至于具体的原因后面会说到。