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( 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請求佇列的一些方法:
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 (即將發表)