如果说真的要去深入分析 HTTP 协议,可能需要花费整整一本书的篇幅。这里我当然不会这干,因为毕竟你是跟着我学习 Android 开发的,而不是网站开发。对于 HTTP 协议,你只需要稍微了解一些就足够了,它的工作原理特别简单,就是客户端向服务器发出一条 HTTP 请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。是不是非常简单?一个浏览器的基本工作原理也就是如此了。比如说上一节中使用到的 WebView 控件,其实也就是我们向百度的服务器发起了一条 HTTP 请求,接着服务器分析出我们想要访问的是百度的首页,于是会把该网页的 HTML 代码进行返回,然后 WebView 再调用手机浏览器的内核对返回的 HTML 代码进行解析,最终将页面展示出来。
简单来说,WebView 已经在后台帮我们处理好了发送 HTTP 请求、接收服务响应、解析返回数据,以及最终的页面展示这几步工作,不过由于它封装得实在是太好了,反而使得我们不能那么直观地看出 HTTP 协议到底是如何工作的。因此,接下来就让我们通过手动发送 HTTP 请求的方式,来更加深入地理解一下这个过程。
11.2.1 使用HttpURLConnection
在过去,Android 上发送 HTP 请求一般有两种方式:HttpURLconnection 和 HttpClient。不过由于 HttpClient 存在 API 数量过多、扩展困难等缺点,Android 团队越来越不建议我们使用这种方式。终于在 Android6.0 系统中,HttpClient 的功能被完全移除了,标志着此功能被正式弃用,因此本小节我们就学习一下现在官方建议使用的 HttpURLConnection 的用法。
首先需要获取到 HttpURLConnection 的实例,一般只需创建 出一个 URL 对象,并传人目标的网络地址,然后调用一下 openConnection() 方法即可,如下所示:
val url = URL("https://www.baidu.com")
val connection = url.openConnection() as HttpURLConnection
在得到了 HttpURLConnection 的实例之后,我们可以设置一下 HTTP 请求所使用的方法。常用的方法主要有两个:GET 和 POST。GET 表示希望从服务器那里获取数据,而 POST 则表示希望提交数据给服务器。写法如下:
connection.requestMethod = "GET"
接下来就可以进行一些自由的定制了,比如设置连接超时、读取超时的毫秒数,以及服务器希望得到的一些消息头等。这部分内容根据自己的实际情况进行编写,示例写法如下:
connection.connectTimeout = 8000
connection.readTimeout = 8000
之后再调用 getInputStream() 方法就可以获取到服务器返回的输人流了,剩下的任务就是对输人流进行读取,如下所示:
val input = connection.inputStream
最后可以调用 disconnect() 方法将这个 HTTP 连接关闭掉,如下所示:
connection.disconnect()
下面就让我们通过一个具体的例子来真正体验一下 HttpURLConnection 的用法。新建一个 NetWorkTest项目,首先修改 activity_main.xml 中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/sendRequestBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Send Request"
/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/responseText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</ScrollView>
</LinearLayout>
注意这里我们使用了一个新的控件:ScrollView。它是用来做什么的呢?由于手机屏幕的空间一般都比较小,有些时候过多的内容一屏是显示不下的,借助 ScrollView 控件的话,我们就可以以滚动的形式査看屏幕外的那部分内容。另外,布局中还放置了一个 Button 和一个 TextView,Button 用于发送 HTTP 请求,TextView 用于将服务器返回的数据显示出来。
接着修改 MainActivity 中的代码,如下所示:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendRequestBtn.setOnClickListener {
sendRequestWithHttpURLConnection()
}
}
private fun sendRequestWithHttpURLConnection() {
// 开启线程发起网络请求
thread {
var collection:HttpURLConnection? = null
try {
val response = StringBuilder()
val url = URL("https://www.baidu.com")
collection = url.openConnection() as HttpURLConnection
collection.connectTimeout = 8000
collection.readTimeout = 8000
val input = collection.inputStream
// 下面对获取到的输入流进行读取
val reader = BufferedReader(InputStreamReader(input))
reader.use {
reader.forEachLine {
response.append(it)
}
}
showResponse(response.toString())
}catch (e:Exception){
e.printStackTrace()
}finally {
collection?.disconnect()
}
}
}
private fun showResponse(response: String) {
runOnUiThread {
// 在这里执行UI 操作,将结果显示到界面上
responseText.text = response
}
}
}
可以看到,我们在 Send Request 按钮的点击事件里调用了 sendRequestWithHttpURLConnection() 方法,在这个方法中先是开启了一个子线程,然后在子线程里使用 HttpURLConnection 发出一条 HTTP 请求,请求的目标地址就是百度的首页。接着利用 BufferedReader 对服务器返回的流进行读取,并将结果传入到了 showResponse() 方法中。而在 showResponse() 方法里则是调用了一个 runOnUiThread() 方法,然后在这个方法的Lambda 表达式中进行操作,将返回的数据显示到界面上。
那么这里为什么要用这个 runOnUiThread() 方法呢?这是因为 Android 是不允许在子线程中进行UI 操作的。我们在10.2.3 小节中学习了异步消息处理机制的工作原理,而 runOnUiThread() 方法其实就是对异步消息处理机制进行了一层封装,它背后的工作原理和10.2.3 小节中所介绍的内容是一模一样的。借助这个方法,我们就可以将服务器返回的数据更新到界面上了。
完成的流程就是这样。不过在开始运行之前,仍然别忘了要声明一下网络权限。修改Androidmanifest.xml 中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.networktest">
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
好了,现在运行一下程序,并点击 Send Request 按钮,结果如图所示。
是不是看得头晕眼花?没错,服务器返回给我们的就是这种 HTML 代码,只是通常情况下浏览器都会将这些代码解析成漂亮的网页后再展示出来。
那么如果是想要提交数据给服务器应该怎么办呢?其实也不复杂,只需要将 HTTP 请求的方法改成 POST,并在获取输人流之前把要提交的数据写出即可。注意每条数据都要以键值对的形式存在,数据与数据之间用“&”符号隔开,比如说我们想要向服务器提交用户名和密码,就可以这样写:
collection.requestMethod = "POST"
val output = DataOutputStream(collection.outputStream)
output.writeBytes("username=admin&password=123456")
好了相信你已经将HttpURLConnection 的用法很好地掌握了。
11.2.2 使用OkHttp
当然我们并不是只能使用 HttpURLConnection,完全没有任何其他选择,事实上在开源盛行的今天,有许多出色的网络通信库都可以替代原生的 HttpURLConnection,而其中 OkHttp 无疑是做得最出色的
Okhttp 是由鼎鼎大名的 Square 公司开发的,这个公司在开源事业上面贡献良多,除了 OkHttp 之外,还开发了像 Retrofit 、Picasso等著名的开源项目。OkHttp 不仅在接口封装上面做得简单易用,就连在底层实现上也是自成一派,比起原生的 HttpURLConnection,可以说是有过之而无不及,现在已经成了广大 Android 开发者首选的网络通信库。那么本小节我们就来学习ー下 OkHttp 的用法,OkHttp 的项目主页地址是: https: /github.com/square//okhttp
在使用OkHttp之前,我们需要先在项目中添加OkHttp库的依赖。编辑ap/build.gradle 文件在 dependencies 闭包中添加如下内容:
dependencies {
...
implementation 'com.squareup.okhttp3:okhttp:4.1.0'
}
添加上述依赖会自动下载两个库,一个是 OkHttp 库,一个是 Okio 库,后者是前者的通信基础。其中 4.1.0 Okhttp 的版本,你可以访问 Okhttp 的项目主页来查看当前最新的版本是多少。
下面我们来看一下 OkHttp 的具体用法,首先需要创建一个 OkHttpClient 的实例,如下所示:
val client = OkHttpClient()
接下来如果想要发起一条HTTP 请求,就需要创建一个Request 对象:
val request = Request.Builder().build()
当然,上述代码只是创建了一个空的 Request 对象,并没有什么实际作用,我们可以在最终的 build() 方法之前连缀很多其他方法来丰富这个 Request 对象。比如可以通过 url() 方法来设置目标的网络地址,如下所示:
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
之后调用 Okhttpclient 的 newCall() 方法来创建一个 Call 对象,并调用它的 execute() 方法来发送请求并获取服务器返回的数据,写法如下:
val response = client.newCall(request).execute()
其中 Response 对象就是服务器返回的数据了,我们可以使用如下写法来得到返回的具体内容:
val responseData = response.body?.string()
如果是发起一条 POST 请求会比 GET 请求稍微复杂我们需要先构建出一个 RequestBody 对象来存放待提交的参数,如下所示:
val requestBody = FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build()
然后在 Request. Builder 中调用一下 post() 方法,并将 RequestBody 对象传入:
val request = Request.Builder()
.url("https://www.baidu.com")
.post(requestBody)
.build()
接下来的操作就和 GET 请求一样了,调用 execute() 方法来发送请求并获取服务器返回的数据即可。
好了,OkHttp 的基本用法就先学到这里,本书中后面所有网络相关的功能我们都将会使用 OkHttp 来实现,到时候再进行进一步的学习。那么现在我们先把 NetworkTest 这个项目改用 OkHttp 的方式再实现一遍吧。
由于布局部分完全不用改动,所以现在直接修改 MainActivity 中的代码,如下所示:
private fun sendRequestWithOkHttp() {
// 开启线程发起网络请求
thread {
try {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
val response = client.newCall(request).execute()
val responseData = response.body?.string()
if (responseData != null){
showResponse(responseData)
}
}catch (e:Exception){
e.printStackTrace()
}
}
}
这里我们并没有做太多的改动,只是添加了一个 sendRequestWithOkHttp() 方法,并在 Send Request 按钮的点击事件里去调用这个方法。在这个方法中同样还是先开启了一个子线程,然后在子线程里使用 OkHttp 发出一条 HTTP 请求,请求的目标地址还是百度的首页,OkHttp的用法也正如前面所介绍的一样。最后仍然还是调用了 showResponse() 方法来将服务器返回的数据显示到界面上。
仅仅是改了这么多代码,现在我们就可以重新运行一下程序了。点击 Send Request 按钮后你会看到和上一小节中同样的运行结果,由此证明,使用 OkHttp 来发送 HTTP 请求的功能也已经成功实现了。
这样的话,相信你就已经把 HttpURLConnection 和 OkHttp 的基本用法都掌握得差不多了。