Android网络请求三篇

来源:互联网 发布:大胸内衣推荐 知乎 编辑:程序博客网 时间:2024/05/21 12:15

篇一:Android 网络操作和优化相关

写在前面的话

Android程序最重要的模块就是网络部分,如何从网络上下载数据,如何将处理过的数据上传至网络,往往是android程序的关键环节。前几天偶一朋友遇到这么一个问题:如何使用volley实现文件上传。最后问题解决了,小伙伴不禁有些飘飘然,大有一番天下之事皆逃不过我的魔掌的感觉。这时候coder君问了他几个问题,大家可以一起思考下:

  • TCP/IP协议、SOCKET、HTTP协议、HTTPS协议都是做什么的,他们之间有关系吗
  • 你是如何管理网络使用情况的
  • 常用的HTTP Client有哪些,该如何选择
  • 假如你需要修改的你网络请求框架,你要动多少代码
  • 你是怎么进行网络优化的

小伙伴支支吾吾,其实他熟悉的只是Android中网络相关的那些点中的一小部分,不免有些盲人摸象的感觉,你呢?

网络通信机制

网络由下往上分为:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层,三者从本质上来说没有可比性,socket则是对TCP/IP协议的封装和应用。也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。

TCP/IP协议

网络编程的目的就是直接或间接地通过网络协议与其他计算机进行通讯。

网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机;另一个就是找到主机后如何可靠高效的进行数据传输。目前使用最广泛的因特网协议是TCP/IP协议。

在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。

Socket

我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

Socket跟TCP/IP协议没有必然的联系。Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,

Http协议

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

HTTP提供了封装或者显示数据的具体形式。Socket提供了网络通信的能力。

Https协议

HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。被广泛用于万维网上安全敏感的通讯,例如交易支付方面。

Android网络权限

<uses-permission android:name="android.permission.INTERNET" /><-- 允许应用程序打开网络套接字 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><-- 允许应用程序访问网络连接信息 -->

选择一个HTTP Client

大多数连接网络的 Android app 会使用 HTTP 来发送与接收数据。Android 提供了三种 HTTP client:HttpURLConnection、Apache HttpClient 和okhttp。二者均支持 HTTPS、流媒体上传和下载、可配置的超时、IPv6 与连接池(connection pooling)。【Apache HttpClient 已废,删除了】

  • Java.net 包中的 HttpURLConnection 类 HttpUrlConnection 是 JDK 里提供的联网 API,我们知道Android SDK 是基于 Java 的,所以当然优先考虑 HttpUrlConnection 这种最原始最基本的 API,其实大多数开源的联网框架基本上也是基于 JDK 的 HttpUrlConnection 进行的封装罢了

  • okhttp http 是现在主流应用使用的网络请求方式, 用来交换数据和内容, 有效的使用HTTP可以使你的APP变的更快和减少流量的使用。OkHttp是一个很棒HTTP客户端:

    • 支持 SPDY,可以合并多个到同一个主机的请求
    • 使用连接池技术减少请求的延迟(如果 SPDY 是可用的话)
    • 使用 GZIP 压缩减少传输的数据量
    • 缓存响应避免重复的网络请求

当你的网络出现拥挤的时候,就是 OKHttp 大显身手的时候,它可以避免常见的网络问题,如果你的服务是部署在不同的 IP上面的,如果第一个连接失败, OkHTtp 会尝试其他的连接。这对现在 IPv4 + IPv6 中常见的把服务冗余部署在不同的数据中心上也是很有必要的。OkHttp 将使用现在 TLS 特性 (SNI ALPN) 来初始化新的连接,如果握手失败,将切换到 TLS 1.0。

使用OkHttp很容易,同时支持异步阻塞请求和回调.

如果你使用OkHttp ,你不用重写你的代码, okhttp-urlconnection模块实现了 java.net.HttpURLConnection 中的API, okhttp-apache模块实现了HttpClient中的API

如何管理网络的使用情况

如果我们的程序需要执行大量网络操作,那么应该提供用户设置选项,来允许用户控制程序的数据偏好。例如,同步数据的频率,是否只在连接到 WiFi 才进行下载与上传操作,是否在漫游时使用套餐数据流量等等。这样用户才不大可能在快到达流量上限时,禁止我们的程序获取后台数据,因为他们可以精确控制我们的 app 使用多少数据流量。

检查网络连接

在执行网络操作之前,检查设备当前连接的网络连接信息是个好习惯。这样可以防止我们的程序在无意间连接使用了非意向的网络频道。如果网络连接不可用,那么我们的应用应该优雅地做出响应。为了检测网络连接,我们需要使用到下面两个类:

ConnectivityManager:它会回答关于网络连接的查询结果,并在网络连接改变时通知应用程序。
NetworkInfo:描述一个给定类型(移动网络或 Wi-Fi等)的网络接口状态。

下面这个方法可以找到的第一个已连接的网络接口,如果返回null,则表示没有已连接的网络接口(意味着网络连接不可用):

public boolean isOnline() {    ConnectivityManager connMgr = (ConnectivityManager)getSystemServic(Context.CONNECTIVITY_SERVICE);    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();    return (networkInfo != null & networkInfo.isConnected());} 

管理网络的使用情况

我们可以实现一个偏好设置的选项,使用户能直接设置程序对网络资源的使用情况。例如:

可以允许用户仅在连接到 Wi-Fi 时上传视频。
可以根据诸如网络可用,时间间隔等条件来选择是否做同步的操作。

检测网络连接变化

要检测网络连接的变化需要用到BroadcastReceiver 的子类:NetworkReceiver。当设备网络连接改变时,NetworkReceiver 会监听到 CONNECTIVITY_ACTION,这时需要判断当前网络连接类型并相应的设置好 wifiConnected 与 mobileConnected。

这里需要注意的是在使用BroadcastReceiver的时候,不必要的声明注册会浪费系统资源。最好在 onCreate()中注册 BroadcastReceiver NetworkReceiver,在 onDestroy()中销毁它。这样做会比在 manifest 里面声明 更轻巧。

在一个单独的线程中执行网络操作

网络操作会遇到不可预期的延迟。为了避免造成不好的用户体验,确保总是在 UI 线程之外单独的线程中执行网络操作。关于Android中的线程和进程可以参考这里。

封装网络请求

有了Http Client,我们需要考虑的是如何优雅的使用这些Http Client进行网络请求。这时候我们可以对Http Clent进行封装来满足我们的需求。封装的基本要点如下:

-支持自定义请求的Header
- 支持Http的基本请求方法:GET、POST、PUT、DELETE等
- 支持文件上传和下载
- 可以加载图片
- 支持多任务网络请求操作
- 支持缓存
- 支持请求回调
- 支持session的保持

具体可以参考Volley源码分析、Retrofit分析等,看看这些成名已久的框架是如何封装的。

网络请求框架

以下是曾经很火或者现在很多的网络请求框架:

Android Async HTTP
AndroidAsync
Volley
Retrofit

AsyncHttp是一个较高层的封装,底层使用的是HttpClient。

Volley是Google推出的Android异步网络请求框架和图片加载框架。底层网络请求可以使用不同的网络库来处理,比如OkHttp,HttpClient。

Retrofit是一个封装比较好的,相对更面向开发者的rest请求库,它的底层网络请求也可以使用不同的网络库来处理。

网络请求框架再封装

我在这些Tips让你的App更容易维护 简单提到过面向接口编程,这对于网络框架再封装很实用。

我们可以定义一套我们自己的网络请求接口,接口规定我们需要有什么能力的框架。调用的话我们可以直接调用接口方法就好了,这样不管我们换什么网络框架,只要这个框架有网络请求的能力就行。举个例子,定义接口如下:

public interface ISender {    void send(METHOD method, String url, SenderCallback callback);}

我们写一个类实现这个接口:

public class BaseSender implements ISender {    @Override    public synchronized void sender(METHOD method, final String url, final SenderCallback callback) {        //volley,retrofit,balabala,用任意网络请求框架实现。    }}

然后是我们的SendCallback

public interface SendCallback {    //请求成功回调    void onSucceeed(String t);    //请求失败回调    void onError(String errorMsg);}

最后是我们的调用:

private ISender mHttp;public void getWeahter(String tag) {    //调用接口里定义好的方法    mHttp.sender();}

这里传入我们需要的参数就好了。

数据解析

以下是比较流行的网络数据解析的库:

  • Gson
  • Jackson
  • FastJson
  • HtmlPaser
  • Jsoup

网络数据解析比较基础,这里就不过多描述了。

移动端网络优化

对于手机程序,网络操作相对来说是比较耗电的行为。优化网络操作能够显著节约电量的消耗。一个网络请求可以简单分为连接服务器和获取数据两个部分。其中连接服务器前还包括 DNS 解析的过程;获取数据后可能会对数据进行缓存。那么我们如果要进行网络优化需要从这两个关键点入手。

以下是具体可以优化的点:

  • 不用域名,用IP直连省去 DNS 解析过程,DNS 全名 Domain Name System,解析意指根据域名得到其对应的IP地址。
  • 服务器合理部署服务器多运营商多地部署,一般至少含三大运营商、南中北三地部署。
  • 根据当前的网络环境选择当下最佳的策略主要的实施步骤有两步:第1是检测收集当前的网络环境信息,第2是根据当前收集到的信息进行网络请求行为的调整。
  • 预取我们需要预先判断用户在此次操作之后,后续零散的请求是否很有可能会马上被触发,可以把后面几分钟有可能会使用到的零散请求都一次集中执行完毕。
  • 连接复用节省连接建立时间,如开启 keep-alive。
  • 用捆绑批量访问的方式来减少访问的频率预先判定那些可能马上就会使用到的网络资源,捆绑一起集中进行网络请求。这点我们可以考虑按照提前预期后续1-2分钟的数据作为基准。
  • 分优先级、延迟部分请求首先我们需要区分哪些网络请求是需要及时返回结果的,哪些是可以延迟执行的。我们可以有针对性的把请求行为捆绑起来,延迟到某个时刻统一发起请求。
  • 对传输的数据进行压缩网络传输数据量的大小主要由两部分组成:图片与序列化的数据,那么我们需要做的就是减少这两部分的数据传输大小。
  • 多连接对于较大文件,如大图片、文件下载可考虑多连接。 需要控制请求的最大并发量,毕竟移动端网络受限。
  • 避免不必要的同步操作应用程序的一个基础功能是能够保持确保界面上呈现的信息是即时最新的,例如呈现最新的新闻,天气,信息流等等信息。但是,过于频繁的促使手机客户端应用去同步最新的服务器数据会对性能产生很大的负面影响,不仅仅使得CPU不停的在工作,内存,网络流量,电量等等都会持续的被消耗,所以在进行网络请求操作的时候一定要避免多度同步操作。
  • 做好网络数据的缓存从手机的缓存中直接读取数据肯定比从网络上获取数据要更加的便捷高效,特别是对于那些会被频繁访问到的数据,需要把这些数据缓存到设备上,以便更加快速的进行访问。

参考链接:

http://www.trinea.cn/android/mobile-performance-optimization/
http://hukai.me/android-performance-patterns-season-4/
http://hukai.me/android-performance-patterns-season-2/


来源:伯乐在线专栏作者 - PleaseCallMeCoder
链接:http://android.jobbole.com/83394/


篇二:Android网络请求心路历程

网络请求是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请求方式有如下几种,常用只有Post与Get。

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

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-urlencodedform-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的用法看这里。

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不允许的。

异步方式:

    //在主线程new的Handler,就会在主线程进行后续处理。    private Handler handler = new Handler();    private TextView textView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        textView = (TextView) findViewById(R.id.text);        new Thread(new Runnable() {            @Override            public void run() {                    //从网络获取数据                final String response = NetUtils.get("http://www.baidu.com");                    //向Handler发送处理操作                handler.post(new Runnable() {                    @Override                    public void run() {                            //在UI线程更新UI                        textView.setText(response);                    }                });            }        }).start();    }

在子线程进行耗时操作,完成后通过Handler将更新UI的操作发送到主线程执行。这就叫异步。Handler是一个Android线程模型中重要的东西,与网络无关便不说了。关于Handler不了解就先去Google一下。
关于Handler原理一篇不错的文章

但这样写好难看。异步通常伴随者他的好基友回调。
这是通过回调封装的Utils类。

    public class AsynNetUtils {        public interface Callback{            void onResponse(String response);        }        public static void get(final String url, final Callback callback){            final Handler handler = new Handler();            new Thread(new Runnable() {                @Override                public void run() {                    final String response = NetUtils.get(url);                    handler.post(new Runnable() {                        @Override                        public void run() {                            callback.onResponse(response);                        }                    });                }            }).start();        }        public static void post(final String url, final String content, final Callback callback){            final Handler handler = new Handler();            new Thread(new Runnable() {                @Override                public void run() {                    final String response = NetUtils.post(url,content);                    handler.post(new Runnable() {                        @Override                        public void run() {                            callback.onResponse(response);                        }                    });                }            }).start();        }    }

然后使用方法。

    private TextView textView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        textView = (TextView) findViewById(R.id.webview);        AsynNetUtils.get("http://www.baidu.com", new AsynNetUtils.Callback() {            @Override            public void onResponse(String response) {                textView.setText(response);            }        });

是不是优雅很多。
嗯,一个蠢到哭的网络请求方案成型了。
愚蠢的地方有很多:

  • 每次都new Thread,new Handler消耗过大
  • 没有异常处理机制
  • 没有缓存机制
  • 没有完善的API(请求头,参数,编码,拦截器等)与调试模式
  • 没有Https

HTTP缓存机制

缓存对于移动端是非常重要的存在。

  • 减少请求次数,减小服务器压力.
  • 本地数据读取速度更快,让页面不会空白几百毫秒。
  • 在无网络的情况下提供数据。
    缓存一般由服务器控制(通过某些方式可以本地控制缓存,比如向过滤器添加缓存控制信息)。通过在请求头添加下面几个字端:

Request

请求头字段 意义 If-Modified-Since: Sun, 03 Jan 2016 03:47:16 GMT 缓存文件的最后修改时间。 If-None-Match: “3415g77s19tc3:0” 缓存文件的Etag(Hash)值 Cache-Control: no-cache 不使用缓存 Pragma: no-cache 不使用缓存

Response

响应头字段 意义 Cache-Control: public 响应被共有缓存,移动端无用 Cache-Control: private 响应被私有缓存,移动端无用 Cache-Control:no-cache 不缓存 Cache-Control:no-store 不缓存 Cache-Control: max-age=60 60秒之后缓存过期(相对时间) Date: Sun, 03 Jan 2016 04:07:01 GMT 当前response发送的时间 Expires: Sun, 03 Jan 2016 07:07:01 GMT 缓存过期的时间(绝对时间) Last-Modified: Sun, 03 Jan 2016 04:07:01 GMT 服务器端文件的最后修改时间 ETag: “3415g77s19tc3:0” 服务器端文件的Etag[Hash]值

正式使用时按需求也许只包含其中部分字段。

客户端要根据这些信息储存这次请求信息。

然后在客户端发起请求的时候要检查缓存。遵循下面步骤:

这里写图片描述

注意服务器返回304意思是数据没有变动滚去读缓存信息。
曾经年轻的我为自己写的网络请求框架添加完善了缓存机制,还沾沾自喜,直到有一天我看到了下面2个东西。(/TДT)/

Volley&OkHttp

Volley&OkHttp应该是现在最常用的网络请求库。用法也非常相似。都是用构造请求加入请求队列的方式管理网络请求。

先说Volley:
Volley可以通过这个库进行依赖.
Volley在Android 2.3及以上版本,使用的是HttpURLConnection,而在Android 2.2及以下版本,使用的是HttpClient。
Volley的基本用法,网上资料无数,这里推荐郭霖大神的博客
Volley存在一个缓存线程,一个网络请求线程池(默认4个线程)。
Volley这样直接用开发效率会比较低,我将我使用Volley时的各种技巧封装成了一个库RequestVolly.
我在这个库中将构造请求的方式封装为了函数式调用。维持一个全局的请求队列,拓展一些方便的API。

不过再怎么封装 Volley 在功能拓展性上始终无法与 OkHttp 相比。
Volley 停止了更新,而OkHttp得到了官方的认可,并在不断优化。
因此我最终替换为了 OkHttp

OkHttp 用法见这里
很友好的 API 与详尽的文档。
这篇文章也写的很详细了。
OkHttp 使用 Okio 进行数据传输。都是 Square 家的。
但并不是直接用 OkHttp。Square 公司还出了一个 Retrofit 库配合 OkHttp 战斗力翻倍。

Retrofit&RestAPI

Retrofit 极大的简化了网络请求的操作,它应该说只是一个 Rest API管理库,它是直接使用 OKHttp 进行网络请求并不影响你对 OkHttp 进行配置。毕竟都是Square公司出品。
RestAPI 是一种软件设计风格。
服务器作为资源存放地。客户端去请求 GET,PUT, POST,DELETE 资源。并且是无状态的,没有 session 的参与。
移动端与服务器交互最重要的就是 API 的设计。比如这是一个标准的登录接口。
这里写图片描述

你们应该看的出这个接口对应的请求包与响应包大概是什么样子吧。
请求方式,请求参数,响应数据,都很清晰。
使用Retrofit这些API可以直观的体现在代码中。

这里写图片描述
然后使用Retrofit提供给你的这个接口的实现类 就能直接进行网络请求获得结构数据。

注意Retrofit2.0相较1.9进行了大量不兼容更新。google上大部分教程都是基于1.9的。这里有个2.0的教程。

教程里进行异步请求是使用Call。Retrofit最强大的地方在于支持RxJava。就像我上图中返回的是一个Observable。RxJava上手难度比较高,但用过就再也离不开了。Retrofit+OkHttp+RxJava配合框架打出成吨的输出,这里不再多说。

网络请求学习到这里我觉得已经到顶了。。

网络图片加载优化

对于图片的传输,就像上面的登录接口的avatar字段,并不会直接把图片写在返回内容里,而是给一个图片的地址。需要时再去加载。

如果你直接用HttpURLConnection去取一张图片,你办得到,不过没优化就只是个BUG不断demo。绝对不能正式使用。
注意网络图片有些特点:

  1. 它永远不会变
    一个链接对应的图片一般永远不会变,所以当第一次加载了图片时,就应该予以永久缓存,以后就不再网络请求。
  2. 它很占内存
    一张图片小的几十k多的几M高清无码。尺寸也是64*64到2k图。你不能就这样直接显示到UI,甚至不能直接放进内存。
  3. 它要加载很久
    加载一张图片需要几百ms到几m。这期间的UI占位图功能也是必须考虑的。
    说说我在上面提到的 RequestVolley 里做的图片请求处理(没错我做了,这部分的代码可以去github里看源码)。

三级缓存

网上常说三级缓存--服务器,文件,内存。不过我觉得服务器不算是一级缓存,那就是数据源嘛。

内存缓存

首先内存缓存使用LruCache。LRU是Least Recently Used 近期最少使用算法,这里确定一个大小,当Map里对象大小总和大于这个大小时将使用频率最低的对象释放。我将内存大小限制为进程可用内存的1/8.
内存缓存里读得到的数据就直接返回,读不到的向硬盘缓存要数据。

硬盘缓存

硬盘缓存使用DiskLruCache。这个类不在API中。得复制使用。
看见LRU就明白了吧。我将硬盘缓存大小设置为100M。

 @Override  public void putBitmap(String url, Bitmap bitmap) {      put(url, bitmap);      //向内存Lru缓存存放数据时,主动放进硬盘缓存里      try {          Editor editor = mDiskLruCache.edit(hashKeyForDisk(url));          bitmap.compress(Bitmap.CompressFormat.JPEG, 100, editor.newOutputStream(0));          editor.commit();      } catch (IOException e) {          e.printStackTrace();      }  }  //当内存Lru缓存中没有所需数据时,调用创造。  @Override  protected Bitmap create(String url) {      //获取key      String key = hashKeyForDisk(url);      //从硬盘读取数据      Bitmap bitmap = null;      try {          DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);          if(snapShot!=null){              bitmap = BitmapFactory.decodeStream(snapShot.getInputStream(0));          }      } catch (IOException e) {          e.printStackTrace();      }      return bitmap;  }

DiskLruCache的原理不再解释了(我还解决了它存在的一个BUG,向Log中添加的数据增删记录时,最后一条没有输出,导致最后一条缓存一直失效。)

硬盘缓存也没有数据就返回空,然后就向服务器请求数据。
这就是整个流程。
但我这样的处理方案还是有很多局限。

  • 图片未经压缩处理直接存储使用
  • 文件操作在主线程
  • 没有完善的图片处理API
  • 以前也觉得这样已经足够好直到我遇到下面俩。

Fresco&Glide

不用想也知道它们都做了非常完善的优化,重复造轮子的行为很蠢。
Fresco 是 Facebook 公司的黑科技。光看功能介绍就看出非常强大。使用方法官方博客说的够详细了。
真三级缓存,变换后的BItmap(内存),变换前的原始图片(内存),硬盘缓存。
在内存管理上做到了极致。对于重度图片使用的APP应该是非常好的。
它一般是直接使用SimpleDraweeView来替换ImageView,呃~侵入性较强,依赖上它 apk 包直接大1M。代码量惊人。

所以我更喜欢 Glide,作者是 bumptech。这个库被广泛的运用在 google 的开源项目中,包括2014年google I/O 大会上发布的官方 app。
这里有详细介绍。直接使用ImageView即可,无需初始化,极简的 API,丰富的拓展,链式调用都是我喜欢的。
丰富的拓展指的就是这个。
另外我也用过 Picasso。API 与 Glide 简直一模一样,功能略少,且有半年未修复的 BUG。

图片管理方案

再说说图片存储。不要存在自己服务器上面,徒增流量压力,还没有图片处理功能。
推荐七牛与阿里云存储(没用过其它 π__π )。它们都有很重要的一项图片处理。在图片Url上加上参数来对图片进行一些处理再传输。
于是(七牛的处理代码)

  public static String getSmallImage(String image){        if (image==null)return null;        if (isQiniuAddress(image)) image+="?imageView2/0/w/"+IMAGE_SIZE_SMALL;        return image;    }    public static String getLargeImage(String image){        if (image==null)return null;        if (isQiniuAddress(image)) image+="?imageView2/0/w/"+IMAGE_SIZE_LARGE;        return image;    }    public static String getSizeImage(String image,int width){        if (image==null)return null;        if (isQiniuAddress(image)) image+="?imageView2/0/w/"+width;        return image;    }

既可以加快请求速度,又能减少流量。再配合Fresco或Glide。完美的图片加载方案。
不过这就需要你把所有图片都存放在七牛或阿里云,这样也不错。

图片/文件上传也都是使用它们第三方存储,它们都有SDK与官方文档教你。
不过图片一定要压缩过后上传。上传1-2M大的高清照片没意义。


文/Jude95(简书作者)
原文:原文
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。


篇三:Android开源项目推荐之「网络请求哪家强」

原则

本篇说的网络请求专指 http 请求,在选择一个框架之前,我个人有个习惯,就是我喜欢选择专注的库,其实在软件设计领域有一个原则叫做 「单一职责原则」,跟我所说的「专注」不谋而合,一个库能把一件事做好就很不错了。现如今有很多大而全的库,比如这个库可以网络请求,同时又可以图片加载,又可以数据存储,又可以 View 注解等等,我们使用这种库当然方便了,但是你有没有想过?这样会使得你整个项目对它依赖性太强,万一以后这个库不维护了,或者中间某个模块出问题了,这个影响非常大,而且我一直认为大而全的框架可能某一块都做的不够好,所以我在选择的时候更喜欢专注某一领域的框架。

在上面原则的基础上,所以目前来说单纯的网络请求库就锁定在了 Volley、OkHttp、Retrofit 三个,android-async-http 的作者已经不维护,所以这里就不多说了,下面我们分别来说说这三个库的区别。

一、 OkHttp

我们知道在 Android 开发中是可以直接使用现成的 api 进行网络请求的,就是使用 HttpClient、HttpUrlConnection 进行操作,目前 HttpClient 已经被废弃,而 android-async-http 是基于 HttpClient 的,我想可能也是因为这个原因作者放弃维护。

而 OkHttp 是 Square 公司开源的针对 Java 和 Android 程序,封装的一个高性能 http 请求库,所以它的职责跟 HttpUrlConnection 是一样的,支持 spdy、http 2.0、websocket ,支持同步、异步,而且 OkHttp 又封装了线程池,封装了数据转换,封装了参数使用、错误处理等,api 使用起来更加方便。可以把它理解成是一个封装之后的类似 HttpUrlConnection 的一个东西,但是你在使用的时候仍然需要自己再做一层封装,这样才能像使用一个框架一样更加顺手。

OkHttp 的具体使用方法这里就不赘述,地址在这里:

http://square.github.io/okhttp/

二、 Volley

Volley 是 Google 官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持 HttpClient、HttpUrlConnection,甚至支持 OkHttp,具体方法可以看 Jake 大神的这个 Gist 文件:

https://gist.github.com/JakeWharton/5616899

而且 Volley 里面也封装了 ImageLoader ,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,对于稍复杂点的需求还是需要用到专门的图片加载框架。

Volley 也有缺陷,比如不支持 post 大数据,所以不适合上传文件。不过 Volley 设计的初衷本身也就是为频繁的、数据量小的网络请求而生!
关于 Volley 的具体用法可以见我很早在 GitHub 的一个 demo :

https://github.com/stormzhang/AndroidVolley

三、 Retrofit

Retrofit 是 Square 公司出品的默认基于 OkHttp 封装的一套 RESTful 网络请求框架,不了解 RESTful 概念的不妨去搜索学习下,RESTful 可以说是目前流行的一套 api 设计的风格,并不是标准。Retrofit 的封装可以说是很强大,里面涉及到一堆的设计模式,你可以通过注解直接配置请求,你可以使用不同的 http 客户端,虽然默认是用 http ,可以使用不同 Json Converter 来序列化数据,同时提供对 RxJava 的支持,使用 Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。

Retrofit 的具体使用方法与地址在这里:

http://square.github.io/retrofit/

四 、Volley VS OkHttp

毫无疑问 Volley 的优势在于封装的更好,而使用 OkHttp 你需要有足够的能力再进行一次封装。而 OkHttp 的优势在于性能更高,因为 OkHttp 基于 NIO 和 Okio ,所以性能上要比 Volley更快。

估计有些读者不理解 IO 和 NIO 的概念,这里姑且简单提下,这两个都是 Java 中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作,这种是最简单的也叫阻塞式 IO,还有一种就是你读你的,我程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式。

所以 NIO 当然要比 IO 的性能要好了, 而 Okio 是 Square 公司基于 IO 和 NIO 基础上做的一个更简单、高效处理数据流的一个库。

理论上如果 Volley 和 OkHttp 对比的话,我更倾向于使用 Volley,因为 Volley 内部同样支持使用 OkHttp ,这点 OkHttp 的性能优势就没了,而且 Volley 本身封装的也更易用,扩展性更好些。

五、 OkHttp VS Retrofit

毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。

六、 Volley VS Retrofit

这两个库都做了非常不错的封装,但是 Retrofit 解耦的更彻底,尤其 Retrofit 2.0 出来,Jake 对之前 1.0 设计不合理的地方做了大量重构,职责更细分,而且 Retrofit 默认使用 OkHttp ,性能上也要比 Volley 占优势,再有如果你的项目如果采用了 RxJava ,那更该使用 Retrofit 。

所以说这两个库相比,Retrofit 毫无疑问更有优势,你在能掌握两个框架的前提下该优先使用 Retrofit。但是个人认为 Retrofit 门槛要比 Volley 稍高些,你要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用 Volley 吧。

七、 总结

所以综上,如果以上三种网络库你都能熟练掌握,那么优先推荐使用 Retrofit ,前提是最好你们的后台 api 也能遵循 RESTful 的风格,其次如果你不想使用或者没能力掌握 Retrofit ,那么推荐使用 Volley ,毕竟 Volley 你不需要做过多的封装,当然如果你们需要上传大数据,那么不建议使用 Volley,否则你该采用 OkHttp 。

最后,我知道可能有些人会纠结 Volley 与 OkHttp 的选择,那是因为我认为 OkHttp 还是需要一定的能力做一层封装的,如果你有能力封装的话那不如直接用 Retrofit 了,如果没能力封装还是乖乖的用 Volley 吧,如果你能有一些不错的基于 OkHttp 封装好的开源库,那么另说了,Volley 与 OkHttp 怎么选择随你便呗。

最最后,以上只是我一家之言,如有误导,概不负责!欢迎讨论与交流。

0 0
原创粉丝点击