android网络操作I: OkHttp, Volley以及Gson
原文:Android Networking I: OkHttp, Volley and Gson
写这篇文章的动机
在安卓项目中有一个问题可能无法避免:网络。不管你是加载图片,请求API数据还是从因特网上获得一个字节,你都是在使用网络。
鉴于网络在安卓中的重要性与基础性,当今安卓开发者面临的问题之一就是使用何种解决方案。有许多优秀的库,你可以用各种方式把一个用在另一个之上。
之所以这么多的人致力于开发网络库是因为 Android framework所提供的办法 不够好,在旧版本中一团糟(Eclair, Froyo 和 Gingerbread),每次进行网络操作的时候,你都需要重复的写乱七八糟的代码。考虑到安卓所获取的强势地位,试图一次性解决所有问题的方案与库就开始出现了。
这篇文章的目的只是分享我的发现与经验,以及我所学之所得。也许能帮助到一些人。
这篇文章中我们将讨论其中的一个解决方案:OkHttp, Volley 和 Gson的组合。今后的文章中我们将讨论其他方案。
假设
-
和服务器的API交互是通过JSON的
-
你在使用Android Studio 和 Gradle
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是谷歌开发的。
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的。 推ter, 非死book 和 Snapch都采用了它。
这个解决方案在2015年还重要吗?
Volley/Gson的解决方案比较成熟,因为这是谷歌的解决方案,同时也因为出现在安卓开发者网站上,因此在2013到2014年都非常流行。到目前为止,这仍然是一个很好的选择,它是简单有效的。不过需要考虑一下的是Volley和Gson现在不怎么更新了。
我们可以从速度,简便性,以及可自定义程度等因素上去分析比较不同解决方案,以帮助我们决定使用哪一种。
你可能想尝试下一些其他的选择:
-
Android 网络操作II: OkHttp, Retrofit, Moshi 以及Picasso. (即将发表)
-
Android 网络操作III: ION (即将发表)
Github样例项目
一些资源