Android网络编程:Http原理及优化

一、前言

网络编程作为移动应用开发的一个重要内容,虽然现在有很多的开源库可以帮我们方便快捷的访问网络。但我们仍要去了解其网络访问的原理,这是基础,也是优秀的开发者必备素质。

二、Http

1、概念

HTTP,全称HyperText Transfer Protocol,超文本传输协议,即使用超文本标记语言(HTML)的一种文本传输协议。它是应用层协议,规定了数据交互的格式内容。

2、特点

支持C/S(客户/服务器)模式。

简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST,每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,	即断开连接。采用这种方式可以节省传输时间。

无状态:HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

3、协议

1)Http Url

http://<主机>:<端口>/<路径>

2)请求报文

请求报文包括 请求行、请求头部、请求数据

请求方法有多种,GET和POST是最主要的两种方式:

GET:从指定的资源请求数据,查询参数(键值对)包含在URL中

POST:向指定的资源提交要被处理的数据,查询参数(键值对)放在 HTTP 消息主体中

HEAD:请求读取由URL所标志的信息的首部

OPTION:请求一些选项的信息

PUT:在指明的URL下存储一个文档

DELETE:删除指明的URL所标志的资源

TRACE:用来进行环回测试的请求报文

CONNECT:用于代理服务器

请求报头有许多种,常见的请求头有:

Host域:指定请求的服务器地址,在HTTP/1.1中请求必须包含主机头域,否则系统会以400状态码返回。

Connection域:指定与连接相关的属性,比如上面例子中Connection: keep-alive,代表保持连接。

User-Agent域:发送请求的应用程序名称。

Accept-Charset域:通知服务端可以发送的编码格式。

Accept-Encoding域:通知服务端可以发送的数据压缩格式。

Accept-Language域:通知服务器可以发送的语言。

Accept域: 告诉WEB服务器自己接受什么介质类型,/ 表示任何类型,type/* 表示该类型下的所有子类型。

Referer域:发送请求页面URL。浏览器向 WEB 服务器表明自己是从哪个 网页/URL 获得/点击 当前请求中的网址/URL。

Pramga域:主要使用 Pramga: no-cache,相当于 Cache-Control: no-cache。

Date域:表示消息发出的时间。

Cookie域:设置Cookie相关的。

Cache-Control域:使用的缓存机制(在请求时和响应时它的值不同,响应的下文会说到)。

no-cache(不要缓存的实体,要求现在从服务器去取)

max-age:(只接受 Age 值小于 max-age 值,并且没有过期的对象)

max-stale:(可以接受过去的对象,但是过期时间必须小于 max-stale 值)

min-fresh:(接受其新鲜生命期大于其当前 Age 跟 min-fresh 值之和的缓存对象)。

3)响应报文

类比请求报文,响应报文同样也包括 响应行、响应头、响应数据

状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:

100~199:指示信息,表示请求已接收,继续处理

200~299:请求成功,表示请求已被成功接收、理解、接受

300~399:重定向,要完成请求必须进行更进一步的操作

400~499:客户端错误,请求有语法错误或请求无法实现

500~599:服务器端错误,服务器未能实现合法的请求

常见的状态码如下:

200 OK:客户端请求成功

400 Bad Request:客户端请求有语法错误,不能被服务器所理解

401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用

403 Forbidden:服务器收到请求,但是拒绝提供服务

500 Internal Server Error:服务器发生不可预期的错误

503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常

响应头,列举一下常用的头部:

Age域:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。

Accept-Ranges域:WEB服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。

Content-Type域:服务器通知客户端它响应的对象类型,如Content-Type:application/json。

Content-Range域:服务器表明该响应包含的部分对象为整个对象的哪个部分。

Content-Length:服务器通知响应包含对象的长度。

Content-Language:服务器通知客户端响应的语言。

Content-Encoding:服务器通知客户端响应数据的压缩格式,在上面的例子中可以看出压缩格式为gzip。

Connection:代表是否需要持久连接。

Expired:WEB服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB服务器验证了其有效性后,才能用来响应客户请求。

Last-Modified: 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。

Location:服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。

Proxy-Authenticate:代理服务器响应浏览器,要求其提供代理身份验证信息。

Server: 服务器表明自己是什么软件及版本等信息。

Refresh:表示浏览器应该在多少时间之后刷新文档,以秒计。

响应数据:

一般来说响应数据也会有一定的格式,上面的例子中因为数据用gzip压缩了,所以显示的为乱码。现在用的最火的为json格式数据,下面的例子为一个请求的响应数据,为json格式:

{“status”:true,”error”:”“,”data”:{“id”:010101,”url”:”http://blog.csdn.net/liushuaiq/article/details/52779689“}}

4、HttpClient与HttpURLConnection

Android SDK中包含了HttpClient,在Android6.0版本直接删除了HttpClient类库,如果仍想使用则解决方法是:

android {
    useLibrary 'org.apache.http.legacy'
     }

1)HttpClient

要使用HttpClient,先创建一个HttpClient,配置好请求参数:

private HttpClient createHttpClient() {
       HttpParams mDefaultHttpParams = new BasicHttpParams();
       //设置连接超时
       HttpConnectionParams.setConnectionTimeout(mDefaultHttpParams, 15000);
       //设置请求超时
       HttpConnectionParams.setSoTimeout(mDefaultHttpParams, 15000);
       HttpConnectionParams.setTcpNoDelay(mDefaultHttpParams, true);
       HttpProtocolParams.setVersion(mDefaultHttpParams, HttpVersion.HTTP_1_1);
       HttpProtocolParams.setContentCharset(mDefaultHttpParams, HTTP.UTF_8);
       //持续握手
       HttpProtocolParams.setUseExpectContinue(mDefaultHttpParams, true);
       HttpClient mHttpClient = new DefaultHttpClient(mDefaultHttpParams);
       return mHttpClient;
   }

GET请求

//创建HttpGet和HttpClient,请求网络并得到HttpResponse
private void useHttpClientGet(String url) {
      HttpGet mHttpGet = new HttpGet(url);
      mHttpGet.addHeader("Connection", "Keep-Alive");
      try {
          HttpClient mHttpClient = createHttpClient();
          HttpResponse mHttpResponse = mHttpClient.execute(mHttpGet);
          HttpEntity mHttpEntity = mHttpResponse.getEntity();
          int code = mHttpResponse.getStatusLine().getStatusCode();
          if (null != mHttpEntity) {
              InputStream mInputStream = mHttpEntity.getContent();
              String respose = converStreamToString(mInputStream);
              Log.i("wangshu", "请求状态码:" + code + "\n请求结果:\n" + respose);
              mInputStream.close();
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

POST请求,需要修改为HttpPost,增加键值对参数:

List<NameValuePair> postParams = new ArrayList<>();
 //要传递的参数
postParams.add(new BasicNameValuePair("username", "admin"));
postParams.add(new BasicNameValuePair("password", "123"));
mHttpPost.setEntity(new UrlEncodedFormEntity(postParams));
HttpResponse mHttpResponse = mHttpClient.execute(mHttpPost);

Get和Post请求区别:

  • GET 请求可被缓存,保留在浏览器历史记录中,不应在处理敏感数据时使用,有长度限制
  • POST 请求不会被缓存,不会保留在浏览器历史记录中,对数据长度没有要求

3)HttpURLConnection

在Android 2.3版本及以后,HttpURLConnection相比HttpClient是最佳的选择,它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。另外在Android 6.0版本中,HttpClient库被移除了,HttpURLConnection则是以后我们唯一的选择。

创建HttpURLConnection,配置参数:

public static HttpURLConnection getHttpURLConnection(String url){
     HttpURLConnection mHttpURLConnection=null;
     try {
         URL mUrl=new URL(url);
         mHttpURLConnection=(HttpURLConnection)mUrl.openConnection();
         //设置链接超时时间
         mHttpURLConnection.setConnectTimeout(15000);
         //设置读取超时时间
         mHttpURLConnection.setReadTimeout(15000);
         //设置请求参数
         mHttpURLConnection.setRequestMethod("POST");
         //添加Header
         mHttpURLConnection.setRequestProperty("Connection","Keep-Alive");
         //接收输入流
         mHttpURLConnection.setDoInput(true);
         //传递参数时需要开启
         mHttpURLConnection.setDoOutput(true);
     } catch (IOException e) {
         e.printStackTrace();
     }
     return mHttpURLConnection ;
 }

组装请求参数

public static void postParams(OutputStream output,List<NameValuePair>paramsList) throws IOException{
      StringBuilder mStringBuilder=new StringBuilder();
      for (NameValuePair pair:paramsList){
          if(!TextUtils.isEmpty(mStringBuilder)){
              mStringBuilder.append("&");
          }
          mStringBuilder.append(URLEncoder.encode(pair.getName(),"UTF-8"));
          mStringBuilder.append("=");
          mStringBuilder.append(URLEncoder.encode(pair.getValue(),"UTF-8"));
      }
      BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,"UTF-8"));
      writer.write(mStringBuilder.toString());
      writer.flush();
      writer.close();
  }

请求网络连接,处理返回结果:

private void useHttpUrlConnectionPost(String url) {
     InputStream mInputStream = null;
     HttpURLConnection mHttpURLConnection = UrlConnManager.getHttpURLConnection(url);
     try {
         List<NameValuePair> postParams = new ArrayList<>();
         //要传递的参数
         postParams.add(new BasicNameValuePair("username", "moon"));
         postParams.add(new BasicNameValuePair("password", "123"));
         UrlConnManager.postParams(mHttpURLConnection.getOutputStream(), postParams);
         mHttpURLConnection.connect();
         mInputStream = mHttpURLConnection.getInputStream();
         int code = mHttpURLConnection.getResponseCode();
         String respose = converStreamToString(mInputStream);
         Log.i("wangshu", "请求状态码:" + code + "\n请求结果:\n" + respose);
         mInputStream.close();
     } catch (IOException e) {
         e.printStackTrace();
     }
 }

三、Http版本

实际上,HTTP协议是有 0.9,1.0,1.1,2四个版本的。

最早版本是1991年发布的0.9版,该版本只有一个请求,GET请求。而且服务器只能回应HTML格式的字符串,不能回应别的格式。

1996年5月,HTTP/1.0 版本发布。支持了多种格式,包括文字、图像、视频、二进制文件等。请求方式有GET,POST,HEAD,同时增加了状态码、多字符集支持、多部分发送、权限、缓存、内容编码等功能。

这个版本缺点:每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭。TCP连接的新建成本很高,因为需要客户端和服务器三次握手。所以性能较差,在请求资源较多时较为明显。

1997年1月1.1版本进一步完善了 HTTP 协议,同时也是目前的主流版本。1.引入了持久连接(persistent connection),TCP连接默认不关闭,可以被多个请求复用。2.管道机制(pipelining),在同一个TCP连接里面,客户端可以同时发送多个请求。3.支持流模式,取代缓存模式, 通过Transfer-Encoding字段使用分块传输。4.增加PUT,OPTIONS,DELETE等更多请求方式,增加HOST字段,指定服务器,可以将请求发往同一台服务器上的不同网站,为虚拟主机的兴起打下了基础。

1.1版本的缺点:允许复用TCP连接,但请求和响应需要同步处理。要是如果某个请求处理特别慢,后面就会有许多请求排队等着。

HTTP/2是最新版本,规范还没有最终完成,基于 SPDY 协议(基于TCP的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验)。打开一个 TCP 连接并重复使用,这使得许多请求得以并行发送,而无需等待响应。

和1.1版本相比, HTTP/2采用二进制格式而非文本格式。HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行。使用报头压缩,压缩头信息,客户端和服务器维护一张头信息表,通过索引,HTTP/2降低了开销。HTTP/2让服务器可以将响应主动“推送”到客户端缓存中。

在开放互联网上HTTP 2.0将只用于https网址,而 http网址将继续使用HTTP/1,目的是在开放互联网上增加使用加密技术,以提供强有力的保护去遏制主动攻击。

四、HTTP安全

HTTP协议版本的更迭,除了功能上的扩展和丰富,更实质上是性能上的优化,已适应越来越高速和频繁的网络请求。

但在互联网时代,HTTP如何保证安全性呢?HTTP 具有相当优秀和方便的一面,但在安全性上也有不足:

1)通信使用明文( 不加密) , 内容可能会被窃听

2)不验证通信方的身份, 因此有可能遭遇伪装

3)无法证明报文的完整性, 所以有可能已遭篡改

这就要说到HTTPS了。

HTTPS可以理解为HTTP+SSL/TLS, 即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL,用于安全的 HTTP 数据传输。

内容加密建立一个信息安全通道,来保证数据传输的安全;

身份认证确认网站的真实性

数据完整性防止内容被第三方冒充或者篡改

不足:对数据进行加解密决定了它比http慢。需要进行非对称的加解密,且需要三次握手。首次连接比较慢点,当然现在也有很多的优化。

TLS(Transport Layer Security,传输层安全协议),及其前身SSL(Secure Sockets Layer,安全套接层)是一种安全协议,目的是为互联网通信,提供安全及数据完整性保障。

至于TLS/SSL怎么保障安全性,有个博主写得非常好,此处贴一下跳转:https://www.jianshu.com/p/24af67c40e8d

五、补充

以上的针对性能和安全性的优化,实际上是基于协议层面的优化,开发者在实际项目中关于网络请求实际上还可以做一些优化。

在APP使用中,网络连接对用户的影响主要有三点:

  • 流量:App的流量消耗对用户来说是比较敏感的, 毕竟流量是花钱的嘛. 现在大部分人的手机上都有安装流量监控的工具App,
    用来监控App的流量使用. 如果我们的App这方面没有控制好, 会给用户不好的使用体验.
  • 电量:电量相对于用户来说, 没有那么明显. 一般用户可能不会太注意. 但是如前文电量优化中说的那样,
    网络连接(radio)是对电量影响很大的一个因素. 所以我们也要加以注意.
  • 用户等待:也就是用户体验, 良好的用户体验, 才是我们留住用户的第一步. 如果App请求等待时间长, 会给用户网络卡, 应用反应慢的感觉,
    如果有对比, 有替代品, 我们的App很可能就会被用户无情抛弃.

从哪些方面去优化网络进而减少甚至消灭这些影响

1)减少Radio活跃时间。也就是减少网络数据获取的频次.这就减少了radio的电量消耗, 控制电量使用.

2)减少获取数据包的大小。可以减少流量消耗,也可以让每次请求更快, 在网络情况不好的情况下也有良好表现, 提升用户体验.

具体入手:

1)API设计:App与Server之间的API设计要考虑网络请求的频次, 资源的状态等. 以便App可以以较少的请求来完成业务需求和界面的展示.

2)Gzip压缩:使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗.

3)图片的Size:图片相对于接口请求来说, 数据量要大得多.我们可以在获取图片时告知服务器需要的图片的宽高, 以便服务器给出合适的图片, 避免浪费.

4)网络缓存:适当的缓存, 既可以让我们的应用看起来更快, 也能避免一些不必要的流量消耗.

5)通过监听设备的状态:休眠,充电,网络状态,根据网络状态对网络请求进行区别对待,对应的网络策略也应该是不一样的。

 例如Splash闪屏广告图片, 我们可以在连接到Wifi时下载缓存到本地; 新闻类的App可以在充电, Wifi状态下做离线缓存。

6)弱网测试&优化:本质上是在弱网的情况下能让用户流畅的使用我们的App.

 主要可以从以下方面入手:压缩/减少数据传输量,利用缓存减少网络传输,针对弱网(移动网络), 不自动加载图片,界面先反馈, 请求延迟提交

7)预取:我们需要预先判断用户在此次操作之后,后续零散的请求是否很有可能会马上被触发,可以把后面几分钟有可能会使用到的零散请求都一次集中执行完毕。

8)IP直连:DNS解析的失败率占联网失败中很大一种,而且首次域名解析一般需要几百毫秒。针对此,我们可以不用域名,才用IP直连省去 DNS 解析过程,节省这部分时间。

9)增量更新:数据更新采用增量,而不是全量,仅将变化的数据返回,客户端进行合并,减少流量消耗。

10)Protocol Buffer:Protocol Buffer是Google的一种数据交换的格式,它独立于语言,独立于平台。相较于目前常用的Json,数据量更小,意味着传输速度也更快。

11)断点续传:文件、图片等的下载,采用断点续传,不浪费用户之前消耗过的流量;

12)重试策略:一次网络请求的失败,需要多次的重试来断定最终的失败。

分析网络连接和弱网测试工具的使用,可以参考博客:https://www.jianshu.com/p/d4c2c62ffc35

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KWMax

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值