Android实习生 —— 网络请求及数据解析
目录
前言 1、http协议定义 2、客户端连接服务器实现内部的原理 3、Http请求方式、区别 4、HTTP返回请求数据的三种方式:一、Android中的网络请求方式 1、在Android上发送HTTP请求的方式 2、关于HttpClient的废除 3、HttpURLConnection、HttpCient介绍。(HttpCient废除之前) 4、HttpURLConnection和HttpCient区别(HttpURLConnection优势) 5、Android配置网络权限二、HttpURLConnection(Demo) 1、Get请求的实现(Demo) 2、Post请求实现(Demo)三、HttpCient(Demo) 1、关于 HttpCient废除 2、HttpCient介绍 3、HttpCient实现步骤 4、HttpCient实现实例(GET)(Demo) 5、HttpCient实现实例(POST)(Demo)四、HttpURLConnection抽象请求方法(Demo) 1、JQuery 2、定义接口HttpCallbackListener,为了实现回调 3、创建HttpTool类,抽象请求方法(GET) 4、调用示例 5、抽象请求方法(POST)五、文件下载(Demo) 1、DownLoadManager简介 2、功能实现六、JSON数据解析(Demo) 1、解析单条Json数据 2、解析多条Json数据七、图片数据解析(Demo)【附录】 Demo
前言
大部分andriod应用需要与服务器进行数据交互,HTTP、FTP、SMTP或者是直接基于SOCKET编程都可以进行数据交互,但是HTTP必然是使用最广泛的协议。
在总结之前先来了解一下Http协议,也是对技术支持的一些补充。
1、http协议定义
HTTP协议是基于TCP/IP协议之上的协议,是客户端和服务器之间的应用层的协议,是通用的、无状态的面向对象的协议。
2、客户端连接服务器实现内部的原理
分析上图,步骤如下:
第一步:在浏览器客户端中得到用户输入的内容。
第二步:浏览器得到这个网址之后,内部会将这个域名发送到DNS上,进行域名解析。得到它的IP之后就会链接到指定的服务器上,假如服务器的地址是:221.104.13.32:80,从浏览器到服务器端口它使用到最底层的TCP/IP协议。
第三步:实现TCP/IP协议用Socket来完成,使用了Socket的套接字。
第四步:服务器端的80端口监听客户端的链接,这样客户端到服务器就链接上了。
通俗一点讲,用户在浏览器输入网址,通过http协议发出去,网址经过DNS域名解析,解析成指定的ip地址,并在80端口上监听用户的请求。服务器监听到请求之后,会以三种方式返回给客户端:HTML、XML、JASON。
3、Http请求方式、区别
根据HTTP标准,HTTP请求可以使用多种请求方法。例如:HTTP1.1支持7种请求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。在Internet应用中,最常用的方法是GET和POST。
区别:
在客户端,GET方式在通过URL提交数据,数据在URL中可以看到;POST方式,数据放在HTML HEADER内提交。
对于GET方式,服务器端用Request.QueryString获取变量的值,对于POST方式,服务器用Request.Form获取提交的数据。
GET方式提交的数据不能大于2KB(主要是URL长度限制),而POST则没有此限制。
安全性问题。使用GET的时候,参数会显示在地址栏上,而POST不会。所以,如果这些数据是中文数据而且是非敏感数据,那么使用GET;如果用户输入的数据不是中文字符而且包含敏感数据,那么还是使用POST为好。
4、HTTP返回请求数据的三种方式:
- 以HTML代码内容返回。
- 以XML字符串的形式返回,在以后的android开发中这种形式返回数据比较多。
- 以JSON对象形式返回,在网络流量上考虑JSON要比XML方式要好一些,便于解析。
在Android当中,一般使用xml和Json数据解析。
一、Android中的网络请求方式
Android中的WebView控件已经在后台帮我们处理好了发送HTTP请求、接收服务响应、解析返回数据,以及最终的页面展示这几步工作,不过由于它封装得太好了,反而不能直观地看出HTTP协议是如何工作的。因此接下来我们通过手动发送HTTP请求的方式,来更加深入的了解这一过程。
1、在Android上发送HTTP请求的方式
一般有两种:HttpURLConnection、HttpCient
2、关于HttpClient的废除
- 在android 6.0(api 23) SDK,不再提供org.apache.http.*(只保留几个类),HttpClient相关类移除,推荐使用HTTPURLConnection。
- 废除原因:之前一直使用HttClient是由于HttpURLConnection不稳定导致,那么现在谷歌虽然修复了HttpURLConnection之前存在的一些问题。
- 若还需使用该类,点击查看解决办法。
3、HttpURLConnection、HttpCient介绍。(HttpCient废除之前)
HttpClient是apache的开源框架,封装了访问http的请求头,参数,内容体,响应等等,使用起来比较方便,而HttpURLConnection是java的标准类,什么都没封装,用起来太原始,不方便,比如重访问的自定义,以及一些高级功能等。
从稳定性方面来说的话,HttpClient很稳定,功能强,BUG少,容易控制细节,而之前的HttpURLConnection一直存在着版本兼容的问题,不过在后续的版本中已经相继修复掉了。
4、HttpURLConnection和HttpCient区别(HttpURLConnection优势)
1、HttpUrlConnection是Android SDK的标准实现,而HttpClient是apache的开源实现;
2、HttpUrlConnection直接支持GZIP压缩;HttpClient也支持,但要自己写代码处理;
3、HttpUrlConnection直接在系统层面做了缓存策略处理,加快重复请求的速度。
4、HttpUrlConnection直接支持系统级连接池,即打开的连接不会直接关闭,在一段时间内所有程序可共用;HttpClient当然也能做到,但毕竟不如官方直接系统底层支持好;
5、Android配置网络权限
因为需要访问网络,需在AndroidManifest.xml中添加如下权限
二、HttpURLConnection(Demo)
1、Get请求实现(Demo)
由于网络请求可能造成耗时操作(网络环境差的情况下)对ui线程的阻塞,我们开启子线程去操作网络请求。
【关于UI线程,请看《Android实习生 —— 异步处理之Handler》】
private void connectWithHttpURLConnection() { new Thread( new Runnable() { @Override public void run() { Message msg =new Message(); HttpURLConnection connection = null; try { // 调用URL对象的openConnection方法获取HttpURLConnection的实例 URL url = new URL("http://www.baidu.com"); connection = (HttpURLConnection) url.openConnection(); // 设置请求方式,GET或POST connection.setRequestMethod("GET"); // 设置连接超时、读取超时的时间,单位为毫秒(ms) connection.setConnectTimeout(8000); connection.setReadTimeout(8000); // 设置是否使用缓存 默认是true connection.setUseCaches(true); //设置请求头里面的属性 //connection.setRequestProperty(); // 开始连接 Log.i("HttpURLConnection.GET","开始连接"); connection.connect(); if (connection.getResponseCode() == 200) { Log.i("HttpURLConnection.GET", "请求成功"); InputStream in = connection.getInputStream(); // 使用BufferedReader对象读取返回的数据流 // 按行读取,存储在StringBuider对象response中 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } // 此处省略处理数据的代码,通过handler直接将返回的结果消息发送给UI线程列队 Bundle bundle =new Bundle(); bundle.putString("data", String.valueOf(response)); msg.setData(bundle); handler.sendMessage(msg); }else{ Log.i("HttpURLConnection.GET", "请求失败"); } } catch (Exception e){ e.printStackTrace(); } finally { if (connection != null){ // 结束后,关闭连接 connection.disconnect(); } } } }).start(); }
-
效果
Get请求效果 -
打印消息
打印消息
2、Post请求实现(Demo)
正如前言中第3条所述,在客户端,GET方式在通过URL提交数据,数据在URL中可以看到;POST方式,数据放在HTML HEADER内提交。
在Get方法中请求参数可以直接写到地址栏中,如:
//用“&”隔开不同参数 String path = "https://reg.163.com/logins.jsp?id=helloworld&pwd=android";
但在Post方式中要把“请求的参数”转换为byte数组,然后通过DataOutputStream(urlConn.getOutputStream())把参数写入。
private void connectWithHttpURLConnectionPOST() { new Thread(new Runnable() { @Override public void run() { Message msg = new Message(); String path = "https://reg.163.com/logins.jsp"; // 请求的参数转换为byte数组 String params = null; HttpURLConnection urlConn = null; try { params = "id=" + URLEncoder.encode("helloworld", "UTF-8") + "&pwd=" + URLEncoder.encode("android", "UTF-8"); byte[] postData = params.getBytes(); // 新建一个URL对象 URL url = new URL(path); // 打开一个HttpURLConnection连接 urlConn = (HttpURLConnection) url.openConnection(); // 设置连接超时时间 urlConn.setConnectTimeout(8 * 1000); // Post请求必须设置允许输出 urlConn.setDoOutput(true); // Post请求不能使用缓存 urlConn.setUseCaches(false); // 设置为Post请求 urlConn.setRequestMethod("POST"); urlConn.setInstanceFollowRedirects(true); // 配置请求Content-Type urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencode"); // 开始连接 urlConn.connect(); Log.i("HttpURLConnection.POST", "开始连接"); // 发送请求参数 DataOutputStream dos = new DataOutputStream(urlConn.getOutputStream()); dos.write(postData); dos.flush(); dos.close(); // 判断请求是否成功 if (urlConn.getResponseCode() == 200) { // 获取返回的数据 InputStream in = urlConn.getInputStream(); // 使用BufferedReader对象读取返回的数据流 // 按行读取,存储在StringBuider对象response中 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } Log.i("HttpURLConnection.POST", "请求成功"); // 此处省略处理数据的代码,直接将返回的结果消息发送给UI线程列队 Bundle bundle = new Bundle(); bundle.putString("data", String.valueOf(response)); msg.setData(bundle); handler.sendMessage(msg); } else { Log.i("HttpURLConnection.POST", "请求失败"); } } catch (Exception e) { e.printStackTrace(); } finally { if (urlConn != null) { // 结束后,关闭连接 urlConn.disconnect(); } } } }).start(); }
-
效果
Post请求效果
三、HttpCient(Demo)
1、关于 HttpCient废除
- 在android 6.0(api 23) SDK,不再提供org.apache.http.*(只保留几个类),HttpClient相关类移除,推荐使用HTTPURLConnection。
- 废除原因:之前一直使用HttClient是由于HttpURLConnection不稳定导致,那么现在谷歌虽然修复了HttpURLConnection之前存在的一些问题。
- 若还需使用该类,点击查看解决办法。
2、HttpCient介绍
HttpClient其实是一个interface类型,HttpClient封装了对象需要执行的Http请求、身份验证、连接管理和其它特性。既然HttpClient是一个接口,因此无法创建它的实例。从文档上看,HttpClient有三个已知的实现类分别是:AbstractHttpClient, AndroidHttpClient, DefaultHttpClient,会发现有一个专门为Android应用准备的实现类AndroidHttpClient,当然使用常规的DefaultHttpClient也可以实现功能。
3、HttpCient实现步骤
(i)创建代表客户端的HttpClient对象。
(ii)创建代表请求的对象,如果需要发送GET请求,则创建HttpGet对象,如果需要发送POST请求,则创建HttpPost对象。
【对于发送请求的参数,GET和POST使用的方式不同,GET方式可以使用拼接字符串的方式,把参数拼接在URL结尾;POST方式需要使用setEntity(HttpEntity entity)方法来设置请求参数。】
(iii)调用HttpClient对象的execute(HttpUriRequest request)发送请求,执行该方法后,将获得服务器返回的HttpResponse对象。服务器发还给我们的数据就在这个HttpResponse相应当中。调用HttpResponse的对应方法获取服务器的响应头、响应内容等。
(iv)检查相应状态是否正常。服务器发给客户端的相应,有一个相应码:相应码为200,正常;相应码为404,客户端错误;相应码为505,服务器端错误。
(v)获得相应对象当中的数据。
4、HttpCient实现实例(GET)(Demo)
private void sendRequestWithHttpClient() { new Thread(new Runnable() { @Override public void run() { //用HttpClient发送请求,分为五步 //第一步:创建HttpClient对象 HttpClient httpCient = new DefaultHttpClient(); //第二步:创建代表请求的对象,参数是访问的服务器地址 HttpGet httpGet = new HttpGet("http://www.baidu.com"); try { //第三步:执行请求,获取服务器发还的相应对象 HttpResponse httpResponse = httpCient.execute(httpGet); //第四步:检查相应的状态是否正常:检查状态码的值是200表示正常 if (httpResponse.getStatusLine().getStatusCode() == 200) { //第五步:从相应对象当中取出数据,放到entity当中 HttpEntity entity = httpResponse.getEntity(); String response = EntityUtils.toString(entity, "utf-8");//将entity当中的数据转换为字符串 //在子线程中将Message对象发出去 Message message = new Message(); message.what = SHOW_RESPONSE; message.obj = response.toString(); handler.sendMessage(message); } } catch (Exception e) { e.printStackTrace(); } } }).start(); }
-
效果
Get请求效果
5、HttpCient实现实例(POST)(Demo)
private void sendRequestWithHttpClientPOST() { new Thread(new Runnable() { @Override public void run() { //用HttpClient发送请求,分为五步 //第一步:创建HttpClient对象 HttpClient httpClient = new DefaultHttpClient(); //第二步:创建代表请求的对象,参数是访问的服务器地址 HttpPost httpPost= new HttpPost("https://reg.163.com/logins.jsp"); // 使用NameValuePair(键值对)存放参数 List data = new ArrayList(); // 添加键值对 data.add(new BasicNameValuePair("id", "helloworld")); data.add(new BasicNameValuePair("pwd", "android")); try { // 使用setEntity方法传入编码后的参数 httpPost.setEntity(new UrlEncodedFormEntity(data, "utf-8")); //第三步:执行请求,获取服务器发还的相应对象 HttpResponse httpResponse = httpClient.execute(httpPost); //第四步:检查相应的状态是否正常:检查状态码的值是200表示正常 if (httpResponse.getStatusLine().getStatusCode() == 200) { Log.i("mm","success"); //第五步:从相应对象当中取出数据,放到entity当中 HttpEntity entity = httpResponse.getEntity(); String response = EntityUtils.toString(entity, "utf-8");//将entity当中的数据转换为字符串 //在子线程中将Message对象发出去 Message message = new Message(); message.what = 1; message.obj = response.toString(); handler.sendMessage(message); }else{ Log.i("mm","fail"); } } catch (Exception e) { e.printStackTrace(); } } }).start(); }
-
效果
POST效果
四、HttpURLConnection抽象请求方法(Demo)
1、JQuery
如果你使用过JQuery(一个javasript库),你一定对JQuery的网路编程印象深刻,比如一个HTTP请求只需以下几行代码。
// JQuery的post方法$.post("http://www.cnblogs.com/gzdaijie",{ "stu_no":12345, "stu_name":"Tom", }).done(function(){ //...请求成功的代码 }).fail(function(){ //...请求失败的代码 }).always(function(){ //...总会执行的代码 })
我们当然不希望每次网络请求都写下“二”中那么繁琐的代码,那么android的HTTP请求能否像JQuery那么简单呢?当然可以!下面的代码实现了HttpURLConnection的HTTP请求方法封装
2、定义接口HttpCallbackListener,为了实现回调
// 定义HttpCallbackListener接口// 包含两个方法,成功和失败的回调函数定义public interface HttpCallbackListener { void onFinish(String response); void onError(Exception e);}
3、创建HttpTool类,抽象请求方法(GET)
public class HttpTool { public static void sendRequest(final String address, final HttpCallbackListener listener) { new Thread(new Runnable() { @Override public void run() { HttpURLConnection connection = null; try { // 调用URL对象的openConnection方法获取HttpURLConnection的实例 URL url = new URL(address); connection = (HttpURLConnection) url.openConnection(); // 设置请求方式,GET或POST connection.setRequestMethod("GET"); // 设置连接超时、读取超时的时间,单位为毫秒(ms) connection.setConnectTimeout(8000); connection.setReadTimeout(8000); // 设置是否使用缓存 默认是true connection.setUseCaches(true); //设置请求头里面的属性 //connection.setRequestProperty(); // 开始连接 Log.i("HttpURLConnection.GET","开始连接"); connection.connect(); if (connection.getResponseCode() == 200) { Log.i("HttpURLConnection.GET", "请求chenggong"); 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); } if (listener != null) { // 回调方法 onFinish() listener.onFinish(response.toString()); } } else { Log.i("HttpURLConnection.GET", "请求失败"); } } catch (Exception e) { e.printStackTrace(); if (listener != null) { // 回调方法 onError() listener.onError(e); } } finally { if (connection != null) { connection.disconnect(); } } } }).start(); }}
4、调用示例
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //使用该HttpTool发起GET请求 String url = "http://www.jianshu.com"; HttpTool.sendRequest(url,new HttpCallbackListener(){ @Override public void onFinish(String response) { // ...省略对返回结果的处理代码 Message message = new Message(); message.what = 0; message.obj = response.toString(); handler.sendMessage(message); } @Override public void onError(Exception e) { // ...省略请求失败的处理代码 } }); } });
-
效果
5、抽象请求方法(POST)
/* 在GET方法实现的基础上增加一个参数params即可, * 将参数转换为字符串后传入 * 也可以传入键值对集合,再处理 */public static void sendRequest(final String address, final String params, final HttpCallbackListener listener){ //...}
五、文件下载(Demo)
1、DownLoadManager简介
虽然我们可以通过HTTP请求的方式下载文件,在api level 9之后,android系统为我们提供了DownLoadManager类,这是android提供的系统服务,我们通过这个服务完成文件下载。整个下载过程全部交给系统负责,不需要我们过多的处理。
其包含两个内部类:
DownLoadManager.Query:主要用于查询下载信息。DownLoadManager.Request:主要用于发起一个下载请求。
2、功能实现
//使用系统下载器下载 private void downloadAPK(String versionUrl, String versionName) { //创建下载任务 DownloadManager.Request request = new DownloadManager.Request(Uri.parse(versionUrl)); request.setAllowedOverRoaming(false);//漫游网络是否可以下载 request.setTitle("jar包下载");//下载是通知栏标题// request.setAllowedNetworkTypes() 设置制定网络下下载,传入系统常量值。// 提供的网络常量有:NETWORK_BLUETOOTH、NETWORK_MOBILE、NETWORK_WIFI。 //设置文件类型,可以在下载结束后自动打开该文件 MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); String mimeString = mimeTypeMap.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(versionUrl)); request.setMimeType(mimeString); //在通知栏中显示,默认就是显示的 request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); request.setVisibleInDownloadsUi(true); //sdcard的目录下的download文件夹,必须设置 request.setDestinationInExternalPublicDir("/download/", versionName); //request.setDestinationInExternalFilesDir(),也可以自己制定下载路径 //将下载请求加入下载队列 downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); //加入下载队列后会给该任务返回一个long型的id, //通过该id可以取消任务,重启任务等等,看上面源码中框起来的方法 mTaskId = downloadManager.enqueue(request); //注册广播接收者,监听下载状态 registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); }
广播接收器
private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { checkDownloadStatus();//检查下载状态 } };
检查下载状态
private void checkDownloadStatus() { DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(mTaskId);//筛选下载任务,传入任务ID,可变参数 Cursor c = downloadManager.query(query); if (c.moveToFirst()) { int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); switch (status) { case DownloadManager.STATUS_SUCCESSFUL: Toast.makeText(this,"下载完成",Toast.LENGTH_SHORT).show(); break; case DownloadManager.STATUS_FAILED: Toast.makeText(this,"下载失败",Toast.LENGTH_SHORT).show(); break; } } }
-
效果
下载效果 -
文件下载路径
文件下载路径
六、JSON数据解析(Demo)
1、解析单条Json数据
- 单条数据
//单条数据 private String parseItemJSONWithJSONObject(String jsonData) { String status=null; String message=null; try { //第一步:将从网络字符串jsonData字符串装入JSONObject JSONObject jsonObject = new JSONObject(jsonData); //第二步:因为单条数据,所以用jsonObject.getString方法直接取出对应键值 status = jsonObject.getString("status"); message = jsonObject.getString("message"); } catch (Exception e) { e.printStackTrace(); } return "status: "+ status+"\n"+"message: " + message; }
2、解析多条Json数据
- 多条数据
//多条数据 private String parseJSONWithJSONObject(String jsonData) { StringBuffer sb =new StringBuffer(); try { //第一步:将从网络字符串jsonData字符串装入JSONObject,即JSONObject JSONObject jsonObject = new JSONObject(jsonData); //第二步:因为多条数据,所以将"取出来的、要遍历的"字段装入JSONArray(这里要遍历data字段) JSONArray jsonArray = jsonObject.getJSONArray("data"); //第三步:循环遍历,依次取出JSONObject对象 for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject2 = jsonArray.getJSONObject(i); String time = jsonObject2.getString("time"); String ftime = jsonObject2.getString("ftime"); String context = jsonObject2.getString("context"); sb.append("time: " + time+" "+"ftime: " + ftime+"\n"+"context: " + context+"\n\n"); } } catch (Exception e) { e.printStackTrace(); } return sb.toString(); }
-
效果
单条
多条
七、图片数据解析(Demo)
使用Http请求下载网络图片并通过handler机制更新系统ui。
public class MainActivity extends AppCompatActivity { private final String PATH = "https://www.baidu.com/img/bdlogo.png"; private Button button; private ImageView imageView; public Handler handler = new Handler() { public void handleMessage(Message msg) { if (msg.what == RESULT_OK) { byte[] data = (byte[]) msg.obj; //使用Bitmap类解析图片 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); imageView.setImageBitmap(bitmap); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView) findViewById(R.id.imageView1); button = (Button) findViewById(R.id.btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { picDown(); } }); } private void picDown() { new Thread(new Runnable() { @Override public void run() { HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(PATH); try { HttpResponse httpResponse = httpClient.execute(httpGet); if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { byte[] data = EntityUtils.toByteArray(httpResponse .getEntity()); Message message = Message.obtain(); message.obj = data; message.what = RESULT_OK; handler.sendMessage(message); } } catch (Exception e) { e.printStackTrace(); } finally { if (httpClient != null && httpClient.getConnectionManager() != null) { httpClient.getConnectionManager().shutdown(); } } } }).start(); }}
【附录】
Demo
- HttpURLConnection
- HttpCient
- HttpURLConnection抽象请求方法
- 文件下载
- JSON数据解析
- 图片数据解析
整理作者:汪博
少壮不努力,老大徒悲伤。
本文为Android学习规划打造,如有不好的地方请多多指教。
更多相关文章
- emulator找不到AVD解决方式
- Android P的Socket通信实现之传输图片数据
- Android四种存储方式 sharedpreference,file,SQlite,contentprov
- Android数据存储的方法
- android添加文件打开方式以及参数传递
- 第九章使用ContentProvider实现数据共享
- android 动画使用方式
- android中用jsonObject解析json数据