首页 > 代码库 > 理解Volley -- Android 学习之路

理解Volley -- Android 学习之路

sky-mxc总结 转载注明:http://blog.csdn.net/mxiaochao?viewmode=contents

  • 介绍
  • 优点
  • 工作原理
  • 请求类型
  • 使用步骤
  • 取消请求
  • 网络图片加载的三种方式
  • 代码实例

介绍

Android中的网络请求一般就是两种 HttpURLConnection 和HttpClient,不论是哪一种在使用的时候都是经过一系列的封装 很繁琐有没有,而Google在2013年推出的Volley网络请求框架 ,使网络请求更加的快捷,方便,只需创建队列,创建请求,将请求放入队列就可以了,volley所有的网络请求都是异步的,不需要再操作线程的问题,而且自带缓存,再也不担心OOM了 我们只需要关心逻辑代码就可以了,需要注意的是 Volley适用于频繁发送请求 但是数据量不大(小于3M)的情况 。

优点

  • 普通数据,json,图片的异步加载
  • 网络请求优先级处理
  • 硬盘缓存(普通数据,图片,json)
  • 与activity的生命周期联动 activity死了他就取消了

缺点

不适合数据量较大的网络操作

工作原理

先来看一张图
技术分享
在执行RequestQueue的add() 时 volley会开启一个缓存处理线程(cacheDispatcher)和一个网络调度线程池。
当request被添加到queue(队列)时,缓存处理线程会先在缓存中查看是否有缓存,如果有缓存就将缓存结果发送到主线程,没有缓存的话,就将request放到queue中执行请求
得到请求结果后将数据发送到主线程解析并将结果写入缓存,当然我们可以设置是否可以缓存
绿色的是 主线程,黄色的是缓存处理线程 ,橙色的是 网络请求线程

请求类型

  • StringRequest 返回字符串
  • JsonRequest 返回json对象
  • ImageRequest 返回Bitmap

使用步骤

  • 创建队列RquesetQueue
  • 创建请求
  • 将请求放入队列

取消请求

取消一个请求只需调用这个请求对象的cancel方法即可。请求取消后 ,响应将不会被调用(只是响应监听哦)。
如果我们有好多请求需要取消,那就得跟踪很多请求 ,岂不是很费精力,费内存吗,还好有另一种办法
volley中的每个请求都可以设置一个 tag (标签) 我们可以通过这个tag来取消一个或多个对应的请求 ,
调用queue的cancelAll(Tag);方法 就可以取消对应的请求了 ,(多个请求是可以设置相同的标签的)

示例代码

mLoginRequest.cancel();//取消单个请求
//为Request设置tag
 mLoginRequest.setTag("login");
 mNewsRequest.setTag("news");
 mMsgListRequest.setTag("msg");
 mMsgRequest.setTag("msg");
//添加到队列中
 mQueue.add(mLoginRequest);
 mQueue.add(mNewsRequest);
 mQueue.add(mMsgListRequest);
 mQueue.add(mMsgRequest);
    //取消队列中包含msg标签的请求
mQueue.cancelAll("msg");

使用注意:如果你要根据请求的响应去执行别的操作的话 就得慎重取消了,因为响应监听不会被调用

volley 加载图片的三种方式

  • NetworkImageView
  • ImageRequst
  • ImageLoader
    - ImageLoader 用到了缓存 ,对于LRU缓存我也不是理解的很透彻就不再赘述了
    - 使用loader 需要一个 ImageCache的缓存 ,上述的工作原理在这里就比较明显了,
    - 每当loader去get一个地址 都会先去ImageCahce访问 是否有缓存 ,如果缓存为空才会去网络加载
    在 ImageCache的getBitmap()方法返回为null的话就执行网络加载如果不为null就不执行网络请求了

代码实例

RequestQueue请求队列的创建
一个应用最好只有一个RequestQueue 实例,所有的请求都是在RequestQueue中进行的,为了更有效的执行请求,一个app中最好只有一个实例,这个我也不是理解的很清楚,看的麻烦指教一下。
RequestQueue 需要依赖于上文来创建的,这里使用简单的单例模式

/**
 * Created by sky-mxc
 */
public class Http {
    private Context context;
    private RequestQueue queue;
    private static Http http;

    private Http(Context context) {
        this.context = context;
        this.queue = Volley.newRequestQueue(context);
    }

    public static Http getInstance(Context context) {
        if (http == null) {
            http = new Http(context);
        }
        return http;
    }

}

StringRequest

StringRequest 的默认请求方式 是GET 如果需要 POST方式,指定即可 ,volley 重载了StringRequest的创建

get

 /**
*String请求get方式(default)
*@paramurl地址
*@paramsuccessListener成功监听
*@paramerrorListener失败监听
*/
public void execStringRequest(Stringurl,Response.Listener<String>successListener,Response.ErrorListenererrorListener){
 StringRequestgetRequest = new StringRequest(url,successListener,errorListener);
queue.add(getRequest);
}

调用

    /**
     * Get方式加载数据
     */
    private void loadData(){
        String url ="http://toolsmi.com/starclass/lessons";
        Http.getInstance(this).execStringRequest(url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
            if (!TextUtils.isEmpty(s)){
                Result<List<Lession>> result = JSON.parseObject(s,new TypeReference<Result<List<Lession>>>(){});
                Toast.makeText(MainActivity.this,result.describe,Toast.LENGTH_SHORT).show();
                lessions.addAll(result.data);
                adapter.notifyDataSetChanged();
            }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
            Toast.makeText(MainActivity.this,"网络访问出错,请稍后重试",Toast.LENGTH_SHORT).show();
            }
        });
    }

post 关于参数的传递 可以重写 getParams()方法 将参数封装为Map集合 返回即可

/**
  *  String 请求 post方式
  * @param url 地址
  * @param params 参数 Map结构
  * @param successListener 成功监听
  * @param errorListener 失败监听
  */
 public void execStringRequestPost(String url,final Map<String,String> params, Response.Listener<String> successListener, Response.ErrorListener errorListener){
     StringRequest getRequest = new StringRequest(Request.Method.POST,url,successListener ,errorListener){
         @Override
         protected Map<String, String> getParams() throws AuthFailureError {

             return params;
         }
     };
     queue.add(getRequest);

 }

调用

/**
 * 检查版本更新使用Post方式
 */
private void checkVersion(){
    String url ="http://toolsmi.com/starclass/ver";
    Map<String,String> params = new HashMap<>();
    params.put("ver",BuildConfig.VERSION_CODE+"");
    Http.getInstance(this).execStringRequestPost(url,params, new Response.Listener<String>() {
        @Override
        public void onResponse(String str) {
            Log.e("Tag","======onResponse=============="+str);
            //将字符串解析为对象
            if (!TextUtils.isEmpty(str)) {
                Result<VersionInfo> result = JSON.parseObject(str, new TypeReference<Result<VersionInfo>>() {
                });
                if (result.state ==1){
                    new AlertDialog.Builder(MainActivity.this).setMessage("目前版本"+BuildConfig.VERSION_NAME+",检查到新版本"+result.data.getVersionName()+",是否更新?")
                            .setPositiveButton("立即更新",null)
                            .setNegativeButton("下次再议",null)
                            .show();
                }else{
                    Toast.makeText(MainActivity.this,"目前已经是最新版本",Toast.LENGTH_SHORT).show();
                }
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError volleyError) {
            Toast.makeText(MainActivity.this,"网络出错,请稍后重试",Toast.LENGTH_SHORT).show();
        }
    });
}

JsonRequest

个人感觉Android原生的json操作有点繁琐,所以一般不使用,所以遇到需要解析json的请求都是用StringRequest 返回数据后使用 fastjson或Gson解析,看上面就知道了

ImageRequest

声明

/**
  * 执行图片的加载
  * @param url 地址
  * @param successListener 成功监听
  * @param maxWidth 最大宽度 px
  * @param maxHeight 最大高度 px
  * @param config 清晰度
  * @param errorListener 错误监听
  */
 public void execImageRequest(String url, Response.Listener<Bitmap> successListener, int maxWidth, int maxHeight, Bitmap.Config config, Response.ErrorListener errorListener){
     ImageRequest request = new ImageRequest(url,successListener,maxWidth,maxHeight, config,errorListener);
     queue.add(request);
}

调用

/**
 * ImageRequest加载图片
 */
private void loadImageRequest() {
    String url =etUrl.getText().toString();
Http.getInstance(this).execImageRequest(url,
                new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap bitmap) {
                if (bitmap!=null){
                    image0.setImageBitmap(bitmap);
                }
            }
        },
        (int) getResources().getDimension(R.dimen.image_w),
        (int)getResources().getDimension(R.dimen.image_h),
        Bitmap.Config.ARGB_8888,
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(MainActivity.this,"网络访问出错,请稍后重试",Toast.LENGTH_SHORT).show();
            }
        });
}

ImageLoader

通过这个看一下Volley的工作模式
在有缓存的情况下 就不会去执行网络请求 在ImageCache的gitBitmap()返回一个Bitmap 当然也就不会执行放入缓存的操作(putBitmap())

ImageLoader 在Http类中的定义

/**
 * 获取ImageLoad 实例
 * @param context 上下文
 * @param cache ImageCache 缓存
 * @return ImageLoader
 */
public static ImageLoader getLoader(Context context, ImageLoader.ImageCache cache){
    if (loader==null){
        loader = new ImageLoader(getInstance(context).queue,cache);
    }
    return loader;
}

调用

/**
 * 使用Loader加载图片
 */
private void loadImageLoader() {
    String url = etUrl.getText().toString();
    ImageLoader.ImageCache cache = new ImageLoader.ImageCache() {
        @Override
        public Bitmap getBitmap(String s) {
            Log.e("Tag","====getBitmap()从缓存加载====="+s);
            //((BitmapDrawable) (image0.getDrawable())).getBitmap()
            return null;
        }

        @Override
        public void putBitmap(String s, Bitmap bitmap) {
            Log.e("Tag","====putBitmap()放入缓存====="+s);
        }
    };
    ImageLoader loader = Http.getLoader(this,cache);
    loader.get(url, new ImageLoader.ImageListener(){

        @Override
        public void onErrorResponse(VolleyError volleyError) {
            Toast.makeText(MainActivity.this,"网络访问出错,请稍后重试",Toast.LENGTH_SHORT).show();
            Log.e("Tag",volleyError+"");
            image1.setImageResource(R.mipmap.jiantou);
        }

  /**
     * 这个方法会被调用两次 缓存处理一次 网络加载一次
     * @param imageContainer 网络请求信息
     * @param b 区分是缓存(true) 还是网络加载
     */
    @Override
    public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) {
        Log.e("Tag","===onResponse()======缓存加载:"+b+"====bitmap:"+imageContainer.getBitmap()+"=====url:"+imageContainer.getRequestUrl());

        Bitmap bmp = imageContainer.getBitmap();
        if (bmp!=null){//这里需要注意 如果没有使用缓存 从缓存中读取的Bitmap就是空的,
            image1.setImageBitmap(bmp);
        }

    }
},(int)getResources().getDimension(R.dimen.image_w),(int)getResources().getDimension(R.dimen.image_h));


}

在有缓存的情况下 就不会去执行网络请求 ;在ImageCache的gitBitmap()返回一个Bitmap 当然也就不会执行放入缓存的操作
在 有缓存的情况下的log日志:

E/Tag: ====getBitmap()从缓存加载=====#W100#H90http://www.codexiu.cn/static/blog/ad/4.jpg
 E/Tag: ===onResponse()======缓存加载:true====bitmap:android.graphics.Bitmap@631b631=====url:http://www.codexiu.cn/static/blog/ad/4.jpg

在无缓存的情况再去执行网络请求, gitBitmap() 返回null

无缓存下的log 日志:

  E/Tag: ====getBitmap()从缓存加载=====#W100#H90http://www.codexiu.cn/static/blog/ad/4.jpg
E/Tag: ===onResponse()======缓存加载:true====bitmap:null=====url:http://www.codexiu.cn/static/blog/ad/4.jpg
E/Tag: ====putBitmap()放入缓存=====#W100#H90http://www.codexiu.cn/static/blog/ad/4.jpg
E/Tag: ===onResponse()======缓存加载:false====bitmap:android.graphics.Bitmap@225e93b5=====url:http://www.codexiu.cn/static/blog/ad/4.jpg

即使没有缓存 Response 方法还是会被调用两次 缓存处理一次,,网络处理一次

NetworkImageView

这个控件继承自 ImageView 只不过增加了几个好用的方法 让我们用着更方便
布局定义

  <com.android.volley.toolbox.NetworkImageView
            android:id="@+id/image2"
           android:layout_width="@dimen/image_h"
        android:layout_height="@dimen/image_h" />

加载图片 ,加载时用到了ImageLoader 和ImageCache

 /**
   * 使用 NetWorkImageView
   */
  private void loadNetWorkImageView() {
      String url = etUrl.getText().toString();
      image2.setDefaultImageResId(R.mipmap.ic_launcher);//  网络加载前的占位图
      image2.setErrorImageResId(R.mipmap.jiantou);        //加载错误时提示图
      image2.setImageUrl(url,Http.getLoader(this, new ImageLoader.ImageCache() {
          @Override
          public Bitmap getBitmap(String s) {
              return null;
          }

          @Override
          public void putBitmap(String s, Bitmap bitmap) {

          }
      }));
 }

关于Volley的使用 我写了个Demo github地址:https://github.com/sky-mxc/AndroidDemo/tree/master/practicenetwork_volley

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    理解Volley -- Android 学习之路