商城項目實戰 | 6.2 OkHttp 輕松封裝 更加靈活的調用

本文為菜鳥窩作者劉婷的連載。”商城項目實戰”系列來聊聊仿”京東淘寶的購物商城”如何實現。
140套Android優秀開源項目源碼,領取地址:http://mp.weixin.qq.com/s/afPGHqfdiApALZqHsXbw-A
或歡迎勾搭運營小姐姐(微信 id:BT474849)免費領取哦~

在前面的文章《商城項目實戰 | 6.1 OkHttp 的詳細介紹 網絡請求更加簡單》中已經詳細介紹了 OkHttp 的基本屬性和使用方法了,OkHttp 開源框架給開發者帶來了很多的便利,使用非常方便,但是在項目中網絡請求一般到處都要使用到,如果總是重復相同的代碼,首先代碼不夠簡潔,其次效率也變低了不少。代碼的簡潔性是一個好的開發者所不可缺少的開發特點,如何使得代碼更為的簡潔呢?那就要學會封裝了,本文主要是針對 OkHttp 框架使用的封裝,學會了這個,封裝其他的也都不是問題了。

封裝的意義和好處

1. 封裝的概念

封裝從字面上來理解就是包裝的意思,專業點就是信息隱藏,是指利用抽象數據類型將數據和基于數據的操作封裝在一起,使其構成一個不可分割的獨立實體,數據被保護在抽象數據類型的內部,盡可能地隱藏內部的細節,只保留一些對外接口使之與外部發生聯系。系統的其他對象只能通過包裹在數據外面的已經授權的操作來與這個封裝的對象進行交流和交互。封裝是面向對象的三大特征之一,就是將類的狀態信息隱藏在類的內部,不允許外部程序直接訪問,而通過該類提供的方法來實現對隱藏信息的操作和訪問。

2. 封裝的好處

  1. 1.良好的封裝能夠減少耦合。
  2. 2.不必關心具體的實現。
  3. 3.控制用戶對類的修改和訪問數據的程度,提高安全性。
  4. 4.可以方便的加入存取控制語句,限制不合理操作。
  5. 5.代碼更加容易被理解和維護。

OkHttp 的封裝

已經了解了封裝的意義和所帶來的好處,更加要把封裝應用到自己的代碼中去,讓代碼更為得具有簡潔性和易維護性,下面開始 OkHttp 的封裝。

1. 封裝 OkHttp 要實現什么

在做一件事情之前,都要考慮為什么要這樣做,在寫一個項目之前,都要考慮這個項目的需求,在寫一行代碼之前,所要想的是要實現怎么的功能,同樣的,在封裝之前,要思考封裝可以實現什么,可以先列舉出來,下面是我列舉的希望封裝 OkHttp 能夠實現的功能。

  1. 1.OkHttpClient 可以不重復寫,直接簡單調用 Get 和 Post 方法,尤其是 post 調用時傳遞參數希望可以一步搞定。
  2. 2.返回的數據可以直接拿來使用,不要自己再過多的處理和序列化。
  3. 3.有些時候加載數據希望顯示提示加載框,數據加載完后顯示框 dismiss,但是有的時候又不希望提示加載框顯示出來。
  4. 4.網絡請求失敗后,可以提示錯誤信息,同時對于請求成功和請求失敗后的操作可以自己再擴展。

按照這樣的想法,一步一步來實現 OkHttp 的封裝。

**2. 實現封裝 OkHttp **

在列舉的所要實現的功能中,希望可以返回的數據可以直接拿來使用,另外還要顯示加載的提示框,那么在封裝的 OkHttp 中一定需要對數據進行處理,比如 Json 類型的數據,我們就用 Gson 框架來處理了,而提示對話框的話,這里也使用一款開源的框架 SpotsDialog 。

2.1 添加 Gradle 依賴

因為使用的工具為 Android Studio 2.3.0,所使用的集成工具為 gradle,所以在使用第三方開源框架時,添加依賴是永遠必不可少的。

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.2.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.1'
    testCompile 'junit:junit:4.12'
    compile 'com.daimajia.slider:library:1.1.5@aar'
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.nineoldandroids:library:2.4.0'
    compile 'com.android.support:support-v4:25.2.0'
    compile 'com.android.support:recyclerview-v7:25.2.0'
    compile 'com.android.support:cardview-v7:25.2.0'
    compile 'com.squareup.okhttp3:okhttp:3.6.0'
    compile 'com.google.code.gson:gson:2.8.0'
    compile 'com.github.d-max:spots-dialog:0.7'
}

2.2 封裝 Request

在使用 OkHttp 的時候,所調用的 get 和 post 方法雖然有所不同,但是其實很多的代碼還是重復的,有必要把重復的代碼提取出來,另外這里主要是講解異步請求的方法,所以封裝的也是針對于異步網絡請求的。
創建好 OkHttpHelper 類,添加構造函數 OkHttpHelper(),首先就是在這里創建 OkHttpClient,進行簡單的設置,同時 OkHttpHelper 的調用也要寫好來,后期要使用 OkHttpHelper 時,就直接調 getInstance() 方法就好了。

private  static  OkHttpHelper mInstance;
static {
            mInstance = new OkHttpHelper();
        }
public static  OkHttpHelper getInstance(){
            return  mInstance;
        }
private OkHttpHelper(){
       mHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(10,TimeUnit.SECONDS)
                    .writeTimeout(30,TimeUnit.SECONDS)
                    .build();
}

至于 get 和 post 還是需要分區下的,直接使用枚舉。

enum  HttpMethodType{
            GET,
            POST,
        }

然后就是 Request 的創建,根據 get 和 post 的不同來相應創建 Request ,后期我們可以直接傳入區分參數 HttpMethodType 調用這一個函數就可以了。代碼如下。

private  Request buildRequest(String url,HttpMethodType methodType,Map<String,Object> params){


            Request.Builder builder = new Request.Builder()
                    .url(url);

            if (methodType == HttpMethodType.POST){
                RequestBody body = builderFormData(params);
                builder.post(body);
            }
            else if(methodType == HttpMethodType.GET){

                url = buildUrlParams(url,params);
                builder.url(url);

                builder.get();
            }


            return builder.build();
        }

        private RequestBody builderFormData(Map<String,Object> params){

            FormBody.Builder builder = new FormBody.Builder();

            if(params !=null){
                for (Map.Entry<String,Object> entry :params.entrySet() ){

                    builder.add(entry.getKey(),entry.getValue()==null?"":entry.getValue().toString());
                }
            }

       private   String buildUrlParams(String url ,Map<String,Object> params) {

        if(params == null)
            params = new HashMap<>(1);
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            sb.append(entry.getKey() + "=" + (entry.getValue()==null?"":entry.getValue().toString()));
            sb.append("&");
        }
        String s = sb.toString();
        if (s.endsWith("&")) {
            s = s.substring(0, s.length() - 1);
        }

        if(url.indexOf("?")>0){
            url = url +"&"+s;
        }else{
            url = url +"?"+s;
        }

        return url;
    }

2.3 封裝 Callback

前面已經寫好了 Request 了,下面就是要考慮網絡請求之后的回調了,主要是針對于網絡請求成功、網絡請求失敗以及請求成功但是遇到錯誤了的情況,定義 abstract 抽象類 BaseCallback。

public abstract class BaseCallback <T> {


    public   Type mType;

    static Type getSuperclassTypeParameter(Class<?> subclass)
    {
        Type superclass = subclass.getGenericSuperclass();
        if (superclass instanceof Class)
        {
            throw new RuntimeException("Missing type parameter.");
        }
        ParameterizedType parameterized = (ParameterizedType) superclass;
        return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
    }


    public BaseCallback()
    {
        mType = getSuperclassTypeParameter(getClass());
    }



    public  abstract void onBeforeRequest(Request request);


    public abstract  void onFailure(Request request, Exception e) ;


    /**
     *請求成功時調用此方法
     * @param response
     */
    public abstract  void onResponse(Response response);

    /**
     *
     * 狀態碼大于200,小于300 時調用此方法
     * @param response
     * @param t
     * @throws IOException
     */
    public abstract void onSuccess(Response response,T t) ;

    /**
     * 狀態碼400,404,403,500等時調用此方法
     * @param response
     * @param code
     * @param e
     */
    public abstract void onError(Response response, int code,Exception e) ;

}

2.4 對獲取的數據處理和序列化

請求之后就是要處理獲取到的數據了,如果是 String 類型的數據,就不用過多處理,但是如果是 Json 格式的數據,就要序列化了,處理數據主要是請求成功并且沒有出現錯誤的時候對獲取的數據進行處理,所以也要用到之前寫好的 BaseCallback 了。
當然先要在構造函數 OkHttpHelper() 中初始化 Gson。

Gson mGson = new Gson();
public  void request(final Request request,final  BaseCallback callback){

            callback.onBeforeRequest(request);

            mHttpClient.newCall(request).enqueue(new Callback() {

                @Override
                public void onFailure(Call call, IOException e) {
                    callbackFailure(callback, request, e);
                }

                @Override
                public void onResponse(Call call,Response response) throws IOException {
                    callbackResponse(callback,response);

                    if(response.isSuccessful()) {

                        String resultStr = response.body().string();

                        Log.d(TAG, "result=" + resultStr);

                        if (callback.mType == String.class){
                            callbackSuccess(callback,response,resultStr);
                        }
                        else {
                            try {

                                Object obj = mGson.fromJson(resultStr, callback.mType);
                                callbackSuccess(callback,response,obj);
                            }
                            catch (com.google.gson.JsonParseException e){ // Json解析的錯誤
                                callback.onError(response,response.code(),e);
                            }
                        }
                    }
                    else {
                        callbackError(callback,response,null);
                    }

                }
            });


        }

2.5 Handler 來幫忙

在使用網絡請求開線程的時候一定要注意一個問題,那就是對 View 的操作,必須在主線程中,所以就需要 Handler 來幫忙了。
當然先要在構造函數 OkHttpHelper() 中初始化 Handler。

Handler mHandler = new Handler(Looper.getMainLooper());
private void callbackSuccess(final  BaseCallback callback , final Response response, final Object obj ){

            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    callback.onSuccess(response, obj);
                }
            });
        }


        private void callbackError(final  BaseCallback callback , final Response response, final Exception e ){

            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    callback.onError(response,response.code(),e);
                }
            });
        }



    private void callbackFailure(final  BaseCallback callback , final Request request, final IOException e ){

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                callback.onFailure(request,e);
            }
        });
    }


    private void callbackResponse(final  BaseCallback callback , final Response response ){

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                callback.onResponse(response);
            }
        });
    }

2.6 加入提示加載框

之前列舉的功能中希望有時候可以顯示加載框,有時候不顯示,所以這里就需要擴展下之前的 BaseCallback,寫一個帶有提示加載框的 SpotsCallBack,并且在里面處理提示框的顯示和隱藏。

public abstract class SpotsCallBack<T> extends SimpleCallback<T> {



    private  SpotsDialog mDialog;

    public SpotsCallBack(Context context){
        super(context);

        initSpotsDialog();
    }


    private  void initSpotsDialog(){

        mDialog = new SpotsDialog(mContext,"拼命加載中...");
    }

    public  void showDialog(){
        mDialog.show();
    }

    public  void dismissDialog(){
        mDialog.dismiss();
    }


    public void setLoadMessage(int resId){
        mDialog.setMessage(mContext.getString(resId));
    }


    @Override
    public void onBeforeRequest(Request request) {

        showDialog();
    }

    @Override
    public void onResponse(Response response) {
        dismissDialog();
    }

    @Override
    public void onFailure(Request request, Exception e) {
        dismissDialog();
        super.onFailure(request, e);
    }

    @Override
    public void onError(Response response, int code, Exception e) {
        dismissDialog();
    }
}

2.7 寫好 get 和 post 的調用方法

所需要封裝的都已經封裝好了,現在就是要在 OkHttpHelper 中寫好相應的 get 和 post 的調用方法,也就是函數。

public void get(String url,Map<String,Object> param,BaseCallback callback){


            Request request = buildGetRequest(url,param);

            request(request,callback);

        }

        public void get(String url,BaseCallback callback){

            get(url,null,callback);
        }


        public void post(String url,Map<String,Object> param, BaseCallback callback){

            Request request = buildPostRequest(url,param);
            request(request,callback);
        }

到這里就基本實現了我之前列舉的功能了,如果還需要其他的功能的話,可以自行封裝其他的。

使用封裝好的 OkHttp

已經封裝好的 OkHttp 要實際運用起來,看下是不是優化了很多,在之前的文章《商城項目實戰 | 6.1 OkHttp 的詳細介紹 網絡請求更加簡單》中使用 OkHttp 來實現炫酷輪播廣告,現在我們就用封裝好的 OkHttp 來實現同樣的功能。

下面是封裝前的網絡請求方法,這里使用的是 get 請求。

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case INIT_SLIDER_TYPE:
                    initSlider();
                    break;
            }
        }
    };

private void getBannerData() {
    String url ="http://112.124.22.238:8081/course_api/banner/query?type=1";
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Toast.makeText(getActivity(),e.getMessage().toString(),Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if(response.isSuccessful()){
                    Type type = new TypeToken<List<BannerInfo>>(){}.getType();
                    Gson gson = new Gson();
                    List<BannerInfo> list= gson.fromJson(response.body().string(),type);
                    for (BannerInfo bannerInfo:list)
                    {
                        listBanner.add(bannerInfo);
                    }
                    handler.sendEmptyMessage(INIT_SLIDER_TYPE);
                }else {
                    Toast.makeText(getActivity(),"IOException",Toast.LENGTH_SHORT).show();
                }
            }
        });
}

下面是封裝后的網絡請求處理。

private void getBannerData() {
        String url ="http://112.124.22.238:8081/course_api/banner/query?type=1";
        httpHelper.get(url, new SpotsCallBack<List<BannerInfo>>(getActivity()){


            @Override
            public void onSuccess(Response response, List<BannerInfo> banners) {

                listBanner = banners;
                initSlider();
            }

            @Override
            public void onError(Response response, int code, Exception e) {
                Toast.makeText(getActivity(),code+e.toString(),Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Request request, Exception e) {
                super.onFailure(request, e);
                Toast.makeText(getActivity(),e.toString(),Toast.LENGTH_SHORT).show();
            }
        });
}

還是使用的 get 請求方法,但是感覺代碼明顯簡潔了不少,也更加的規整,同時調用也方便了很多。

效果圖

最后還是運行下代碼,獲取到效果圖。
[圖片上傳失敗...(image-d9fec5-1565145693331)]

效果圖和之前是一樣的,但是方法已經簡便了很多,封裝可以給我們帶來很多的便利,也讓我們更加靈活的使用 OkHttp 了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容