第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,网络异常时能提示错误消息