Android使用HTTP协议访问网络
如果说真的要去深入分析HTTP协议,可能需要花费整整一本书的篇幅。这里我当然不会这么干,因为毕竟你是跟着我学习Android开发的,而不是网站开发。
对于HTTP协议,你只需要稍微了解一些就足够了,它的工作原理特别简单,就是客户端向服务器发出一条HTTP请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。
是不是非常简单?一个浏览器的基本工作原理也就是如此了。比如说上一节中使用到的WebView控件,其实也就是我们向百度的服务器发起了一条HTTP请求,接着服务器分析出我们想要访问的是百度的首页,于是会把该网页的HTML代码进行返回,然后WebView再调用手机浏览器的内核对返回的HTML代码进行解析,最终将页面展示出来。
简单来说,WebView已经在后台帮我们处理好了发送HTTP请求、接收服务响应、解析返回数据,以及最终的页面展示这几步工作,不过由于它封装得实在是太好了,反而使得我们不能那么直观地看出HTTP协议到底是如何工作的。因此,接下来就让我们通过手动发送HTTP请求的方式,来更加深入地理解一下这个过程。
使用HttpURLConnection
在过去,Android上发送HTTP请求一般有两种方式:HttpURLConnection和HttpClient。不过由于HttpClient存在API数量过多、扩展困难等缺点,Android团队越来越不建议我们使用这种方式。终于在Android 6.0系统中,HttpClient的功能被完全移除了,标志着此功能被正式弃用,因此本小节我们就学习一下现在官方建议使用的HttpURLConnection的用法。
首先需要获取到HttpURLConnection的实例,一般只需new出一个URL对象,并传入目标的网络地址,然后调用一下openConnection()方法即可,如下所示:
URL url = new URL("http://www.baidu.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
在得到了HttpURLConnection的实例之后,我们可以设置一下HTTP请求所使用的方法。常用的方法主要有两个:GET和POST。GET表示希望从服务器那里获取数据,而POST则表示希望提交数据给服务器。写法如下:
connection.setRequestMethod("GET");
接下来就可以进行一些自由的定制了,比如设置连接超时、读取超时的毫秒数,以及服务器希望得到的一些消息头等。这部分内容根据自己的实际情况进行编写,示例写法如下:
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
之后再调用getInputStream()方法就可以获取到服务器返回的输入流了,剩下的任务就是对输入流进行读取,如下所示:
InputStream in = connection.getInputStream();
最后可以调用disconnect()方法将这个HTTP连接关闭掉,如下所示:
connection.disconnect();
下面就让我们通过一个具体的例子来真正体验一下HttpURLConnection的用法。新建一个NetworkTest项目,首先修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/send_request"
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/response_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</LinearLayout>
注意这里我们使用了一个新的控件:ScrollView,它是用来做什么的呢?由于手机屏幕的空间一般都比较小,有些时候过多的内容一屏是显示不下的,借助ScrollView控件的话,我们就可以以滚动的形式查看屏幕外的那部分内容。另外,布局中还放置了一个Button和一个TextView,Button用于发送HTTP请求,TextView用于将服务器返回的数据显示出来。
接着修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView) findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.send_request) {
sendRequestWithHttpURLConnection();
}
}
private void sendRequestWithHttpURLConnection() {
//开启线程来发起网络请求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream in = connection.getInputStream();
//下面对获取到的输入流进行读取
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
showResponse(response.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在这里进行UI操作,将结果显示到界面上
responseText.setText(response);
}
});
}
}
可以看到,我们在Send Request按钮的点击事件里调用了sendRequestWithHttpURLConnection()方法,在这个方法中先是开启了一个子线程,然后在子线程里使用HttpURLConnection发出一条HTTP请求,请求的目标地址就是百度的首页。
接着利用BufferedReader对服务器返回的流进行读取,并将结果传入到了showResponse()方法中。而在showResponse()方法里则是调用了一个runOnUiThread()方法,然后在这个方法的匿名类参数中进行操作,将返回的数据显示到界面上。那么这里为什么要用这个runOnUiThread()方法呢?这是因为Android是不允许在子线程中进行UI操作的,我们需要通过这个方法将线程切换到主线程,然后再更新UI元素。关于这部分内容,我们将会在下一章中进行详细讲解,现在你只需要记得必须这么写就可以了。
完整的一套流程就是这样,不过在开始运行之前,仍然别忘了要声明一下网络权限。修改AndroidManifest.xml中的代码,如下所示:
<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,并在获取输入流之前把要提交的数据写出即可。注意每条数据都要以键值对的形式存在,数据与数据之间用“&”符号隔开,比如说我们想要向服务器提交用户名和密码,就可以这样写:
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");
好了,相信你已经将HttpURLConnection的用法很好地掌握了。
使用OkHttp
当然我们并不是只能使用HttpURLConnection,完全没有任何其他选择,事实上在开源盛行的今天,有许多出色的网络通信库都可以替代原生的HttpURLConnection,而其中OkHttp无疑是做得最出色的一个。
OkHttp是由鼎鼎大名的Square公司开发的,这个公司在开源事业上面贡献良多,除了OkHttp之外,还开发了像Picasso、Retrofit等著名的开源项目。OkHttp不仅在接口封装上面做得简单易用,就连在底层实现上也是自成一派,比起原生的HttpURLConnection,可以说是有过之而无不及,现在已经成了广大Android开发者首选的网络通信库。那么本小节我们就来学习一下OkHttp的用法,OkHttp的项目主页地址是:https://github.com/square/okhttp
在使用OkHttp之前,我们需要先在项目中添加OkHttp库的依赖。编辑app/build.gradle文件,在dependencies闭包中添加如下内容:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.squareup.okhttp3:okhttp:3.4.1'
}
添加上述依赖会自动下载两个库,一个是OkHttp库,一个是Okio库,后者是前者的通信基础。
下面我们来看一下OkHttp的具体用法,首先需要创建一个OkHttpClient的实例,如下所示:
OkHttpClient client = new OkHttpClient();
接下来如果想要发起一条HTTP请求,就需要创建一个Request对象:
Request request = new Request.Builder().build();
当然,上述代码只是创建了一个空的Request对象,并没有什么实际作用,我们可以在最终的build()方法之前连缀很多其他方法来丰富这个Request对象。比如可以通过url()方法来设置目标的网络地址,如下所示:
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
之后调用OkHttpClient的newCall()方法来创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据,写法如下:
Response response = client.newCall(request).execute();
其中Response对象就是服务器返回的数据了,我们可以使用如下写法来得到返回的具体内容:
String responseData = response.body().string();
如果是发起一条POST请求会比GET请求稍微复杂一点,我们需要先构建出一个Request Body对象来存放待提交的参数,如下所示:
RequestBody requestBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build();
然后在Request.Builder中调用一下post()方法,并将RequestBody对象传入:
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
接下来的操作就和GET请求一样了,调用execute()方法来发送请求并获取服务器返回的数据即可。
好了,OkHttp的基本用法就先学到这里,本书中后面所有网络相关的功能我们都将会使用OkHttp来实现,到时候再进行进一步的学习。那么现在我们先把NetworkTest这个项目改用OkHttp的方式再实现一遍吧。
由于布局部分完全不用改动,所以现在直接修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
...
@Override
public void onClick(View v) {
if (v.getId() == R.id.send_request) {
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
showResponse(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
...
}
这里我们并没有做太多的改动,只是添加了一个sendRequestWithOkHttp()方法,并在Send Request按钮的点击事件里去调用这个方法。在这个方法中同样还是先开启了一个子线程,然后在子线程里使用OkHttp发出一条HTTP请求,请求的目标地址还是百度的首页,OkHttp的用法也正如前面所介绍的一样。最后仍然还是调用了showResponse()方法来将服务器返回的数据显示到界面上。
仅仅是改了这么多代码,现在我们就可以重新运行一下程序了。点击Send Request按钮后,你会看到和上一小节中同样的运行结果,由此证明,使用OkHttp来发送HTTP请求的功能也已经成功实现了。
这样的话,相信你就已经把HttpURLConnection和OkHttp的基本用法都掌握得差不多了。
如果对你有帮助,就一键三连呗(关注+点赞+收藏),我会持续更新更多干货~~