1. 程式人生 > >OkHttp使用教程——網路操作之OkHttp, Volley以及Gson

OkHttp使用教程——網路操作之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是谷歌開發的。

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

這就是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依賴的,只有原始碼包。

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

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)其實並沒有什麼卵用。

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

在安卓的網路世界裡,你並不孤獨。

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樣例專案

一些資源