Android网络要心路历程

网要是android客户端非常关键之一对。下面从入门级开始介绍下好Android网络要的实践进程。希望能被刚接触Android网络有的恋人有帮助。
正文包含:

  • HTTP请求&响应
  • Get&Post
  • [HttpClient & HttpURLConnection](#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的用法看这里。

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的计划性。比如就是一个正规的记名接口。

Paste_Image.png

你们应当看的来之接口对应的请保管以及应包约是呀样子吧。
告方式,请求参数,响应数据,都死清楚。
应用Retrofit这些API可以直观的体现在代码中。

Paste_Image.png

然后使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大的高清照片没有意义。