写这篇文章的动机

在安卓项目中有一个问题可能无法避免:网络。不管你是加载图片,请求API数据还是从因特网上获得一个字节,你都是在使用网络。

鉴于网络在安卓中的重要性与基础性,当今安卓开发者面临的问题之一就是使用何种解决方案。有许多优秀的库,你可以用各种方式把一个用在另一个之上。

之所以这么多的人致力于开发网络库是因为 Android framework所提供的办法 不够好,在旧版本中一团糟(Eclair, Froyo 和 Gingerbread),每次进行网络操作的时候,你都需要重复的写乱七八糟的代码。考虑到安卓所获取的强势地位,试图一次性解决所有问题的方案与库就开始出现了。

这篇文章的目的只是分享我的发现与经验,以及我所学之所得。也许能帮助到一些人。

这篇文章中我们将讨论其中的一个解决方案:OkHttp, Volley 和 Gson的组合。今后的文章中我们将讨论其他方案。

假设

OkHttp

OkHttp是一个现代,快速,高效的Http client,支持HTTP/2以及SPDY,它为你做了很多的事情。纵观一眼OkHttp为你实现的诸多技术如连接池,gziping,缓存等就知道网络相关的操作是多么复杂了。OkHttp扮演着传输层的角色。

OkHttp使用Okio来大大简化数据的访问与存储,Okio是一个增强
java.io 和 java.nio的库 。

OkHttp和Okio都是Square团队开发的。

OkHttp是一个现代,快速,高效的Http client,支持HTTP/2以及SPDY,扮演着传输层的角色。

Volley

Volley是一个简化网络任务的库。他负责处理请求,加载,缓存,线程,同步等问题。它可以处理JSON,图片,缓存,文本源,支持一定程度的自定义。

Volley是为RPC网络操作而设计的,适用于短时操作。

Volley默认在Froyo上使用Apache Http stack作为其传输层,在Gingerbread及之后的版本上使用HttpURLConnection stack作为传输层。原因是在不同的安卓版本中这两种http stack各自存在一些问题。

Volley可以轻松设置OkHttp作为其传输层。

Volley是谷歌开发的。

这就是Ficus Kirkpatrick(Volley背后的开发者)所描述的安卓网络操作:许许多多异步调用。

Gson

Gson 是一个JSON序列化与反序列化库,使用反射来把JSON对象转换成Java数据模型对象。你可以添加自己的序列化与反序列化来更好的控制与自定义。

Gson是谷歌开发的。

设置

Android Studio的gradle依赖

你需要在app模块的build.gradle文件中添加如下几行代码:

compile 'com.squareup.okio:okio:1.5.0'

compile 'com.squareup.okhttp:okhttp:2.4.0'

compile 'com.mcxiaoke.volley:library:1.0.16'

compile 'com.google.code.gson:gson:2.3.1'

其中的版本号可能随着它们的更新而发生改变。

除了Volley外,以上几个依赖都是官方的,虽然Volley不是官方提供的,但是也值得信赖。据我所知,Volley是没有官方的gradle依赖的,只有源码包。

Volley

Volley的工作方式是创建不同的request,然后把它们添加到队列中(queue)。一个项目只需要一个queue就足够了,每次你想创建一个request的时候你都只需要获得这个唯一的queue来添加。

我现在使用的是如下方法获得的全局的queue单例:

/**
 * Returns a Volley request queue for creating network requests
 *
 * @return {@link com.android.volley.RequestQueue}
 */
public RequestQueue getVolleyRequestQueue()
{
   if (mRequestQueue == null)
   {
      mRequestQueue = Volley.newRequestQueue(this, new OkHttpStack(new OkHttpClient()));
   }
   return mRequestQueue;
}

这里创建一个新请求队列的方法中我们使用了一个HttpStack参数。如果你不提供HttpStack参数Volley会根据API等级创建一个stack。( API level 9上是AndroidHttpClient , API level 10 及以上是HttpURLConnection )。

就如刚刚我提到的,我想使用OkHttp作为我们的传输层,所以我们使用OkHttpStack作为我们的参数之一。OkHttpClient的实现我们使用的是这个

接下来是添加请求(request)到Volley请求队列的一些方法:

/**
 * Adds a request to the Volley request queue with a given tag
 * 
 * @param request is the request to be added
 * @param tag is the tag identifying the request
 */
public static void addRequest(Request<?> request, String tag)
{
    request.setTag(tag);
    addRequest(request);
}/**
 * Adds a request to the Volley request queue
 * 
 * @param request is the request to add to the Volley queue
 */
public static void addRequest(Request<?> request)
{
    getInstance().getVolleyRequestQueue().add(request);    
}

下面这个方法则是取消请求的方法,通常用在生命周期的onStop方法中。

/**
 * Cancels all the request in the Volley queue for a given tag
 *
 * @param tag associated with the Volley requests to be cancelled
 */
public static void cancelAllRequests(String tag)
{
    if (getInstance().getVolleyRequestQueue() != null)
    {
        getInstance().getVolleyRequestQueue().cancelAll(tag);
    }
}

到此我们已经准备好了Volley和OkHttp。因此可以开始制做String,JsonObject或者JsonArray请求了。

一个JsonObject请求差不多是这样子的:

JsonObjectRequest jsonObjectRequest =
        new JsonObjectRequest(Request.Method.GET, mUrl, new Response.Listener<JSONObject>()
        {
            @Override
            public void onResponse(JSONObject response)
            {
                // Deal with the JSONObject here
            }
        },
        new Response.ErrorListener()
        {
            @Override
            public void onErrorResponse(VolleyError error)
            {
                // Deal with the error here
            }
        });

App.addRequest(jsonObjectRequest, mTAG);

我们还需要解析JSON对象成Java模型(model)。从Volley请求直接获得的响应(不管是String, JsonObject 还是 JsonArray)其实并没有什么卵用。

在安卓的网络世界里,你并不孤独。

Gson

我们可以通过自定义request来获得符合我们数据模型的java对象的响应。我们只需要一个继承自Request的GsonRequest类, 比如这个例子里面的这个

译者注:实际上下面代码中要用到的GsonRequest和上面那个例子中的GsonRequest并不完全一致。

下面是一个GET调用如何获得与解析Json object的例子:

/**
 * Returns a dummy object parsed from a Json Object to the success  listener and a Volley error to the error listener
 *
 * @param listener is the listener for the success response
 * @param errorListener is the listener for the error response
 *
 * @return @return {@link com.sottocorp.sotti.okhttpvolleygsonsample.api.GsonGetRequest}
 */
public static GsonRequest<DummyObject> getDummyObject
(
        Response.Listener<DummyObject> listener,
        Response.ErrorListener errorListener
)
{
    final String url = "http://www.mocky.io/v2/55973508b0e9e4a71a02f05f";

    final Gson gson = new GsonBuilder()
            .registerTypeAdapter(DummyObject.class, new DummyObjectDeserializer())
            .create();

    return new GsonRequest<>
            (
                    url,
                    new TypeToken<DummyObject>() {}.getType(),
                    gson,
                    listener,
                    errorListener
            );
}

下面是一个GET调用如何取得与解析Json数组的例子:

/**
 * Returns a dummy object's array in the success listener and a Volley error in the error listener
 *
 * @param listener is the listener for the success response
 * @param errorListener is the listener for the error response
 *
 * @return @return {@link com.sottocorp.sotti.okhttpvolleygsonsample.api.GsonGetRequest}
 */
public static GsonRequest<ArrayList<DummyObject>> getDummyObjectArray
(
        Response.Listener<ArrayList<DummyObject>> listener,
        Response.ErrorListener errorListener
)
{
    final String url = "http://www.mocky.io/v2/5597d86a6344715505576725";

    final Gson gson = new GsonBuilder()
            .registerTypeAdapter(DummyObject.class, new DummyObjectDeserializer())
            .create();

    return new GsonRequest<>
            (
                    url,
                    new TypeToken<ArrayList<DummyObject>>() {}.getType(),
                    gson,
                    listener,
                    errorListener
            );
}

Gson会在后台线程解析一个GsonRequest,而不是主线程中。

上面的例子中,我提供了一个deserializer(反序列化,即解析工具,这里就是指的DummyObjectDeserializer),但是这并不强制必须要提供erializers活着deserializers,只要类的域名和JSON文件相匹配,Gson可以自动处理好一切。我比较喜欢自己提供自定义的serializer/deserializer 。

上面的两个例子都是用的GET调用。为了以防调用是POST的,我在项目中包含了一个GsonPostRequest 以及用法示例 。

OkHttp works as the transport layer for Volley, which on top of OkHttp is a handy way of making network requests that are parsed to Java objects by Gson just before delivering the response to the main

加载图片

ImageLoader 与 NetworkImageView

Volley中有一个叫做NetworkImageView(ImageView的子类)的自定义View,用它加载图片非常方便。你可以设置一个URL,一张默认的空白占位图,以及提示加载错误的图片。

mNetworkImageView = (NetworkImageView) itemView.findViewById(R.id.networkImageView);
mNetworkImageView.setDefaultImageResId(R.drawable.ic_sun_smile);
mNetworkImageView.setErrorImageResId(R.drawable.ic_cloud_sad);
mNetworkImageView.setImageUrl(imageUrl, App.getInstance().getVolleyImageLoader());

代码中比较重要的部分是setImageUrl 方法,它接收两个参数:图片的地址以及一个ImageLoader(从远程地址加载和缓存图片的Volley帮助类),让我们看看我们定义的getVolleyImageLoader方法是如何获得一个ImageLoader的:

/**
 * Returns an image loader instance to be used with Volley.
 *
 * @return {@link com.android.volley.toolbox.ImageLoader}
 */
public ImageLoader getVolleyImageLoader()
{
    if (mImageLoader == null)
    {
        mImageLoader = new ImageLoader
                (
                        getVolleyRequestQueue(),
                        App.getInstance().getVolleyImageCache()
                );
    }

    return mImageLoader;
}

这里唯一没有讲到的就是这个LruBitmapCache。Volley并没有实现提供这个类的实现,但是我们可以从这里找到,它可以针对不同的屏幕设置不同的缓存大小,这点很酷。

译者注:为了方便对英语不熟悉的同学,我把提到的这篇文章中的代码拷贝在下面,不过仍然建议读一读原文:

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

ImageRequest

某些情况下,我们可能不像使用NetworkImageView。比如我们想要一个圆形的图片,同时我们使用的是CircleImageView。这种情况下,我们必须使用ImageRequest,使用方法如下:

final CircleImageView circleImageView =
            (CircleImageView) findViewById(R.id.circularImageView);

    // Retrieves an image specified by the URL, displays it in the UI.
    final com.android.volley.toolbox.ImageRequest imageRequest =
            new ImageRequest
            (
                    mImageUrl,
                    new Response.Listener<Bitmap>()
                    {
                        @Override
                        public void onResponse(Bitmap bitmap)
                        {
                            circleImageView.setImageBitmap(bitmap);
                        }
                    },
                    0,
                    0,
                    ImageView.ScaleType.CENTER_INSIDE,
                    null,
                    new Response.ErrorListener()
                    {
                        public void onErrorResponse(VolleyError error)
                        {          circleImageView.setImageResource(R.drawable.ic_cloud_sad);
                        }
                    }
            );
    // Access the RequestQueue through your singleton class.
    App.getInstance().getVolleyRequestQueue().add(imageRequest);
}

Curiosities

  • 本文所讨论的所有组建(Okio, OkHttp, Volley 和 Gson)都是可以单独使用的,它们并非一定要在一起使用。

  • 在引言部分我提到的第一篇文章(这篇)的作者是Jesse
    Wilson
    。Jesse Wilson是 HTTP, Gson, OkHttp 和 Okio项目的参与者之一。我觉得应该提一下它。

  • OkHttp引擎在Android 4.4上是基于HttpURLConnection的。 Twitter, Facebook 和 Snapch都采用了它。

这个解决方案在2015年还重要吗?

Volley/Gson的解决方案比较成熟,因为这是谷歌的解决方案,同时也因为出现在安卓开发者网站上,因此在2013到2014年都非常流行。到目前为止,这仍然是一个很好的选择,它是简单有效的。不过需要考虑一下的是Volley和Gson现在不怎么更新了。

我们可以从速度,简便性,以及可自定义程度等因素上去分析比较不同解决方案,以帮助我们决定使用哪一种。

你可能想尝试下一些其他的选择:

  • Android 网络操作II: OkHttp, Retrofit, Moshi 以及Picasso. (即将发表)

  • Android 网络操作III: ION (即将发表)

Github样例项目

一些资源

OkHttp使用教程——网络操作之OkHttp, Volley以及Gson的更多相关文章

  1. 安卓开发常用网络请求框架OkHttp、Volley、XUtils、Retrofit对比

    网络请求框架总结1.xutils     此框架庞大而周全,这个框架可以网络请求,同时可以图片加载,又可以数据存储,又可以 View 注解,使用这种框架很方便,这样会使得你整个项目对它依赖性太强,万一 ...

  2. 深入理解OkHttp源码(三)——网络操作

    这篇博客侧重于了解OkHttp的网络部分,包括Socket的创建.连接,连接池等要点.OkHttp对Socket的流操作使用了Okio进行了封装,本篇博客不做介绍,想了解的朋友可以参考拆轮子系列:拆O ...

  3. OkHttp使用教程

    Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient.关于HttpURLConnection和HttpClient的选择>>官方博客尽管Go ...

  4. 安卓网络请求之——OkHttp学习

    之前做安卓项目的时候,HTTP请求用的是android api中的HttpURLConnection和HttpClient,编码比较繁琐,自己封装的也不好.后来知道有很多网络请求的第三方框架,可以方便 ...

  5. 学习RxJava+Retrofit+OkHttp+MVP的网络请求使用

    公司的大佬用的是这一套,那我这个菜鸟肯定要学习使用了. 我在网上找了很多文章,写的都很详细,比如 https://www.jianshu.com/u/5fd2523645da https://www. ...

  6. OkHttp使用进阶(译自OkHttp官方教程)

    没有使用过OkHttp的,可以先看OkHttp使用介绍 英文版原版地址 Recipes · square/okhttp Wiki 同步get 下载一个文件,打印他的响应头,以string形式打印响应体 ...

  7. Retrofit+Okhttp+RxJava打造网络请求之Post

    之前一直在准备Android培训的事情,所幸的是终于完事啦,在这过程中真的发现了自身无论从沟通能力还是技术能力上很多的不足,就用一句 路漫漫其修远兮,吾将上下而求索 来勉励自己吧.之前也在项目上用上O ...

  8. Android Volley和Gson实现网络数据加载

    Android Volley和Gson实现网络数据加载 先看接口 1 升级接口 http://s.meibeike.com/mcloud/ota/cloudService POST请求 参数列表如下 ...

  9. Base-Android快速开发框架(四)--网络操作之FastJson以及AsyncHttpClient

    Android的展示数据,除了上章所讲的本地存储外,大部分数据都来自于网络.首先介绍一下Android APP开发常见的网络操作方式.从网络层面上有底层的tcp/ip,也就是我们常见的socket套接 ...

随机推荐

  1. python 将验证码保存到本地 读取 写入

    #验证码 #读取验证码网址.打开本地路径.写入.输入验证码 downpicture = urllib.request.urlopen(SecretCodeUrl).read() local = ope ...

  2. Azure Functions + Azure Batch实现MP3音频转码方案

    客户需求 客户的环境是一个网络音乐播放系统,根据网络情况提供给手机用户收听各种码率的MP3歌曲,在客户没购买歌曲的情况下提供一个三十秒内的试听版本.这样一个系统非常明确地一个需求就是会定期需要将一批从 ...

  3. Model和ModelAndView

    在请求处理方法可出现和返回的参数类型中,最重要的就是Model和ModelAndView.对于MVC框架,控制器Controller执行业务逻辑,用于产生模型数据Model,而试图View则用于渲染模 ...

  4. Python学习笔记(二):字典

    字典由多个键及与其对应的值构成的键值对构成,字典中键唯一,值不唯一. 1)dict 函数: >>>items=[('name','lilei'),('age',12)] >&g ...

  5. 怎么改变title属性的样式?

    我们经常会设置title属性来显示提示的内容,最常见的一种就是超过文本框的内容显示省略号,鼠标移上去显示完整的内容,这里顺便说下显示省略号的设置,如 div{text-overflow:ellipsi ...

  6. 完全总结bash中的条件判断test [ [[ 使用

    在bash脚本编程中,我们经常做一些条件判断, 我们主要用到了三种,test,单中括号,双中括号 经常有看到不同的写法,如: [ $? –eq ] [[ $myvar == “mysql” ]] te ...

  7. Spring整合quartz框架实现任务定时调度

    1.  首先需要引入需要的jar包,如上图所示. 2. 编写需要定时调度的测试类: package com.jp.task; import java.util.Date; public class T ...

  8. CTP期货期权交易开发

    CTP交易部分接口说明 综合交易平台(Comprehensive Transaction Platform,CTP)是专门为期货公司开发的一套期货经纪业务管理系统,由交易.风险控制和结算三大系统组成. ...

  9. POJ置换群入门[3/3]

    POJ 3270 Cow Sorting 题意: 一个序列变为升序,操作为交换两个元素,代价为两元素之和,求最小代价 题解: 看了黑书... 首先循环因子分解 一个循环完成的最小代价要么是循环中最小元 ...

  10. 走进JavaScript——重拾函数

    创建函数 通过构造器的方式来创建函数,最后一个参数为函数体其他为形参 new Function('a','b','alert(a)') /* function anonymous(a,b) { ale ...