Android网络之Volley的使用

Volley的概念及优点

Volley是一个 HTTP 库,它能够帮助 Android app 更方便地执行网络操作,最重要的是,它更快速高效。平时Android中用到网络操作,我们最熟悉的无非就是HttpURLConnection以及HttpClient(已经弃用),不过这两种方法的用法还是有一些复杂,而Volley大大地简化了网络操作,只需几行代码即可完成HttpURLConnection可完成的操作。 
它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作。

Volley 不适合用来下载大的数据文件。因为 Volley 会保持在解析的过程中所有的响应。对于下载大量的数据操作,请考虑使用 DownloadManager

Volley有如下的优点:

  • 自动调度网络请求。
  • 高并发网络连接。
  • 通过标准的 HTTP cache coherence(高速缓存一致性)缓存磁盘和内存透明的响应。
  • 支持指定请求的优先级
  • 撤销请求 API。我们可以取消单个请求,或者指定取消请求队列中的一个区域。
  • 框架容易被定制,例如,定制重试或者回退功能。
  • 强大的指令(Strong ordering)可以使得异步加载网络数据并正确地显示到 UI 的操作更加简单。
  • 包含了调试与追踪工具。

Volley的简单使用

使用Volley的方式是,建立一个RequestQueue(请求队列),再把Request对象传递给它,RequestQueue 管理用来执行网络操作的工作线程,从缓存中读取数据,写数据到缓存,并解析 Http 的响应内容。请求解析原始的响应数据,Volley 会把解析完的响应数据分发给主线程。

StringRequest的用法

Volley提供了一个简便地方法:Volley.newRequestQueue用来为你建立一个RequestQueue,使用默认值并启动这个队列。 
例如:

TextView mTextView = (TextView) findViewById(R.id.text);
...

// 建立一个请求队列
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.baidu.com";

// 从要请求的网络链接中请求返回String类型的数据
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener() {
    @Override
    public void onResponse(String response) {
        // 将返回的数据填充到TextView
        mTextView.setText("Response is: "+ response);
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
//将请求添加到请求队列
queue.add(stringRequest);

另外,由于Volley是要访问网络的,因此不要忘记在你的AndroidManifest.xml中添加如下权限:

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

就这样,我们就可以得到百度页面的Html代码。

如果我们仅仅是想做一个单次的请求并且不想要线程池一直保留,我们可以通过使用默认的方式(Volley.newRequestqueue)来发送一个简单的请求,在任何需要的时刻创建 RequestQueue,然后在我们的响应回调里面执行 stop() 方法来停止操作。

但是更通常的做法是创建一个 RequestQueue 并设置为一个单例。下面部分将演示这种做法。

使用单例模式

如果我们的应用需要持续地使用网络,更加高效的方式应该是建立一个 RequestQueue 的单例,这样它能够持续保持在整个 app 的生命周期中。我们可以通过多种方式来实现这个单例。推荐的方式是实现一个单例类,里面封装了 RequestQueue 对象与其它的 Volley 功能。另外一个方法是继承 Application 类,并在 Application.OnCreate() 方法里面建立 RequestQueue。但是我们并不推荐这个方法,因为一个 static 的单例能够以一种更加模块化的方式提供同样的功能。

一个关键的概念是 RequestQueue 必须使用 Application context 来实例化,而不是 Activity context。这确保了 RequestQueue 在我们 app 的生命周期中一直存活,而不会因为 activity 的重新创建而被重新创建(例如,当用户旋转设备时)。

下面是一个单例类,提供了 RequestQueue 与 ImageLoader 功能:

public class MySingleton {
    private static MySingleton mInstance;
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    private static Context mCtx;

    private MySingleton(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();

        mImageLoader = new ImageLoader(mRequestQueue,
                new ImageLoader.ImageCache() {
            private final LruCache<String, Bitmap>
                    cache = new LruCache<String, Bitmap>(20);

            @Override
            public Bitmap getBitmap(String url) {
                return cache.get(url);
            }

            @Override
            public void putBitmap(String url, Bitmap bitmap) {
                cache.put(url, bitmap);
            }
        });
    }

    public static synchronized MySingleton getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new MySingleton(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }
}

然后我们就可以利用单例类来建立一个RequestQueue:

RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
    getRequestQueue();
...

//添加请求到请求队列中去
MySingleton.getInstance(this).addToRequestQueue(stringRequest);

JsonRequest的用法

Volley提供了以下的类来请求Json数据:

  • JsonArrayRequest —— 一个为了获取给定 URL 的Json数组响应正文的请求。

  • JsonObjectRequest —— 一个为了获取给定 URL 的 Json对象响应正文的请求。允许传进一个可选的 JSONObject 作为请求正文的一部分。

    这两个类都是基于一个公共的基类JsonRequest。它们的用法和StringRequest几乎完全一样。


TextView mTxtDisplay;
mTxtDisplay = (TextView) findViewById(R.id.txtDisplay);
String url = "http://172.30.18.222:8080/weather.html";
//用单例类建立请求队列
RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
    getRequestQueue();

JsonObjectRequest jsObjRequest = new JsonObjectRequest
        (Request.Method.GET, url, null, new Response.Listener() {

    @Override
    public void onResponse(JSONObject response) {
        mTxtDisplay.setText("Response: " + response.toString());
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO Auto-generated method stub

    }
});

// 用单例类添加请求到请求队列
MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);

请求图片

Volley为请求图片提供了如下的类:

  • ImageRequest —— 一个封装好的,用来处理 URL 请求图片并且返回一张解完码的位图(bitmap)。它同样提供了一些简便的接口方法,例如指定一个大小进行重新裁剪。它的主要好处是 Volley 会确保类似 decode,resize 等耗时的操作在工作线程中执行。

  • ImageLoader —— 一个用来处理加载与缓存从网络上获取到的图片的帮助类。ImageLoader 是大量 ImageRequest 的协调器。例如,在 ListView 中需要显示大量缩略图的时候。ImageLoader 为通常的 Volley cache 提供了更加前瞻的内存缓存,这个缓存对于防止图片抖动非常有用。这还使得在不阻塞或者延迟主线程的前提下实现缓存命中(这对于使用磁盘 I/O是无法实现的)。ImageLoader 还能够实现响应联合(response coalescing),避免几乎每一个响应回调里面都设置bitmap 到 view 上面。响应联合使得能够同时提交多个响应,这提升了性能。

  • NetworkImageView —— 在 ImageLoader 的基础上建立,并且在通过网络 URL 取回的图片的情况下,有效地替换ImageView。如果 view 从层次结构中分离,NetworkImageView 也可以管理取消挂起请求。

ImageRequest的用法

下面是一个使用 ImageRequest 的示例。它会获取 URL 上指定的图片并显示到 app 上。注意到,里面演示的 RequestQueue 是通过单例类实现的:

 <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/ic_launcher"
        android:visibility="gone"/>
ImageView mImageView;
String url = "http://img3.imgtn.bdimg.com/it/u=715900948,1406530055&fm=21&gp=0.jpg";
mImageView = (ImageView) findViewById(R.id.imageView);
...

ImageRequest request = new ImageRequest(url,
    new Response.Listener() {
        @Override
        public void onResponse(Bitmap bitmap) {
            mImageView.setImageBitmap(bitmap);
        }
    }, 0, 0, null,
    new Response.ErrorListener() {
        public void onErrorResponse(VolleyError error) {
            mImageView.setImageResource(R.drawable.image_load_error);
        }
    });
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(request);

ImageRequest的用法也是非常简单,现在运行程序,发送网络请求,就会看到图片被加载出来啦 
这里写图片描述

ImageLoader的用法

我们也可以使用ImageLoader自身来显示一张图片。

ImageLoader mImageLoader;
ImageView mImageView;

private static final String IMAGE_URL =
    "hhttp://img3.imgtn.bdimg.com/it/u=715900948,1406530055&fm=21&gp=0.jpg";
...
mImageView = (ImageView) findViewById(R.id.regularImageView);

//通过单例类获得ImageLoder对象
mImageLoader = MySingleton.getInstance(this).getImageLoader();
mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView,
         R.drawable.def_image, R.drawable.err_image));

get()方法接收两个参数,第一个参数就是图片的URL地址,第二个参数则是单例类中封装好的ImageListener对象。getImageListener()方法中第一个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示的图片,第三个参数指定加载图片失败的情况下显示的图片。

运行程序也可以得到指定的那张图片。

NetworkImageView的用法

然而,如果我们要做的是为 ImageView 进行图片设置,那么我们也可以使用 NetworkImageView 来实现。

在 layout XML 文件中,我们以与使用 ImageView 差不多的方法使用 NetworkImageView:

 <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/networkImageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerHorizontal="true" />
ImageLoader mImageLoader;
NetworkImageView mNetworkImageView;
private static final String IMAGE_URL =
    "http://developer.android.com/images/training/system-ui.png";
...

//得到NetworkImageView控件
mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView);

//得到ImageLoader对象
mImageLoader = MySingleton.getInstance(this).getImageLoader();

// Set the URL of the image that should be loaded into this view, and
// specify the ImageLoader that will be used to make the request.

mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);

上面的代码是通过单例类来访问 RequestQueue 与 ImageLoader。这种方法保证了我们的 app 创建这些类的单例会持续存在于 app 的生命周期。这对于 ImageLoader(一个用来处理加载与缓存图片的帮助类)很重要的原因是:内存缓存的主要功能是允许非抖动旋转。使用单例模式可以使得 bitmap 的缓存比 activity 存在的时间长。如果我们在 activity 中创建 ImageLoader,这个 ImageLoader 有可能会在每次旋转设备的时候都被重新创建。这可能会导致抖动。

优化图片请求

Volley 工具箱中提供了一种通过 DiskBasedCache 类实现的标准缓存。这个类能够缓存文件到磁盘的指定目录。但是为了使用 ImageLoader,我们应该提供一个自定义的内存 LRC bitmap 缓存,这个缓存实现了 ImageLoader.ImageCache 接口。我们可能想把缓存设置成一个单例。

下面是一个内存 LruBitmapCache 类的实现示例。它继承 LruCache 类并实现了 ImageLoader.ImageCache 接口:

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import com.android.volley.toolbox.ImageLoader.ImageCache;

public class LruBitmapCache extends LruCache<String, Bitmap>
        implements ImageCache {

    public LruBitmapCache(int maxSize) {
        super(maxSize);
    }

    public LruBitmapCache(Context ctx) {
        this(getCacheSize(ctx));
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override
    public Bitmap getBitmap(String url) {
        return get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }

    // Returns a cache size equal to approximately three screens worth of images.
    public static int getCacheSize(Context ctx) {
        final DisplayMetrics displayMetrics = ctx.getResources().
                getDisplayMetrics();
        final int screenWidth = displayMetrics.widthPixels;
        final int screenHeight = displayMetrics.heightPixels;
        // 4 bytes per pixel
        final int screenBytes = screenWidth * screenHeight * 4;

        return screenBytes * 3;
    }
}

下面演示了如何实例化一个ImageLoader:

RequestQueue mRequestQueue; // assume this exists.
ImageLoader mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache(LruBitmapCache.getCacheSize()));

Volley+Gson解析复杂的Json数据:

这里面会用到一个Android Studio的插件GsonFormat,它可以自动的为你生成Json数据对应的Bean类,比起我们自己建立要快得多,一键搞定所有Bean类,妈妈从此不再担心我的学习!

  • 点击File–>Settings–>Plugins–>然后搜索GsonFormat,点击下载并安装。
  • 自定义个Javabean(就是新建一个名字最好带bean的无内容的class文件),然后在该文件处点击–>code–>Generate–>GsonFormat(或者直接按alt+s键[Window]),然后将JSON数据复制到该框框内。点击Format可以查看格式化后的效果(跳过)。直接点击OK即可。

    下面以和风天气为例,它返回的Json数据如下所示:

{"HeWeather data service 3.0":[{
...}]}

由于数据实在太多,就不贴出来了,但是这个开头十分之恶心,参考了别人的方法后,可以这么做:

//将开头的“HeWeather data service 3.0”中的空格去掉
 String str1=s.replaceAll(" ","");

此时json数据变成:

{"HeWeatherdataservice3.0":[{
...}]}

然后添加如下语句:

//将"dataservice3.0"去掉
String str2 = str1.replaceFirst("dataservice3.0","");

则Json数据变成我们好理解的样子了:

{"HeWeather":[{
...}]}

然后就可以开始解析!! 
Bean类由GsonFormat自动生成,下面给出完整代码,我这里只解析出城市名称,若有兴趣可以再一层层解析:

MainActivity.class类

public class MainActivity extends AppCompatActivity {
    String url = "https://api.heweather.com/x3/weather?cityid=CN101040100&key=xxxxxx";
    RequestQueue requestQueue;
    StringRequest stringRequest;
    Gson gson;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

       gson = new Gson();

        requestQueue = MySingleton.getInstance(this.getApplicationContext()).
                getRequestQueue();

        stringRequest = new StringRequest(url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                try{
                   String str1=s.replaceAll(" ","");
                   String str2 = str1.replaceFirst("dataservice3.0","");
                   Status myStatus = gson.fromJson(str2,Status.class);
                    Log.i("str2",str2);

                    List<Status.HeWeatherBean> heWeatherBean = myStatus.getHeWeather();

                    Status.HeWeatherBean.BasicBean basicBean = heWeatherBean.get(0).getBasic();

                    String city = basicBean.getCity();
                    Log.i("city",city);

                }catch(Exception e){
                    e.printStackTrace();
                }


            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
             //   textView.setText("i don't know");
            }
        });
        MySingleton.getInstance(this).addToRequestQueue(stringRequest);

    }
}

贴一下打印结果:

这里写图片描述

可以看到城市名已经完全地打印出来了,如果有兴趣大家可以逐层解析,这里就不继续了。

猜你喜欢

转载自blog.csdn.net/suyimin2010/article/details/81380332