一.Android端进行网络访问的几种实现方式
- Socket
套接字,为TCP/IP协议网络通信的网络操作单元;
而抽象上来说:Socket只是一个供上层调用的抽象接口,相当于是传输层下的数据,还没经过应用层的封装,或者说不需要应用层的封装,因为直接使用socket连接的有两种情况,第一种情况就是直接获取传输层传输过来的输入流,按顺序合成之后就是一个完整的文件,或者是一个字符串等等不需要所谓的解析;另一种情况就是从socket连接返回的数据进行二次封装,进行应用层解析,达到大牛的网络访问框架,实现应用层封装,不过很多细节需要注意。
- UrlConnection
UrlConnection是基于Http协议的,Http协议是应用层的协议,基于Tcp/IP协议之上的协议,是Web浏览器和Web服务器之间的应用层的协议,基于传输层上再进行了一次协议封装,是无状态协议,不需要你往考虑线程、同步、状态治理等,内部是通过socket进行连接和收发数据的,不过一般在数据传输完成之后需要封闭socket连接。
联系:UrlConnection基于Http协议,只不过多了封装,本质上也是建立Tcp连接,利用socket进行连接和数据传输,只不过每次连接之后都要手动关闭连接。因此直接使用Socket进行网络通讯得考虑线程治理、客户状态监控等,但是不用发送头信息等,更省流量。
二.客户端与服务器交互
- 第一种:HTTP协议,也是在工作中最常用的,是建立在TCP/IP基础上实现的。
- 第二种:FTP协议
- 第三种:TCP/IP协议,它也是最底层的协议,其它的方式必须是要通过它,但是要想实现这种协议必须要实现socket编程,这种方法是用来上传一些比较大的文件,视频,进行断点续传的操作。
三.Android中达到网络访问的封装类/框架
-
HttpURLConnection
HttpURLConnection只是继承UrlConnection,两者都是接口,只是在该接口的基础上进行简单封装
从Android4.4开始HttpURLConnection的底层实现采用的是okHttp
-
HttpClient
HttpClient就是对java提供的方法的一些封装,在HttpURLConnection的输入输出流操作,在HttpClient接口里直接封装成HttpPost、HttpGet、HttpResponse。很方便,另外需要注意的是post方式的情况下,我们需要进行字符编码,否则会出错。
Apache HttpClient早就不推荐httpclient,5.0之后干脆废弃,后续会删除。6.0删除了HttpClient。
-
OkHttp
okhttp是高性能的http库,支持同步、异步,而且实现了spdy、http2、websocket协议,api很简洁易用,和volley一样实现了http协议的缓存。picasso就是利用okhttp的缓存机制实现其文件缓存,实现的很优雅,很正确,反例就是UIL(universal image loader),自己做的文件缓存,而且不遵守http缓存机制。
OkHttp的最底层是Socket,而不是HTTP,它通过Platform的Class.forName()反射获得当前Runtime使用的socket库
-
volley
volley是一个简单的异步http库,仅此而已。缺点是不支持同步,这点会限制开发模式。自带缓存,支持自定义请求。不适合大文件上传和下载。
Volley在Android 2.3及以上版本,使用的是HttpURLConnection,而在Android 2.2及以下版本,使用的是HttpClient。
Volley自己的定位是轻量级网络交互,适合大量的,小数据传输。
不过再怎么封装Volley在功能拓展性上始终无法与OkHttp相比。Volley停止了更新,而OkHttp得到了官方的认可,并在不断优化。 -
android-async-http。
与volley一样是异步网络库,但volley是封装的httpUrlConnection,它是封装的httpClient,而android平台不推荐用HttpClient了,所以这个库已经不适合android平台了。 -
Retrofit
Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。注意这里并没有说它是网络请求框架,主要原因在于网络请求的工作并不是 Retrofit 来完成的。Retrofit 2.0 开始内置 OkHttp,前者专注于接口的封装,后者专注于网络请求的高效,二者分工协作,宛如古人的『你耕地来我织布』,小日子别提多幸福了。参考深入浅出 Retrofit
retrofit与picasso一样都是在okhttp基础之上做的封装,项目中可以直接用了。Retrofit因为也是square出的,所以大家可能对它更崇拜些。Retrofit的跟Volley是一个套路,但解耦的更彻底:比方说通过注解来配置请求参数,通过工厂来生成CallAdapter,Converter,你可以使用不同的请求适配器(CallAdapter), 比方说RxJava,Java8, Guava。你可以使用不同的反序列化工具(Converter),比方说json, protobuff, xml, moshi等等。炒鸡解耦,里面涉及到超多设计模式,个人觉得是很经典的学习案例。虽然支持Java8, Guava你可能也不需要用到。xml,protobuff等数据格式你也可能不需要解析。but,万一遇到鬼了呢。至于性能上,个人觉得这完全取决于请求client,也就是okhttp的性能,跟这些封装工具没太大关系。
四.HttpUrlConnection和HttpClient的简单使用
对于我们熟知的网络访问工具类HttpURLConnection和HttpClient,这两个接口都可以用来开发Http访问。
需要引入httpClient包,在本人AS环境,SDK处于23的情况下,需要引入:
android { useLibrary 'org.apache.http.legacy'//httpClient需要包 }
需要的权限:
<uses-permission android:name="android.permission.INTERNET" />
简单的使用:
httpURLConnection、HttpClient:
/** * 使用HTTPUrlConnection例子 * @param username * @param password * @return */ public static String login(String username,String password){ String msg = ""; try { username = URLEncoder.encode(username,"UTF-8");//这里要注意编码,如果参数含有汉字或是空格(尤其是日期中的空格),不编码会发生错误 password = URLEncoder.encode(password,"UTF-8"); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } //要访问的HttpServlet String urlStr="http://127.0.0.1:8080/MyProject/getUser?"; //要传递的数 String params ="username="+username+"&password="+password; urlStr = urlStr+params; try{ URL url =new URL(urlStr); //获得连接 HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setConnectTimeout(6000); conn.setRequestMethod("GET");//请求方式 InputStream in = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in, HTTP.UTF_8)); String line = null; while ((line = reader.readLine()) != null) { if(msg==null){ msg=line; }else{ msg += line; } } reader.close(); in.close();//关闭数据流 conn.disconnect(); }catch(Exception e){ e.printStackTrace(); return null; } return msg; } /** * 使用HttpClient访问,get方式,如果sdk版本为23,需要引入org.apache.http.legacy * @return */ private static String loginHttpClientGet(){ // http地址 String httpUrl = "http://192.168.1.110:8080/httpget.jsp?par=HttpClient_android_Get"; //HttpGet连接对象 HttpGet httpRequest = new HttpGet(httpUrl); String strResult = ""; try { //取得HttpClient对象 HttpClient httpclient = new DefaultHttpClient(); //请求HttpClient,取得HttpResponse HttpResponse httpResponse = httpclient.execute(httpRequest); //请求成功 if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { //取得返回的字符串 strResult = EntityUtils.toString(httpResponse.getEntity()); // mTextView.setText(strResult); } else { // mTextView.setText("请求错误!"); } return strResult; } catch (ClientProtocolException e) { // mTextView.setText(e.getMessage().toString()); } catch (IOException e) { // mTextView.setText(e.getMessage().toString()); } catch (Exception e) { // mTextView.setText(e.getMessage().toString()); } return strResult; } /** * 使用HttpClient访问,post方式 * @return */ private static String loginHttpClientPost(){ // http地址 String httpUrl = "http://192.168.1.110:8080/httpget.jsp"; //HttpPost连接对象 HttpPost httpRequest = new HttpPost(httpUrl); //使用NameValuePair来保存要传递的Post参数 List<NameValuePair> params = new ArrayList<NameValuePair>(); //添加要传递的参数 params.add(new BasicNameValuePair("par", "HttpClient_android_Post")); String strResult = ""; try { //设置字符集 HttpEntity httpentity = new UrlEncodedFormEntity(params, "gb2312"); //请求httpRequest httpRequest.setEntity(httpentity); //取得默认的HttpClient HttpClient httpclient = new DefaultHttpClient(); //取得HttpResponse HttpResponse httpResponse = httpclient.execute(httpRequest); //HttpStatus.SC_OK表示连接成功 if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { //取得返回的字符串 strResult = EntityUtils.toString(httpResponse.getEntity()); // mTextView.setText(strResult); } else { // mTextView.setText("请求错误!"); } return strResult; } catch (ClientProtocolException e) { // mTextView.setText(e.getMessage().toString()); } catch (IOException e) { // mTextView.setText(e.getMessage().toString()); } catch (Exception e) { // mTextView.setText(e.getMessage().toString()); } return strResult; }
socket:
//服务器端 public class MyServer { private static int count=0; public static void main(String[]args){ try { //实例化服务器套接字 设置端口号8888 ServerSocket server=new ServerSocket(8888); while(true){ //连接编号设置 count=count+1; //时间格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //实例化客户端 Socket client=server.accept(); //实例化时间 以及 id System.out.println(count+":"+sdf.format(System.currentTimeMillis())); //获取输出流 OutputStream out=client.getOutputStream(); //输出字符串 String msg="Hello,Android!"; //写字符串 out.write(msg.getBytes()); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //客户端 public class MyClientActivity extends Activity { /** Called when the activity is first created. */ private Button rev=null; private TextView revtext=null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); rev=(Button)findViewById(R.id.rev); revtext=(TextView)findViewById(R.id.receiver); rev.setOnClickListener(new receiverlistenr()); } class receiverlistenr implements OnClickListener{ public void onClick(View v) { // TODO Auto-generated method stub try { //实例化Socket Socket socket=new Socket("169.254.202.149",8888); //获得输入流 InputStream in=socket.getInputStream(); //缓冲区 byte[] buffer=new byte[in.available()]; //读取缓冲区 in.read(buffer); //转换字符串 String msg=new String(buffer); //设置文本框的字符串 revtext.setText(msg); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
Socket连接---至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认
四.请求报文与响应报文
请求报文的一般格式:
通常来说一个HTTP请求报文由请求行、请求报头、空行、和请求数据4个部分组成。
GET http://blog.csdn.net/itachi85 HTTP/1.1 //请求行 Host: blog.csdn.net //请求报头 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36 QQBrowser/9.3.6872.400 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8 Cookie: bdshare_firstime=1443768140949; uuid_tt_dd=5028529250430960147_20151002;
//不能省略的空格
28b5
}ysI 1ߡFsgl n- ]{^_ { 'z! C , m# 0 !l ` 4x ly .ݪ*
...省略
响应报文的一般格式:
HTTP的响应报文由状态行、消息报头、空行、响应正文组成。
HTTP/1.1 200 OK //状态行 Server: openresty //响应报头 Date: Sun, 27 Mar 2016 08:26:54 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Keep-Alive: timeout=20 Vary: Accept-Encoding Cache-Control: private X-Powered-By: PHP 5.4.28 Content-Encoding: gzip //不能省略的空格 28b5 }ysI 1ߡFsgl n- ]{^_ { 'z! C , m# 0 !l ` 4x ly .ݪ* ڴzAt_Xl * 9'O ɬ ' ק 3 ^1a ...省略
如果是请求文件(下载)
要从文件已经下载的地方开始继续下载。在以前版本的 HTTP 协议是不支持断点的,HTTP/1.1 开始就支持了。一般断点下载时才用到 Range 和 Content-Range 实体头。
Range
用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式:
Range:(unit=first byte pos)-[last byte pos]
Content-Range
用于响应头,指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式:
Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]
请求下载整个文件:
- GET /test.rar HTTP/1.1
- Connection: close
- Host: 116.1.219.219
- Range: bytes=0-801 //一般请求下载整个文件是bytes=0- 或不用这个头
一般正常回应
- HTTP/1.1 200 OK
- Content-Length: 801
- Content-Type: application/octet-stream
- Content-Range: bytes 0-800/801 //801:文件总大小
注意:对于socket网络访问
服务器端:根据serversocket.accept()接收到请求socket,socket的inputstream为请求报文(与Http请求报文一致),并且把数据写入到socket的outputStream中(数据格式与HTTP响应报文一致)。
客户端:根据socket,inputStream为服务器返回的数据
五.HTTP的请求方式(八种)的使用环境
据RFC2616标准(现行的HTTP/1.1)得知有以下8种方法:OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE和CONNECT。
con.setRequestMethod("");//设置请求状态
HTTP请求方法有8种,分别是GET、POST、DELETE、PUT、HEAD、TRACE、CONNECT 、OPTIONS。其中PUT、DELETE、POST、GET分别对应着增删改查,对于移动开发最常用的就是POST和GET了。
- GET:请求获取Request-URI所标识的资源(查)
- POST:在Request-URI所标识的资源后附加新的数据(增)
- HEAD:请求获取由Request-URI所标识的资源的响应消息报头,类似于GET, 但是不返回body信息,用于检查对象是否存在,以及得到对象的元数据
- PUT: 请求服务器存储一个资源,并用Request-URI作为其标识(改)
- DELETE :请求服务器删除Request-URI所标识的资源(删)
- TRACE : 请求服务器回送收到的请求信息,主要用于测试或诊断
- CONNECT: HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。用于代理进行传输,如使用SSL
- OPTIONS :请求查询服务器的性能,或者查询与资源相关的选项和需求
- 获取服务器支持的HTTP请求方法;也是黑客经常使用的方法。
-
用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。
- PATCH,部分文档更改
- PROPFIND查看属性
- PROPPATCH, 设置属性
- MKCOL,创建集合(文件夹)
- COPY, 拷贝
- MOVE,移动
- LOCK, 加锁
- UNLOCK,解锁
- TRACE,用于远程诊断服务器
参考链接:
URLConnection/HttpURLConnection/HttpClient/socket 差别