第13.2.2讲 使用HttpURLConnection访问网络资源
1 HttpURLConnection简介
1.1 背景
(1.1.1)URL因为其网络通信的基本设置功能过于薄弱,很少使用;
(1.1.2)URLConnect在URL的基础上,进一步封装了网络通讯控制(例如超时)、区分了POST和GET,但是其对Response的解析支持过于薄弱,二次开发工作量大;
(1.1.3)在URLConnect的基础上,需要再进一步封装了,于是就出现了HttpURLConnect;
1.2 HttpURLConnect的新增内容
HttpURLConnectURL是URLConnectURL的一个子类,具有父类的基本属性特征;新增特色部分主要如下: (1.2.1)public Int getResponseCode():获取服务器的响应代码;发时根据响应码来判断后续内容的正确如否;
(1.2.2)public String getResponseMessage():获取服务器的响应消息;
(1.2.3)public Int getContentLength():获得内容的长度;
(1.2.4)public void setRequestMethod(String method):设置发送请求的的方法,参数method的取值为:“POST”或“GET”;
(1.2.5)public String getRequestMethod():获取发送请求的方法。
(1.2.6)public void disconnect():关闭当前的连接。
2 .HttpURLConnection
2.1 构造
(2.1.1)HttpURLConnection继续是一个抽象类,不能直接用new来实例化;
(2.1.2)URL的openConnection()方法将返回一个URLConnection对象,需要人为强制转换为HttpURLConnection类型,该对象表示应用程序和URL之间的通信连接;
2.2 通讯
HttpURLConnection的通讯区继续分GET模式和POST两种模式,每种模式的使用步骤都不相同; 2.2 get模式的 使用步骤
(2.2.1)确定URL的地址,参数组,构建URL。
(2.2.2)通过URL对象的openConnection()方法来创建HttpURLConnection对象。
(2.2.3)设置HttpURLConnection对象的常用的头字段内容和扩展头字段内容,例如超时、缓存、长连接等;
(2.2.4)取消URLConnection的connect()方法的执行,改为检查常用头字段设置中是否完成,和conn.setRequestMethod("GET")的GET模式设定;问:怎么此流程中貌似没有提交函数或者提交的触发函数呢?
(2.2.5)然后进入接收阶段,读取conn.getResponseCode()的返回值是否等于200;
int code=conn.getResponseCode();
if(code==HttpURLConnection.HTTP_OK){
}
(2.2.6)不等于200,则通过conn.getResponseMessage()读取报错信息;
(2.2.7)等于200则表示OK,读者可以根据 conn.getResponseMessage()、conn.getHeaderFields()、conn.getInputStream()按需解析处理;
(2.2.8)处理完成后,建议读者执行conn.disconnect(),释放当前的网络连接资源;
2.3 post 使用步骤
(2.3.1)确定URL的地址,参数组,构建URL。
(2.3.2)通过URL对象的openConnection()方法来创建HttpURLConnection对象。
(2.3.3)设置URLConnection对象的常用的头字段内容和扩展头字段内容,例如超时、缓存、长连接等。
(2.3.4)查常用头字段设置中是否完成和conn.setRequestMethod("POST")的POST模式设定;
(2.3.4)设置HttpURLConnection的输入流和输出流为可用,只有设置启用后才能获取其对应的流。
//代码片段
//设置URLConnection对象在发送POST请求时,连接可读入输入信息。
conn.setDoInput(true)
//设置URLConnection对象在发送POST请求时,连接可以输出信息。
conn.setDoOutput(true);
(2.3.5通过getOutputStream()获得输出流对象,进而发送请求参数;
//生成一个字符打印流
os = new PrintWriter(conn.getOutputStream());
os.print(parms);
os.flush();
(2.3.6)然后进入接收阶段,读取conn.getResponseCode()的返回值是否等于200;
(2.3.6)不等于200,则通过conn.getResponseMessage()读取报错信息;
(2.3.7)等于200则表示OK,读者可以根据 conn.getResponseMessage()、conn.getHeaderFields()、conn.getInputStream()按需解析处理;
(2.3.8)处理完成后,建议读者执行conn.disconnect(),释放当前的网络连接资源;
2.4. GET和POS步骤比较
步骤 | GET | POST |
URL连接串 | 主地址串+?+参数串 | 主地址串 |
打开连接 | (HttpURLConnection) url.openConnection() | (HttpURLConnection) url.openConnection() |
设置头域 | 设置主头域+扩展头域 | 设置主头域+扩展头域 |
检查请求方式 | setRequestMethod("GET") | setRequestMethod("POST") |
检查头域 | -- | 必须启动输入流和输出流 |
输出参数 | -- | 通过输出流格式输出参数串 |
连接服务器 | --(跟URLConnect的比较) | -- |
解析响应码 | getResponseCode() getResponseMessage() | getResponseCode()、 getResponseMessage() |
解析头域 | getHeaderFields() | getHeaderFields() |
解析输入流 | getInputStream() | getInputStream() |
释放资源 | 释放流资源、网络连接资源 | 释放流资源、网络连接资源 |
3. 案例实训
演示(Fragment版)
3.1 需求
需求1
在MainActivity部署一个输入框用于输入网址的主地址、一个输入框用于输入网址后面的参数串;
需求2
在MainActivity内部署3个按钮,一个用于get模式的测试、一个用于POST模式的测试、一个用于恢复默认的网址便于测试者不知道主地址和参数;
需求3
在MainActivity内部署1个只读的文本编辑框(因为文本编辑框具有滚动条),用于显示基本的头字段域内容和ResponseMessage的内容;
需求4
输入不同的网址和网址后缀的参数串,分别点击GET模式和POST模式的按钮,观察返回结果的变化;
3.2 步骤1 准备主布局、Demo13020201Fragment类、默认的网址
内容省略 3.3 步骤2 准备Button的事件
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView= inflater.inflate(R.layout.fragment_demo13020201,
container,false);
initViewInRootView(rootView);
return rootView;
}
/***
* 初始化Fragment内部的组件
*/
private void initViewInRootView(View rootView){
//找到第1组按钮
mButton1=(Button)rootView.findViewById(R.id.button1);
mButton2=(Button)rootView.findViewById(R.id.button2);
mButton3=(Button)rootView.findViewById(R.id.button3);
//URL主地址和参数输入框
mEditText1=(EditText)rootView.findViewById(R.id.editText1);
mEditText2=(EditText)rootView.findViewById(R.id.editText2);
//内容提示框
mTextView=(TextView)rootView.findViewById(R.id.textView1);
mEditText3=(EditText)rootView.findViewById(R.id.editText3);
//设置按钮的点击事件侦听器
mButton1.setOnClickListener(mButtonOnClickListener);
mButton2.setOnClickListener(mButtonOnClickListener);
mButton3.setOnClickListener(mButtonOnClickListener);
}
//按钮的点击事件侦听器
private View.OnClickListener mButtonOnClickListener=new View.OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()){
case R.id.button1:
demoByHttpGet();
break;
case R.id.button2:
demoByHttpPost();
break;
case R.id.button3:
setDefaultURL();
break;
}
}
};
3.4 步骤3 准备处理demoByHttpGet函数的处理内容
(3.4.1)生成一个匿名线程,重写run()。
(3.4.2)根据输入框的主地址和参数合并成并生成URL。
(3.4.3)通过 url.openConnection()获取一个连接,强制转换成HttpURLConnection后放到变量conn中。
(3.4.4)设置conn的设置通用的请求属性和设置通用的扩展 请求属性。
(3.4.5)检查conn.setRequestMethod("GET");
(3.4.6) 读取conn.getResponseCode()的返回值,和gconn.etResponseMessage()的值;
(3.4.7) 获取getHeaderFields中的键值对用于显示;
(3.4.8)准备 Handler和对应的处理函数;
(3.4.9)根据ResponseCode的不同,发送不同Handler消息;
(3.4.10)处理流关闭善后工作;
//HttpGet的演示
private void demoByHttpGet(){
//如果当前已经有线程在运行,则不要继续
if(mThreadIsRun)
return;
mThreadIsRun=true;
mTextView.setText("等待:demoByHttpGet()");
new Thread() {
public void run() {
InputStream is=null;
try {
//------生成连接
// 定义一个URL对象
String urlStr=mEditText1.getText().toString()+"?"+mEditText2.getText().toString();
URL url = new URL(urlStr);
// 打开和URL之间的连接,并把强制转换为HttpURLConnection
HttpURLConnection conn= (HttpURLConnection) url.openConnection();
//-----设置通用的请求属性
//连接可读入输入信息
//conn.setDoInput(true);
//设置不使用缓存。
conn.setUseCaches(false);
//设置连接网络超时时间,以毫秒为单位;
conn.setConnectTimeout(20*1000);
//设置流读的最长超时时间
conn.setReadTimeout(10 * 1000);
//-----设置通用的扩展 请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
//设置请求模式
conn.setRequestMethod("GET");
//---------处理服务器端的响应
int code=conn.getResponseCode();
mResultText="ResponseCode="+code+"\r\n"+conn.getResponseMessage()+"\r\n";
if(code==HttpURLConnection.HTTP_OK){
mResultText+="-------HeaderFields-------";
Map> map = conn.getHeaderFields();
//显示一下返回的头字段,大家可以学习一下都有哪些扩展属性
for (String key : map.keySet())
{
mResultText+= key + " --> " + map.get(key)+"\r\n";
}
// 发送消息、通知UI组件显示该内容
mImageViewHandler.sendEmptyMessage(0x123);
}else{
// 发送消息、通知UI组件显示该内容
mImageViewHandler.sendEmptyMessage(0x124);
}
}catch (Exception e){
mResultText="发生异常"+e.getMessage();
mImageViewHandler.sendEmptyMessage(0x124);
e.printStackTrace();
}finally {
try {
if(is !=null)
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//设置线程运行结束
mThreadIsRun=false;
}
}
}.start();
}
//添加HTTP完成后的Handler
Handler mImageViewHandler =new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
if(msg.what == 0x123){
mThreadIsRun=false;
mTextView.setText("HTTP调用成功,返回如下:");
mEditText3.setText(mResultText);
}
else if(msg.what == 0x124){
mThreadIsRun=false;
mTextView.setText("HTTP调用异常,原因如下:");
mEditText3.setText(mResultText);
}
super.handleMessage(msg);
}
};
3.5 步骤4 准备处理demoByPOST函数的处理内容
(3.5.1)生成一个匿名线程,重写run()。
(3.5.2)从输入框提取主网址,利用地址构造一个URL。
(3.5.3)通过 url.openConnection()获取一个连接,强制转换成HttpURLConnection后放到变量conn中。
(3.5.4)设置conn的设置通用的请求属性和设置通用的扩展 请求属性。
(3.5.5)检查conn.setRequestMethod("POST");
(3.5.6)检查通用头字段属性中的输入流和输出流是否启动;
(3.5.7)获取conn的输出流,并用输出流构建一个格式输出字符流读写器保存到变量os中;
(3.5.8)利用os输出URL的参数部分,并提交;
(3.5.9) 读取conn.getResponseCode()的返回值,和gconn.etResponseMessage()的值;
(3.5.10) 获取getHeaderFields中的键值对用于显示;
(3.5.11)根据ResponseCode的不同,发送不同Handler消息;
(3.5.12)处理流关闭善后工作;
//HttpPost的演示
private void demoByHttpPost(){
//如果当前已经有线程在运行,则不要继续
if(mThreadIsRun)
return;
//如果当前已经有线程在运行,则不要继续
if(mThreadIsRun)
return;
mThreadIsRun=true;
mTextView.setText("等待:demoByHttpGet()");
new Thread() {
public void run() {
InputStream is=null;
BufferedReader br=null;
PrintWriter os=null;
try {
//------生成连接
// 定义一个URL对象
String urlStr=mEditText1.getText().toString();
URL url = new URL(urlStr);
// 打开和URL之间的连接,并把强制转换为HttpURLConnection
HttpURLConnection conn= (HttpURLConnection) url.openConnection();
//-----设置通用的请求属性
//连接可读入输入信息
conn.setDoInput(true);
conn.setDoOutput(true);
//设置不使用缓存。
conn.setUseCaches(false);
//设置连接网络超时时间,以毫秒为单位;
conn.setConnectTimeout(20*1000);
//设置流读的最长超时时间
conn.setReadTimeout(10 * 1000);
//-----设置通用的扩展 请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
//设置请求模式
conn.setRequestMethod("POST");
//-----发送参数
// 获取URLConnection对象对应的输出流
os = new PrintWriter(conn.getOutputStream());
// 发送请求参数
String parmStr=mEditText2.getText().toString();
os.print(parmStr);
//flush输出流的缓冲
os.flush();
//---------处理服务器端的响应
int code=conn.getResponseCode();
mResultText="ResponseCode="+code+"\r\n"+conn.getResponseMessage()+"\r\n";
if(code==HttpURLConnection.HTTP_OK){
mResultText+="-------HeaderFields-------";
Map> map = conn.getHeaderFields();
//显示一下返回的头字段,大家可以学习一下都有哪些扩展属性
for (String key : map.keySet())
{
mResultText+= key + " --> " + map.get(key)+"\r\n";
}
// 发送消息、通知UI组件显示该内容
mImageViewHandler.sendEmptyMessage(0x123);
}else{
// 发送消息、通知UI组件显示该内容
mImageViewHandler.sendEmptyMessage(0x124);
}
}catch (Exception e){
mResultText="发生异常"+e.getMessage();
mImageViewHandler.sendEmptyMessage(0x124);
e.printStackTrace();
}finally {
try {
if(is !=null)
is.close();
if(os !=null)
os.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//设置线程运行结束
mThreadIsRun=false;
}
}
}.start();
}
3.6 步骤5 加入权限
关于主机服务器回应的输入流和编码,本例不继续测试,兴趣的同学自己参照上一节内容自己练习; 3.7 步骤6 加入权限
<!-- 互联网权限 -->
<uses-permission android:name="android.permission.INTERNET" />
3.8 步骤7测试 网络正常时OK,网络异常时能提示错误消息