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樣例項目
一些資源