Android学习之网络请求(访问网路数据)一

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请求服务器删除指定的页面
CONNECTHTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
OPTIONS允许客户端查看服务器的性能
TRACE回显服务器收到的请求,主要用于测试或诊断

常用的只有Post和get

GET和Post

网络请求中,我们常用键值对来传输参数(少部分api用json)
通过上面的介绍,可以看书虽然Post和get本意一个是表单提交一个是请求页面,但本质没有什么区别。

  • Get方式
    在url中填写参数:
 http://xxxx.xx.com/xx.php?params1=value1&params2=value2

甚至使用路由:

  http://xxxx.xx.com/xxx/value1/value2/value3
  • 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-urlencode的优越性就很明显了。不过x-www-form-urlencode只能传键值对,但是form-data可是传二进制

因为url是存在于请求行中的,所以Get和Post区别本质就是参数是放在请求行还是请求体中
当然无论用哪种都能放在请求头中国。一般在请求头中放一些发送端的常量

有人说:

  • Get是明文,Post隐藏
    移动端不是浏览器,不用https全都是明文。
  • Get传递数据上限XXX
    胡说。有限制的是浏览器中的url长度,不是Http协议,移动端请求无影响。Http服务器部分有限制的设置一下即可。
  • Get中文需要编码
    是真的…要注意。URLEncoder.encode(params, “gbk”);

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

上面说的是请求,下面说响应
请求是键值对,但返回数据我们常用Json。
对于内存中的结构数据,肯定要用数据描述语言将对象序列化文本,再用Http传递,接受端并从文本还原成结构数据
对象(服务器)< —– >文本(Http传输)< ——– >对象(移动端)

HttpURLConnection

HttpClient早被废弃,原因可看:http://blog.csdn.net/guolin_blog/article/details/12452307

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;
        }
    }
同步和异步

这两个概念仅存在于多线程中。
android中国默认只有一个主线程,也叫UI线程。因为View绘制只能在这个线程内进行。所以如果我们阻塞了这个线程,这期间View绘制将不能进行,UI就会卡。所以要避免在UI线程进行耗时操作
网络请求就是一个典型耗时操作
如果我们这样写:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String response = Utils.get("http://www.baidu.com");
    }

就会死,这是同步方式。Android不允许这样

异步方式:

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的操作发送到主线程执行。这就叫异步。

这样写好丑。异步通常伴随着他的好基友—>回调

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 2017 03:47:16 GMT缓存文件的最后修改时间
if-None-Match:”3415g77s19tc3:0”缓存文件的Etag(Hash)值
Cache-Control:on-cache不使用缓存
Pragma:no-cache不使用缓存

response

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

正式使用时按需求也许只包含其中部分字段
客户端要根据这些信息储存这次请求信息
然后在客户端发起请求的时候要检查缓存。
这里写图片描述

注意,服务器返回304意思是数据没有变动滚去读缓存信息

网络请求库


Volley&OkHttp
Volley在Android 2.3及以上版本,使用的是HttpURLConnection,而在Android 2.2及以下版本,使用的是HttpClient。
Volley存在一个缓存线程,一个网络请求线程池(默认4个线程)。

OkHttp使用Okio进行数据传输。都是Square家的。
但并不是直接用OkHttp。Square公司还出了一个Retrofit库配合OkHttp战斗力翻倍。

Retrofit&RestAPI
Retrofit极大的简化了网络请求的操作,它应该说只是一个Rest API管理库,它是直接使用OKHttp进行网络请求并不影响你对OkHttp进行配置。毕竟都是Square公司出品。
RestAPI是一种软件设计风格。
服务器作为资源存放地。客户端去请求GET,PUT, POST,DELETE资源。并且是无状态的,没有session的参与。

网络图片加载优化

网络图片的特点:

  1. 它永远不会变
    一个连接对应的图片一般永远不会变,所以当死一个加载了图片时,就应该予以永久缓存,以后就不再网络请求
  2. 它很占内存
    一张图片小的几十K,多的几M高清无码。尺寸也是64*64到2K图。我们不能就这样直接显示到UI,甚至不能直接放进内存
  3. 它要加载很久
    加载一张图片需要几百ms到几m。这期间的UI占位图功能也是必须考虑得
三级缓存

网上常说三级缓存– 服务器,文件,内存。

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

  • 硬盘缓存:
    硬盘缓存使用DiskLruCache。这个类不在API中。得复制使用。

@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;
  }

将硬盘缓存大小设置为100M

  • 硬盘缓存也没有数据就返回空,然后就向服务器请求数据

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

Glide,作者是bumptech。这个库被广泛的运用在google的开源项目中,包括2014年google I/O大会上发布的官方app。
直接使用ImageView即可,无需初始化,极简的API,丰富的拓展,链式调用


本篇来源:
作者:Jude95
链接:http://www.jianshu.com/p/3141d4e46240

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值