1. 程式人生 > >android網路操作I: OkHttp, Volley以及Gson

android網路操作I: OkHttp, Volley以及Gson

寫這篇文章的動機

在安卓專案中有一個問題可能無法避免:網路。不管你是載入圖片,請求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單例:

1 2 3 4 5 6 7 8 9 10 11 12 13 /** * Returns a Volley request queue for creating network requests * * @return {@link com.android.volley.RequestQueue} */ public RequestQueue getVolleyRequestQueue() { if (mRequestQueue == null) { mRequestQueue = Volley.newRequestQueue(thisnew OkHttpStack(new OkHttpClient())); } return mRequestQueue; }

這裡建立一個新請求佇列的方法中我們使用了一個HttpStack引數。如果你不提供HttpStack引數Volley會根據API等級建立一個stack。( API level 9上是AndroidHttpClient , API level 10 及以上是HttpURLConnection )。

就如剛剛我提到的,我想使用OkHttp作為我們的傳輸層,所以我們使用OkHttpStack作為我們的引數之一。OkHttpClient的實現我們使用的是這個

接下來是新增請求(request)到Volley請求佇列的一些方法:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * 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方法中。

1 2 3 4 5 6 7 8 9 10 11 12 /** * 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請求差不多是這樣子的:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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的例子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /** * 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 Gson gson = new GsonBuilder() .registerTypeAdapter(DummyObject.class, new DummyObjectDeserializer()) .create(); return new GsonRequest<> ( url, new TypeToken<DummyObject>() {}.getType(), gson, listener, errorListener ); }

下面是一個GET呼叫如何取得與解析Json陣列的例子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /** * 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 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,一張預設的空白佔位圖,以及提示載入錯誤的圖片。

1 2 3 4 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的:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** * 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並沒有實現提供這個類的實現,但是我們可以從這裡找到,它可以針對不同的螢幕設定不同的快取大小,這點很酷。

譯者注:為了方便對英語不熟悉的同學,我把提到的這篇文章中的程式碼拷貝在下面,不過仍然建議讀一讀原文:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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,使用方法如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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樣例專案

一些資源