Http协议工作原理
简单来说,就是客户端向服务器发出一条HTTP 请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。一个浏览器的基本工作原理也不过如此,当时当我们需要访问网络很多时候还是需要用到Http协议,你不可能去写一个浏览器出来吧?
一、使用HttpURLConnection
在 Android 上发送 HTTP 请求的方式一般有两种,HttpURLConnection 和 HttpClient,我们先来学习一下 HttpURLConnection 的用法。
首先需要获取到 HttpURLConnection 的实例,一般只需 new 出一个 URL 对象,并传入目标的网络地址,然后调用一下 openConnection()方法即可,如下所示:
URL url = new URL(“http://blog.youkuaiyun.com/turodog“);
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 的用法。
代码:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<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 控件就可以允许我们以滚动的形式查看屏幕外的那部分内容。另外,布局中还放置了一个 Button和一个 TextView, Button 用于发送 HTTP 请求, TextView 用于将服务器返回的数据显示出来。
MainActivity
package com.turo.networktest;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
public static final int SHOW_RESPONSE = 0;
private Button sendRequest;
private TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView) findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.send_request) {
sendRequestWithHttpURLConnection();
}
}
private void sendRequestWithHttpURLConnection() {
// 开启线程来发起网络请求
new Thread(new Runnable() {
@Override
public void run() {
//第一步:HttpURLConnection 的实例,new 出一个 URL 对象,并传入目标的网络地址,然后调用openConnection()方法
HttpURLConnection connection = null;
try {
URL url = new URL("http://blog.youkuaiyun.com/turodog");
connection = (HttpURLConnection) url.openConnection();
//第二步:设置HTTP请求所使用的方法。这里GET方式表示希望从服务器那里获取数据
connection.setRequestMethod("GET");
//第三步:设置连接超时、读取超时的毫秒数
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
//第四步:调用 getInputStream()方法获取到服务器返回的输入流
InputStream in = connection.getInputStream();
// 下面对获取到的输入流进行读取
BufferedReader reader = new BufferedReader(new
InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
Message message = new Message();
message.what = SHOW_RESPONSE;
// 将服务器返回的结果存放到Message中
message.obj = response.toString();
handler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
//最后调用 disconnect()方法将这个HTTP连接关闭掉
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_RESPONSE:
String response = (String) msg.obj;
// 在这里进行UI操作,将结果显示到界面上
responseText.setText(response);
}
}
};
}
可以看到,我们在 Send Request 按钮的点击事件里调用了 sendRequestWithHttpURLConnection()方法,在这个方法中先是开启了一个子线程(访问网络都需要开启子线程),然后在子线程里使用 HttpURLConnection发出一条 HTTP 请求,请求的目标地址就是我的博客的首页。接着利用BufferedReader 对服务器返回的流进行读取,并将结果存放到了一个 Message 对象中。这里为什么要使用 Message 对象呢?当然是因为子线程中无法对 UI 进行操作了。我们希望可以将服务器返回的内容显示到界面上,所以就创建了一个 Message 对象,并使用 Handler 将它发送出去。之后又在 Handler的 handleMessage()方法中对这条 Message 进行处理,最终取出结果并设置到 TextView 上
最后千万次声明,凡是要用到网络,一定要在Androidmanifest.xml中加上访问网络的权限:
<uses-permission android:name="android.permission.INTERNET" />
效果图:
是的,你没看错,服务器返回给我们的就是这种 HTML 代码,只是通常情况下浏览器都会将这些代码解析成漂亮的网页后再展示出来。
那么如果是想要提交数据给服务器应该怎么办呢?其实也不复杂,只需要将 HTTP 请求的方法改成 POST,并在获取输入流之前把要提交的数据写出即可。注意每条数据都要以键值对的形式存在,数据与数据之间用&符号隔开,比如说我们想要向服务器提交用户名和密码,就可以这样写:
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");
二、使用HttpClient
HttpClient 是 Apache 提供的 HTTP 网络访问接口,从一开始的时候就被引入到了 AndroidAPI 中。它可以完成和 HttpURLConnection 几乎一模一样的效果,但两者之间的用法却有较大的差别,那么我们自然要看一下 HttpClient 是如何使用的了。
首先你需要知道, HttpClient 是一个接口,因此无法创建它的实例,通常情况下都会创建一个DefaultHttpClient 的实例,如下所示:
HttpClient httpClient = new DefaultHttpClient();
接下来如果想要发起一条 GET 请求,就可以创建一个 HttpGet 对象,并传入目标的网络地址,然后调用 HttpClient 的 execute()方法即可:
HttpGet httpGet = new HttpGet("http://blog.youkuaiyun.com/turodog");
httpClient.execute(httpGet);
如果是发起一条 POST 请求会比 GET 稍微复杂一点,我们需要创建一个 HttpPost 对象,并传入目标的网络地址,如下所示:
HttpPost httpPost = new HttpPost(“http://blog.youkuaiyun.com/turodog“);
然后通过一个 NameValuePair 集合来存放待提交的参数,并将这个参数集合传入到一个UrlEncodedFormEntity 中,然后调用 HttpPost 的 setEntity()方法将构建好的 UrlEncodedFormEntity传入,如下所示:
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("username", "admin"));
params.add(new BasicNameValuePair("password", "123456"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "utf-8");
httpPost.setEntity(entity);
接下来的操作就和 HttpGet 一样了,调用 HttpClient 的 execute()方法,并将 HttpPost 对象传入即可:
httpClient.execute(httpPost);
执行 execute()方法之后会返回一个 HttpResponse 对象,服务器所返回的所有信息就会包含在这里面。通常情况下我们都会先取出服务器返回的状态码,如果等于 200 就说明请求和响应都成功了,如下所示:
if (httpResponse.getStatusLine().getStatusCode() == 200) {
// 请求和响应都成功了
}
接下来在这个 if 判断的内部取出服务返回的具体内容,可以调用 getEntity()方法获取到一个 HttpEntity 实例,然后再用EntityUtils.toString()这个静态方法将 HttpEntity 转换成字符串即可,如下所示:
HttpEntity entity = httpResponse.getEntity();
String response = EntityUtils.toString(entity);
注意如果服务器返回的数据是带有中文的,直接调用EntityUtils.toString()方法进行转换会有乱码的情况出现,这个时候只需要在转换的时候将字符集指定成 utf-8 就可以了,如下所示:
String response = EntityUtils.toString(entity, “utf-8”);
好了,基本的用法就是如此,接下来就让我们把 NetworkTest 这个项目改用 HttpClient的方式再实现一遍吧。
布局不变,下面看一下MainActivity
@Override
public void onClick(View view) {
if (view.getId() == R.id.send_request) {
//sendRequestWithHttpURLConnection();
sendRequestWithHttpClient();
}
}
private void sendRequestWithHttpClient() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//第一步:HttpClient 是一个接口,因此无法创建它的实例,通常情况下都会创建一个 DefaultHttpClient 的实例
HttpClient httpClient = new DefaultHttpClient();
//第二步:创建一个 HttpGet 对象,并传入目标的网络地址,然后调用 HttpClient 的 execute()方法
HttpGet httpGet = new HttpGet("http://blog.youkuaiyun.com/turodog");
HttpResponse httpResponse = httpClient.execute(httpGet);
//第三步:执行 execute()方法之后会返回一个 HttpResponse 对象,服务器所返回的所有信息就会包含在这里面。
if (httpResponse.getStatusLine().getStatusCode() == 200) {
// 200代表请求和响应都成功了
//第四步:调用 getEntity()方法获取到一个 HttpEntity 实例,
// 然后再用 EntityUtils.toString()这个静态方法将 HttpEntity 转换成字符串
//如果返回数据有中文还要加上utf_8,否则会出现乱码
HttpEntity entity = httpResponse.getEntity();
String response = EntityUtils.toString(entity,
"utf-8");
Message message = new Message();
message.what = SHOW_RESPONSE;
// 将服务器返回的结果存放到Message中
message.obj = response.toString();
handler.sendMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
基本上什么都没改变,只是按钮监听事件改成触发sendRequestWithHttpClient()方法。效果是一样的。