微信号:appjiagou

介绍:分享最有价值的APP技术干货文章,做一个有逼格的APP架构师,拒绝平庸,打造最有价值的APP社区!

Android网络请求心路历程(上)

2017-01-04 00:19 APP架构师


来源:Jude95  

链接:http://www.jianshu.com/p/d9e4ddd1c530


网络请求是android客户端很重要的部分。下面从入门级开始介绍下自己Android网络请求的实践历程。希望能给刚接触Android网络部分的朋友一些帮助。

本文包含:


  • HTTP请求&响应

  • Get&Post

  • HttpClient & HttpURLConnection

  • 同步&异步

  • HTTP缓存机制

  • Volley&OkHttp

  • Retrofit&RestAPI

  • 网络图片加载优化

  • Fresco&Glide

  • 图片管理方案


HTTP请求&响应


既然说从入门级开始就说说Http请求包的结构。

一次请求就是向目标服务器发送一串文本。什么样的文本?有下面结构的文本。

HTTP请求包结构



请求包

例子:


POST /meme.php/home/user/login HTTP/1.1

   Host: 114.215.86.90

   Cache-Control: no-cache

   Postman-Token: bd243d6b-da03-902f-0a2c-8e9377f6f6ed

   Content-Type: application/x-www-form-urlencoded

   tel=13637829200&password=123456


请求了就会收到响应包(如果对面存在HTTP服务器)

HTTP响应包结构




响应包

例子:


HTTP/1.1 200 OK

   Date: Sat, 02 Jan 2016 13:20:55 GMT

   Server: Apache/2.4.6 (CentOS) PHP/5.6.14

   X-Powered-By: PHP/5.6.14

   Content-Length: 78

   Keep-Alive: timeout=5, max=100

   Connection: Keep-Alive

   Content-Type: application/json; charset=utf-8

   {"status":202,"info":"\u6b64\u7528\u6237\u4e0d\u5b58\u5728\uff01","data":null}


Http请求方式有


方法 描述
GET 请求指定url的数据,请求体为空(例如打开网页)。
POST 请求指定url的数据,同时传递参数(在请求体中)。
HEAD 类似于get请求,只不过返回的响应体为空,用于获取响应头。
PUT 从客户端向服务器传送的数据取代指定的文档的内容。
DELETE 请求服务器删除指定的页面。
CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS 允许客户端查看服务器的性能。
TRACE 回显服务器收到的请求,主要用于测试或诊断。

常用只有Post与Get。


Get&Post


网络请求中我们常用键值对来传输参数(少部分api用json来传递,毕竟不是主流)。

通过上面的介绍,可以看出虽然Post与Get本意一个是表单提交一个是请求页面,但本质并没有什么区别。下面说说参数在这2者的位置。


  • Get方式

    在url中填写参数:


http://xxxx.xx.com/xx.php?params1=value1&params2=value2



甚至使用路由


http://xxxx.xx.com/xxx/value1/value2/value3


这些就是web服务器框架的事了。


  • Post方式

    参数是经过编码放在请求体中的。编码包括x-www-form-urlencoded 与 form-data。x-www-form-urlencoded的编码方式是这样:


tel=13637829200&password=123456



  • form-data的编码方式是这样:


----WebKitFormBoundary7MA4YWxkTrZu0gW

 Content-Disposition: form-data; name="tel"

 13637829200

 ----WebKitFormBoundary7MA4YWxkTrZu0gW

 Content-Disposition: form-data; name="password"

 123456

 ----WebKitFormBoundary7MA4YWxkTrZu0gW


x-www-form-urlencoded的优越性就很明显了。不过x-www-form-urlencoded只能传键值对,但是form-data可以传二进制


因为url是存在于请求行中的。


所以Get与Post区别本质就是参数是放在请求行中还是放在请求体中

当然无论用哪种都能放在请求头中。一般在请求头中放一些发送端的常量。


有人说:


  • Get是明文,Post隐藏

  • 移动端不是浏览器,不用https全都是明文。

  • Get传递数据上限XXX

  • 胡说。有限制的是浏览器中的url长度,不是Http协议,移动端请求无影响。Http服务器部分有限制的设置一下即可。

  • Get中文需要编码

  • 是真的…要注意。URLEncoder.encode(params, "gbk");


还是建议用post规范参数传递方式。并没有什么更优秀,只是大家都这样社会更和谐。


上面说的是请求。下面说响应。

请求是键值对,但返回数据我们常用Json。

对于内存中的结构数据,肯定要用数据描述语言将对象序列化成文本,再用Http传递,接收端并从文本还原成结构数据。

对象(服务器)<–>文本(Http传输)<–>对象(移动端) 。


服务器返回的数据大部分都是复杂的结构数据,所以Json最适合。

Json解析库有很多Google的Gson,阿里的FastJson。

Gson的用法看这里。

http://www.cnblogs.com/Jude95/p/Mr_Dentist.html


HttpClient & HttpURLConnection


HttpClient早被废弃了,谁更好这种问题也只有经验落后的面试官才会问。具体原因可以看这里。


下面说说HttpURLConnection的用法。

最开始接触的就是这个。


public class NetUtils {

       public static String post(String url, String content) {

           HttpURLConnection conn = null;

           try {

               // 创建一个URL对象

               URL mURL = new URL(url);

               // 调用URL的openConnection()方法,获取HttpURLConnection对象

               conn = (HttpURLConnection) mURL.openConnection();

               conn.setRequestMethod("POST");// 设置请求方法为post

               conn.setReadTimeout(5000);// 设置读取超时为5秒

               conn.setConnectTimeout(10000);// 设置连接网络超时为10秒

               conn.setDoOutput(true);// 设置此方法,允许向服务器输出内容

               // post请求的参数

               String data = content;

               // 获得一个输出流,向服务器写数据,默认情况下,系统不允许向服务器输出内容

               OutputStream out = conn.getOutputStream();// 获得一个输出流,向服务器写数据

               out.write(data.getBytes());

               out.flush();

               out.close();

               int responseCode = conn.getResponseCode();// 调用此方法就不必再使用conn.connect()方法

               if (responseCode == 200) {

                   InputStream is = conn.getInputStream();

                   String response = getStringFromInputStream(is);

                   return response;

               } else {

                   throw new NetworkErrorException("response status is "+responseCode);

               }

           } catch (Exception e) {

               e.printStackTrace();

           } finally {

               if (conn != null) {

                   conn.disconnect();// 关闭连接

               }

           }

           return null;

       }

       public static String get(String url) {

           HttpURLConnection conn = null;

           try {

               // 利用string url构建URL对象

               URL mURL = new URL(url);

               conn = (HttpURLConnection) mURL.openConnection();

               conn.setRequestMethod("GET");

               conn.setReadTimeout(5000);

               conn.setConnectTimeout(10000);

               int responseCode = conn.getResponseCode();

               if (responseCode == 200) {

                   InputStream is = conn.getInputStream();

                   String response = getStringFromInputStream(is);

                   return response;

               } else {

                   throw new NetworkErrorException("response status is "+responseCode);

               }

           } catch (Exception e) {

               e.printStackTrace();

           } finally {

               if (conn != null) {

                   conn.disconnect();

               }

           }

           return null;

       }

       private static String getStringFromInputStream(InputStream is)

               throws IOException {

           ByteArrayOutputStream os = new ByteArrayOutputStream();

           // 模板代码 必须熟练

           byte[] buffer = new byte[1024];

           int len = -1;

           while ((len = is.read(buffer)) != -1) {

               os.write(buffer, 0, len);

           }

           is.close();

           String state = os.toString();// 把流中的数据转换成字符串,采用的编码是utf-8(模拟器默认编码)

           os.close();

           return state;

       }

   }


注意网络权限!被坑了多少次。


<uses-permission android:name="android.permission.INTERNET"/>


同步&异步


这2个概念仅存在于多线程编程中。

android中默认只有一个主线程,也叫UI线程。因为View绘制只能在这个线程内进行。

所以如果你阻塞了(某些操作使这个线程在此处运行了N秒)这个线程,这期间View绘制将不能进行,UI就会卡。所以要极力避免在UI线程进行耗时操作。

网络请求是一个典型耗时操作。

通过上面的Utils类进行网络请求只有一行代码。


NetUtils.get("http://www.baidu.com");//这行代码将执行几百毫秒。


如果你这样写


@Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_main);

       String response Utils.get("http://www.baidu.com");

   }


就会死。。

这就是同步方式。直接耗时操作阻塞线程直到数据接收完毕然后返回。Android不允许的。


(本文字数超过限制,后续请看下一条内容)

 
APP架构师 更多文章 Android TextureView的原理分析_haihengcao_新浪博客 Android属性动画源代码解析(超详细) Android DiskLruCache 源码解析 硬盘缓存的绝佳方案(上) Android 沉浸式状态栏攻略 让你的状态栏变色吧 Android干货框架集锦,搭建项目必不可少
猜您喜欢 小象学院招聘讲师! 数据科学家的15个原则 踢人们屁股并让他们震惊 iOS交互式动画详解(下):iOS 10 的新变化 常见的八种导致 APP 内存泄漏的问题 关于CodeReview第一次线下大会的一些总结