Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 淺談Retrofit封裝-讓框架更加簡潔易用

淺談Retrofit封裝-讓框架更加簡潔易用

編輯:關於Android編程

不知不覺在在這家公司快三個月了,剛接手項目的時候是真的被裡面的代碼惡心到了,網絡請求用的原生的httpURLconnection。這本來什麼,關鍵是根本就沒有進行任何封裝。activity裡面充斥著大量的handler,要找個oncreated()函數先得把類拉到一半,那種感覺豈止酸爽。由於項目周期緊張。沒時間去大規模的重構,新框架只能在新功能裡寫。采用了retrofit,初期做了簡單的封裝,斷斷續續一段時間的優化整理。現在分享出來給大家。

retrofit獨樹一幟的把請求采用了接口,方法和注解參數(parameter annotations)來聲明式定義一個請求應該如何被創建的方式。
像這樣

public interface GitHub {
      @GET("/repos/{owner}/{repo}/contributors")
        List contributors(@Path("owner")
        String owner, @Path("repo")
        String repo);

然後去實例化並且調用請求

  GitHub github = restAdapter.create(GitHub.class);
        // Fetch and print a list of the contributors to this library.
        List contributors = github.contributors("square",
                "retrofit");

先不談retrofit到底做了多少優化、性能上有多少提升。光是這樣的調用方式我就受不了。我特麼得寫多少個 像上面的GitHub 一樣的Service,就算是把url注解方法都寫在一個裡面,那也得多少行?一個項目六七十行應該是沒什麼問題的了。嗯。反正我會看瘋了的。而且這樣的一種調用方式是直接面向框架層的,以後萬一我們換框架了怎麼辦?代碼挨個找出來全換一邊?你瘋不瘋?
那我們有沒有什麼辦法改變他?很簡單,我們在框架外面再套一層通用框架,作為框架的設計者,我們應該讓調用者知道怎麼調用就可以了,而不應該讓調用者去考慮底層實現的細節。
好在retrofit提供了Url 參數替換注解@Url String url,通過這個注解我們可以動態的設置請求的url。
下面列出一些簡單的參數注解

@Url 替換url
@QueryMap  替換url中查詢參數
@Header  替換header
@FieldMap 替換post請求body中參數
@FormUrlEncoded post請求需要加的方法注解
@POST() 標示該方法為post請求
@GET() 標示該方法為get請求

了解了這些注解這樣我們就可以將我們項目的請求變成幾個基本的方法,由於我的項目的服務端返回的基本格式不是固定的,所以我的項目每個請求方式會有兩個方法

public interface RetrofitHttpService {

    @GET()
    Call get(@Url String url, @QueryMap Map params, @Header("Cache-Time") String time);

    @GET()
    Call getArray(@Url String url, @QueryMap Map params, @Header("Cache-Time") String time);

    @FormUrlEncoded
    @POST()
    Call post(@Url String url, @FieldMap Map params, @Header("Cache-Time") String time);

    @FormUrlEncoded
    @POST()
    Call postArray(@Url String url, @FieldMap Map params, @Header("Cache-Time") String time);
}

如果你項目的返回請求外層有固定的格式可以把 Call替換成Call,這裡的model就是你的基礎數據返回類型。
如果你要使用rxjava的話需要額外四個請求方法

 @GET()
    Observable Obget(@Url String url, @QueryMap Map params, @Header("Cache-Time") String time);

    @GET()
    Observable ObgetArray(@Url String url, @QueryMap Map params, @Header("Cache-Time") String time);

    @FormUrlEncoded
    @POST()
    Observable Obpost(@Url String url, @FieldMap Map params, @Header("Cache-Time") String time);

    @FormUrlEncoded
    @POST()
    Observable ObpostArray(@Url String url, @FieldMap Map params, @Header("Cache-Time") String time);

構建網絡數據請求類

public class HttpUtil {
    private static RetrofitHttpService mInstance;
    private static Context mAppliactionContext;
    private static String BASE_URL = "";

    public static RetrofitHttpService getmInstance(Context context) {
        try {//防止傳入的是activity的上下文
            Activity activity = (Activity) context;
            mAppliactionContext = context.getApplicationContext();
        } catch (Exception e) {
            e.printStackTrace();
            mAppliactionContext = context;
        }
        if (mInstance == null)
            mInstance = SingletonHolder.sInstance;
        return mInstance;
    }

    //構造函數私有,不允許外部調用
    private HttpUtil() {
    }
 private static class SingletonHolder {

        private static final RetrofitHttpService sInstance = createRetrofitHttpService();

        public static RetrofitHttpService createRetrofitHttpService() {
            OkHttpClient client = new OkHttpClient.Builder()
                    .retryOnConnectionFailure(true)
                    .connectTimeout(8, TimeUnit.SECONDS)
                    .readTimeout(5, TimeUnit.SECONDS)
                    .writeTimeout(5, TimeUnit.SECONDS)
                    .build();
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL + "/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(client)
                    .build();

            RetrofitHttpService Service =
                    retrofit.create(RetrofitHttpService.class);
            return Service;
        }
    }
}

一般網絡請求都是異步的,retrofit裡面提供了異步回調接口類,不過我們不能直接拿來用,因為一旦我們換網絡請求框架。肯定是不能再使用舊的框架裡面的回調接口的,所以需要我們定義自己的外層回調接口,一般有成功的回調和失敗的回調兩種。由於我支持lambda,所以我會把不同的情況分別定義成接口,
成功回調,一般就是返回的json串,一個參數就夠了

@FunctionalInterface
public interface Success {
    void Success( String model);
}

失敗回調,失敗回調不同的網絡框架返回的參數個數不一樣,這裡我們定義成Object數組

@FunctionalInterface
public interface Error {
    void Error(Object... values);
}

下面我們封裝post請求

  public static void post(String url, Map params, String cacheTime,Success mSuccessCallBack ,Error mErrorCallBack){
   mInstance.post(url, params, cacheTime).enqueue(new Callback() {
                @Override
                public void onResponse(Call call, Response response) {
                    if (response.code() == 200) {
                        mSuccessCallBack.Success(response.body().toString());
                    } else {
                        mErrorCallBack.Error(response.code(), message(response.message()), null);
                    }
                }

                @Override
                public void onFailure(Call call, Throwable t) {
                    mErrorCallBack.Error(200, message(t.getMessage()), t);
                }
            });}

這樣我們每次調用post的時候只要調用這個方法就可以了,其他幾個請求方法一樣的封裝方式。
但是這樣我還是覺得不夠簡潔,不夠優雅,代碼太寬,太多了。兩個回調函數看起來也太臃腫了。有時候我們是只有一個url的,有時候我們卻還會有查詢參數的,有時候我們的url是要追加版本號的,有時候我們是需要當前接口支持緩存的,而有的項目是需要統一追加各種配置參數的,如設備號,手機號碼等等。這樣我們的請求方法的參數個數就是不固定的,比較暴力的方法就是寫多個不同參數的重載方法。我以前一般是這麼干的,不過太low,這裡我們采用builder模式。

  public static class Builder {
        Map params = new HashMap<>();
        String url;
        Error mErrorCallBack;
        Success mSuccessCallBack;
        String cacheTime;
        boolean addVersion = false;

        public Builder CacheTime(String time) {
            this.cacheTime = time;
            return this;
        }

        public Builder Url(String url) {
            this.url = url;
            return this;
        }

        public Builder Params(Map params) {
            this.params.putAll(params);
            return this;
        }

        public Builder Params(String key, String value) {
            this.params.put(key, value);
            return this;
        }

        public Builder Success(Success success) {
            this.mSuccessCallBack = success;
            return this;
        }

        public Builder Version() {
            this.addVersion = true;
            return this;
        }

        public Builder Failure(Failure failure) {
            this.mFailureCallBack = failure;
            return this;
        }

        public Builder Error(Error error) {
            this.mErrorCallBack = error;
            return this;
        }

        public Builder() {
            this.setParams();
        }

        public Builder(String url) {
            this.setParams(url);
        }

        private void setParams() {
            this.setParams(null);
        }

        private void setParams(String url) {
            this.url = url;
            this.params = new HashMap<>();
            this.mErrorCallBack = (v) -> {
            };
            this.mSuccessCallBack = (s) -> {
            };
        }


        private String checkUrl(String url) {
            if (Util.checkNULL(url)) {
                throw new NullPointerException("absolute url can not be empty");
            }
            if (addVersion && !url.contains(mVersionApi)) {
                url = V(url);
            }
            return url;
        }

        public void get() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            mInstance.get(url, checkParams(params), cacheTime).enqueue(new Callback() {
                @Override
                public void onResponse(Call call, Response response) {
                    if (response.code() == 200) {
                        mSuccessCallBack.Success(response.body().toString());
                    } else {
                        mErrorCallBack.Error(response.code(), message(response.message()), null);
                    }
                }

                @Override
                public void onFailure(Call call, Throwable t) {
                  mErrorCallBack.Error(200, message(t.getMessage()), t);
                }
            });
        }}

checkParams()方法就是對參數的統一處理,追加參數什麼的可以在這裡面做,Version()方法 是決定要不要給url追加開發版本號。寫了這麼一大堆,貌似代碼變多了啊。別擔心,下面我們看看當我們調用請求的時候的代碼是多少

  new HttpUtil.Builder("favorite_authorized/list?page=1")
                    .Version()
                    .CacheTime("3600*24")
                    .Params("carId", sb.toString())
                    .Params(sortMap_)
                    .Success((s) -> {
                        ld_.dismiss();
                        BaseModel model = new BaseModel(s);
                    })
                    .Error((v) -> {
                        ld_.dismiss();
                        handler_.obtainMessage(MSG, v[1]).sendToTarget();
                    })
                    .get();
        });

看到沒?就這麼多,采用鏈式調用,上面我們動態設置了請求的url,參數,追加開發版本號,追加參數,設置了緩存一天,代碼簡潔明了,
如果你的項目需要支持緩存,請參照okhttp之自定義Interceptor:緩存攔截器 這篇博客
如果你項目需要失敗重試和切換服務器IP並且服務端不支持需要客戶端配置實現,請參照 okhttp之自定義Interceptor:請求失敗切換IP重試攔截器

下面貼出完整的httputil類

package com.sunshine.retrofit;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.telephony.TelephonyManager;
import android.util.Log;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.sunshine.retrofit.cacahe.CacheProvide;
import com.sunshine.retrofit.interceptor.CacheInterceptor;
import com.sunshine.retrofit.interceptor.RetryAndChangeIpInterceptor;
import com.sunshine.retrofit.interfaces.Error;
import com.sunshine.retrofit.interfaces.Failure;
import com.sunshine.retrofit.interfaces.Success;
import com.sunshine.utillibrary.Util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Observable;

/**
 * Created by 耿 on 2016/6/28.
 */
public class HttpUtil {
    private static RetrofitHttpService mInstance;
    private static Context mAppliactionContext;
    private static String BASE_URL = "";
    public static List SERVERS = new ArrayList<>();
    private static String mVersionApi = "api/v245/";

    public static RetrofitHttpService getmInstance(Context context) {
        try {//防止傳入的是activity的上下文
            Activity activity = (Activity) context;
            mAppliactionContext = context.getApplicationContext();
        } catch (Exception e) {
            e.printStackTrace();
            mAppliactionContext = context;
        }
        if (mInstance == null)
            mInstance = SingletonHolder.sInstance;
        return mInstance;
    }

    //構造函數私有,不允許外部調用
    private HttpUtil() {
    }

    public static void setBaseUrl(String baseUrl) {
        BASE_URL = baseUrl;
    }

    public static void setSERVERS(List sERVERS) {
        SERVERS = sERVERS;
    }

    public static void setVersionApi(String versionApi) {
        mVersionApi = versionApi;
    }



    public static Map checkParams(Map params) {
        if (params == null) {
            params = new HashMap<>();
        }

        if (!params.containsKey("tel")) {
            SharedPreferences sharedPreferences = mAppliactionContext.getSharedPreferences(
                    "share", Context.MODE_PRIVATE);
            String tel = sharedPreferences.getString(Constant.KEY_USERNAME, "");
            if (!Util.checkNULL(tel))
                params.put("tel", tel);
        }

        if (!params.containsKey("device_id")) {
            String deviceId = collectDeviceInfo(mAppliactionContext);
            params.put("device_id", deviceId);
        }

        String channel = getChannelId(mAppliactionContext);
        if (!Util.isEmpty(channel)) {
            params.put("app_channel", channel);
        }
        params.put("version", Constant.APP_VERSION);
        params.put("app_type", "android_price");
        //retrofit的params的值不能為null,此處做下校驗,防止出錯
        for (Map.Entry entry : params.entrySet()) {
            if (entry.getValue() == null) {
                params.put(entry.getKey(), "");
            }
        }
        return params;
    }


    // 判斷是否NULL
    public static boolean checkNULL(String str) {
        return str == null || "null".equals(str) || "".equals(str);

    }

    public static String message(String mes) {
        if (checkNULL(mes)) {
            mes = "網路連接異常";
        }

        if (mes.equals("timeout") || mes.equals("SSL handshake timed out")) {
            return "網絡請求超時";
        } else {
            return mes;
        }
    }


    private static class SingletonHolder {

        private static final RetrofitHttpService sInstance = createRetrofitHttpService();

        public static RetrofitHttpService createRetrofitHttpService() {
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(new RetryAndChangeIpInterceptor(BASE_URL, SERVERS))
                    .addNetworkInterceptor(new CacheInterceptor())
                    .cache(new CacheProvide(mAppliactionContext).provideCache())
                    .retryOnConnectionFailure(true)
                    .connectTimeout(8, TimeUnit.SECONDS)
                    .readTimeout(5, TimeUnit.SECONDS)
                    .writeTimeout(5, TimeUnit.SECONDS)
                    .build();
            if (BuildConfig.DEBUG) {//printf logs while  debug
                HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
                logging.setLevel(HttpLoggingInterceptor.Level.BODY);
                client = client.newBuilder().addInterceptor(logging).build();
            }
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL + "/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(client)
                    .build();

            RetrofitHttpService Service =
                    retrofit.create(RetrofitHttpService.class);
            return Service;
        }
    }

    public static class Builder {
        Map params = new HashMap<>();
        String url;
        Error mErrorCallBack;
        Failure mFailureCallBack;
        Success mSuccessCallBack;
        String cacheTime;
        boolean addVersion = false;

        public Builder CacheTime(String time) {
            this.cacheTime = time;
            return this;
        }

        public Builder Url(String url) {
            this.url = url;
            return this;
        }

        public Builder Params(Map params) {
            this.params.putAll(params);
            return this;
        }

        public Builder Params(String key, String value) {
            this.params.put(key, value);
            return this;
        }

        public Builder Success(Success success) {
            this.mSuccessCallBack = success;
            return this;
        }

        public Builder Version() {
            this.addVersion = true;
            return this;
        }

        public Builder Failure(Failure failure) {
            this.mFailureCallBack = failure;
            return this;
        }

        public Builder Error(Error error) {
            this.mErrorCallBack = error;
            return this;
        }

        public Builder() {
            this.setParams();
        }

        public Builder(String url) {
            this.setParams(url);
        }

        private void setParams() {
            this.setParams(null);
        }

        private void setParams(String url) {
            this.url = url;
            this.params = new HashMap<>();
            this.mErrorCallBack = (v) -> {
            };
            this.mFailureCallBack = (c, m, t) -> {
            };
            this.mSuccessCallBack = (s) -> {
            };
        }


        private String checkUrl(String url) {
            if (Util.checkNULL(url)) {
                throw new NullPointerException("absolute url can not be empty");
            }
            if (addVersion && !url.contains(mVersionApi)) {
                url = V(url);
            }
            return url;
        }

        public void get() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            mInstance.get(url, checkParams(params), cacheTime).enqueue(new Callback() {
                @Override
                public void onResponse(Call call, Response response) {
                    if (response.code() == 200) {
                        mSuccessCallBack.Success(response.body().toString());
                    } else {
                        mFailureCallBack.Failure(response.code(), message(response.message()), null);
                        mErrorCallBack.Error(response.code(), message(response.message()), null);
                    }
                }

                @Override
                public void onFailure(Call call, Throwable t) {
                    mFailureCallBack.Failure(200, message(t.getMessage()), t);
                    mErrorCallBack.Error(200, message(t.getMessage()), t);
                }
            });
        }

        public void getArray() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            mInstance.getArray(url, checkParams(params), cacheTime).enqueue(new Callback() {
                @Override
                public void onResponse(Call call, Response response) {
                    if (response.code() == 200) {
                        mSuccessCallBack.Success(response.body().toString());
                    } else {
                        mFailureCallBack.Failure(response.code(), message(response.message()), null);
                        mErrorCallBack.Error(response.code(), message(response.message()), null);
                    }
                }

                @Override
                public void onFailure(Call call, Throwable t) {
                    mFailureCallBack.Failure(200, message(t.getMessage() ), t);
                    mErrorCallBack.Error(200, message(t.getMessage()), t);
                }
            });
        }

        public void post() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            mInstance.post(url, checkParams(params), cacheTime).enqueue(new Callback() {
                @Override
                public void onResponse(Call call, Response response) {
                    if (response.code() == 200) {
                        mSuccessCallBack.Success(response.body().toString());
                    } else {
                        mFailureCallBack.Failure(response.code(), message(response.message()), null);
                        mErrorCallBack.Error(response.code(), message(response.message()), null);
                    }
                }

                @Override
                public void onFailure(Call call, Throwable t) {
                    mFailureCallBack.Failure(200, message(t.getMessage()), t);
                    mErrorCallBack.Error(200, message(t.getMessage()), t);
                }
            });
        }

        public void postArray() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            mInstance.postArray(url, checkParams(params), cacheTime).enqueue(new Callback() {
                @Override
                public void onResponse(Call call, Response response) {
                    if (response.code() == 200) {
                        mSuccessCallBack.Success(response.body().toString());
                    } else {
                        mFailureCallBack.Failure(response.code(), message(response.message()), null);
                        mErrorCallBack.Error(response.code(), message(response.message()), null);
                    }
                }

                @Override
                public void onFailure(Call call, Throwable t) {
                    mFailureCallBack.Failure(200, message(t.getMessage()), t);
                    mErrorCallBack.Error(200, message(t.getMessage()), t);
                }
            });
        }

        public Observable Obget() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            return mInstance.Obget(url, checkParams(params), cacheTime);
        }

        public Observable ObgetArray() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            return mInstance.ObgetArray(url, checkParams(params), cacheTime);
        }

        public Observable Obpost() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            return mInstance.Obpost(url, checkParams(params), cacheTime);
        }

        public Observable ObpostArray() {
            this.url = checkUrl(this.url);
            this.params = checkParams(this.params);
            if (cacheTime == null) {
                cacheTime = "";
            }
            return mInstance.ObpostArray(url, checkParams(params), cacheTime);
        }
    }
}

以上就是本次retrofit的封裝的完整實現了,有什麼建議的可以留言喔

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved