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

jopen 9年前發布 | 323K 次閱讀 OkHttp Android開發 移動開發

原文:Android Networking I: OkHttp, Volley and 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的。 推ter, 非死book 和 Snapch都采用了它。

這個解決方案在2015年還重要嗎?

Volley/Gson的解決方案比較成熟,因為這是谷歌的解決方案,同時也因為出現在安卓開發者網站上,因此在2013到2014年都非常流行。到目前為止,這仍然是一個很好的選擇,它是簡單有效的。不過需要考慮一下的是Volley和Gson現在不怎么更新了。


我們可以從速度,簡便性,以及可自定義程度等因素上去分析比較不同解決方案,以幫助我們決定使用哪一種。


你可能想嘗試下一些其他的選擇:


  • Android 網絡操作II: OkHttp, Retrofit, Moshi 以及Picasso. (即將發表)

  • Android 網絡操作III: ION (即將發表)

Github樣例項目

一些資源


 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!