網(wǎng)絡(luò)訪問組件的思考一(Volley)

前提##

網(wǎng)絡(luò)訪問是一個App的基礎(chǔ)功能勃教,也是非常重要的一塊兄墅;一般我們會使用一些第三方的網(wǎng)絡(luò)組件捎迫,如:volley晃酒、okhttp,xutils窄绒,并通過一定的封裝來實現(xiàn)網(wǎng)絡(luò)請求贝次;

我們的項目已經(jīng)快3年了,從最初彰导,簡單的封裝了一下 xutils的工具類蛔翅,直到現(xiàn)在敲茄,一直在用,雖沒出現(xiàn)過問題山析,但隨著一些其他 網(wǎng)絡(luò)庫的 出現(xiàn)堰燎,想替換確因耦合度太高,造成無法替換笋轨;

耦合度極高

通過上圖秆剪,可以看到,只要我們想訪問網(wǎng)絡(luò)爵政,我們直接去調(diào)用 封裝的網(wǎng)絡(luò)仅讽,而不管是在 界面UI,還是Service钾挟,或是MVP中的model洁灵,這樣就造成了。在代碼的任何處掺出,都有可能看到網(wǎng)絡(luò)請求的代碼徽千;
如下:
** 在activity中請求網(wǎng)絡(luò)**

private void initData() {
        NetWorkManager.request(this, NetworkConstant.API_GET_MSG_TYPE, new SimpleRequestCallback<String>(null, false, true) {
            @Override
            public void onStart() {
                super.onStart();
                // UI 上的控件
                mSwipeRefreshLayout.setRefreshing(true);
            }

            @Override
            public void onSuccess(ResponseInfo<String> info) {
                super.onSuccess(info);
                mSwipeRefreshLayout.setRefreshing(false);
                ResponseParser parser = new ResponseParser(info.result, getActivity(), false);
                parser.parse(new ResponseParser.ParseCallback() {

在MVP模式(對應(yīng)的M中訪問)

class DetailSonRepo implements DetailSonContract.Repo {
    @Override
    public void loadData(String processInstanceId, String subCode, String subColumns, final LoadDataCallback<DetailSonModel> callback) {
        Map<String, String> params = new HashMap<>();
        params.put("processInstanceId", processInstanceId);
        params.put("subCode", subCode);
        params.put("subColumns", subColumns);

        NetWorkManager.request(this, NetworkConstant.API___, new SimpleReqCallbackAdapter<>(new AbsReqCallback<DetailSonModel>(DetailSonModel.class) {
            @Override
            protected void onSuccess(DetailSonModel detailSonModel, List<DetailSonModel> tArray, String rawData) {
                super.onSuccess(detailSonModel, tArray, rawData);
                callback.onDataLoaded(detailSonModel);
            }

            @Override
            public void onFailure(String errorMsg, int code) {
                super.onFailure(errorMsg);
                callback.onDataNotAvailable(errorMsg, code);
            }
        }), params);
    }

通過MVP模式,可將網(wǎng)絡(luò)的請求汤锨,在M層搞定双抽;避免了在界面UI層,去做網(wǎng)絡(luò)請求的事情泥畅;

思考##

上面的層級結(jié)構(gòu)荠诬,是有缺陷的:

  1. 網(wǎng)絡(luò)模塊耦合度太高琅翻,單獨分離出來位仁,困難;
  2. 另開一個App方椎,代碼無法直接復(fù)用聂抢,無法移植代碼,造成重復(fù)開發(fā)棠众;

MVP模式

在MVP模式中琳疏,是通過M去操作數(shù)據(jù)的(網(wǎng)絡(luò)、數(shù)據(jù)庫闸拿、緩存)都是在這塊完成空盼,但某個時候,迫于業(yè)務(wù)上開發(fā)壓力新荤,經(jīng)常導(dǎo)致開發(fā)人員違反設(shè)計規(guī)范揽趾,直接在頁面中操作任何數(shù)據(jù);畢竟采用MVP模式開發(fā)苛骨,是會用大量的子類需要創(chuàng)建篱瞎;

basenet模塊
我們思考苟呐,通過一個 basenet 的組件去訪問網(wǎng)絡(luò),這個 組件的目標(biāo)就是純網(wǎng)絡(luò)訪問俐筋,不涉及到任何業(yè)務(wù)(如:解析牵素,加密等);一句話:來了請求澄者,我就請求網(wǎng)絡(luò)笆呆;此模塊依賴于第三方的網(wǎng)絡(luò)組件庫,如:volley粱挡;
basenet通過接口來解耦腰奋,通過統(tǒng)一的抽象類 or 接口,對外實現(xiàn)網(wǎng)絡(luò)請求抱怔,客戶端劣坊,無須關(guān)注具體的實現(xiàn)類;

basenet

實踐1(老套路屈留,參數(shù)通過方法傳)##

總體設(shè)計圖

basenet整體設(shè)計圖

具體實現(xiàn)代碼片段

// 請求接口
public interface IRequest {

    public static final Short GET = 0;
    public static final Short POST = 1;

    /**
     * @param reqType 請求方式
     * @param url     地址
     * @param headers 請求頭
     * @param param   請求參數(shù)
     */
    void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback);

    void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback);

    void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback, final long timeout);
}

// 回調(diào)接口
public interface IRequestCallBack<T> {
    void onSuccess(T t);

    void onFailure(Throwable e);
}

請求抽象類

/**
 * 抽象類
 * Created by zhaoyu1 on 2017/3/6.
 */
public abstract class AbsRequest implements IRequest {

    public static final String CHAR_SET = "UTF-8";

    /**
     * 生成請求的url地址
     *
     * @param url
     * @param params
     * @return
     */
    protected String generateUrl(String url, Map<String, String> params) {
        StringBuilder sb = new StringBuilder(url);
        if (params != null && params.size() > 0) {      // GET 請求局冰,拼接url
            if (sb.charAt(sb.length() - 1) != '?') {            // get 請求 有 ?
                sb.append("?");
            }
            for (Map.Entry<String, String> entry : params.entrySet()) {
                try {
                    sb.append(URLEncoder.encode(entry.getKey(), CHAR_SET)).append("=").append(URLEncoder.encode(entry.getValue(), CHAR_SET)).append("&");
                } catch (UnsupportedEncodingException e) {
                    // NOT_HAPPEND
                }
            }
            sb = sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

Volley Request 類

public class VolleyRequest extends AbsRequest {

    private RequestQueue requestQueue;
    private static VolleyRequest sRequest;

    public static VolleyRequest getInstance() {
        if (sRequest == null) {
            synchronized (VolleyRequest.class) {
                if (sRequest == null) {
                    sRequest = new VolleyRequest();
                    sRequest.requestQueue = Volley.newRequestQueue();
                }
            }
        }
        return sRequest;
    }

    @Override
    public void request(final int reqType, String url, final Map<String, String> headers, final Map<String, String> params, long timeout, final IRequestCallBack callback) {
        StringRequest stringRequest = null;
        int tReqType = Request.Method.GET;
        String tUrl = url;
        switch (reqType) {
            case RequestType.GET:
                tReqType = Request.Method.GET;
                tUrl = generateUrl(url, params);
                break;
            case RequestType.POST:
                tReqType = Request.Method.POST;
                break;
        }

        // 創(chuàng)建請求
        stringRequest = new StringRequest(tReqType, tUrl, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                callback.onSuccess(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                callback.onFailure(error.getCause());
            }
        }) {

            // 設(shè)置Header
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> superHeader = super.getHeaders();
                if (headers != null && headers.size() > 0) {
                    superHeader = headers;
                }
                return superHeader;
            }

            // 設(shè)置Body參數(shù)
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> tParams = super.getParams();
                if (params != null && params.size() > 0) {
                    tParams = params;
                }
                return tParams;
            }
        };

        // 設(shè)置此次請求超時時間
        if (timeout > 1000) {
            stringRequest.setRetryPolicy(new DefaultRetryPolicy((int) timeout, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        }
        stringRequest.setTag(url);
        requestQueue.add(stringRequest);
    }

    @Override
    public void request(final int reqType, String url, Map<String, String> headers, Map<String, String> param, IRequestCallBack callback) {
        this.request(reqType, url, headers, param, 0, callback);
    }

    @Override
    public void request(int reqType, String url, Map<String, String> params, IRequestCallBack callback) {
        this.request(reqType, url, null, params, 0, callback);
    }
}

客戶端測試調(diào)用

    @Test
    public void testVolley() {
        Context appContext = InstrumentationRegistry.getTargetContext();
        getRequest(appContext).request(IRequest.RequestType.GET, "https://www.jd.com", null, new IRequestCallBack<String>() {
            @Override
            public void onSuccess(String o) {
                Log.e("volley", o.toString());
            }

            @Override
            public void onFailure(Throwable e) {
            }
        });
        SystemClock.sleep(500);
    }

實踐2(改由builder模式)##

經(jīng)過上面的一系列步驟,我們完成了一個簡單的基于Volley的請求封裝灌危,可以實現(xiàn)訪問接口了康二;和同事討論過后,覺得可用 Builder 建造者模式來重新構(gòu)建勇蝙,于是沫勿,經(jīng)常改造,有了第二版味混,各種網(wǎng)絡(luò)請求的參數(shù)产雹,不再有 方法去傳,而改由builder去構(gòu)造翁锡;

修改后的接口

// IRequest 接口
public interface IRequest {

    interface RequestType {
        int GET = 0;
        int POST = 1;
    }

    /**
     * 執(zhí)行請求蔓挖,默認是 get 方式
     */
    void request();

    /**
     * 取消網(wǎng)絡(luò)請求
     */
    void cancel();
}

// AbsRequest抽象類
public abstract class AbsRequest implements IRequest {

    public static final String CHAR_SET = "UTF-8";

    /**
     * url 地址
     */
    protected String mUrl;

    /**
     * 參數(shù)
     */
    protected Map<String, String> mParams;

    /**
     * 請求頭信息
     */
    protected Map<String, String> mHeader;

    /**
     * 本地請求超時時間
     */
    protected long mTimeOut;

    /**
     * 請求標(biāo)記
     */
    protected Object mTag;

    /**
     * 回調(diào)
     */
    protected IRequestCallBack mCallBack;

    /**
     * 請求方式
     */
    protected int mReqType;

    // 通過builder來構(gòu)造
    protected AbsRequest(Builder builder) {
        this.mUrl = builder.mUrl;
        this.mCallBack = builder.mCallBack;
        this.mTag = builder.mTag;
        this.mTimeOut = builder.mTimeOut;
        this.mReqType = builder.mReqType;
        this.mParams = builder.mParams;
        this.mHeader = builder.mHeader;
    }

    @Override
    public final void request() {
        switch (mReqType) {
            case RequestType.GET:
                get();
                break;
            case RequestType.POST:
                post();
                break;
        }
    }

    /**
     * 執(zhí)行g(shù)et方式
     */
    protected abstract void get();

    /**
     * 執(zhí)行post方式
     */
    protected abstract void post();


    /**
     * 生成請求的url地址
     *
     * @param url
     * @param params
     * @return
     */
    protected String generateUrl(String url, Map<String, String> params) {
        StringBuilder sb = new StringBuilder(url);
        if (params != null && params.size() > 0) {      // GET 請求,拼接url
            if (sb.charAt(sb.length() - 1) != '?') {            // get 請求 有 ?
                sb.append("?");
            }
            for (Map.Entry<String, String> entry : params.entrySet()) {
                try {
                    sb.append(URLEncoder.encode(entry.getKey(), CHAR_SET)).append("=").append(URLEncoder.encode(entry.getValue(), CHAR_SET)).append("&");
                } catch (UnsupportedEncodingException e) {
                    // NOT_HAPPEND
                }
            }
            sb = sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

    // 抽象的建造者 Builder
    public static abstract class Builder {

        /**
         * url 地址
         */
        private String mUrl;

        /**
         * 參數(shù)
         */
        private Map<String, String> mParams;

        /**
         * 請求頭信息
         */
        private Map<String, String> mHeader;

        /**
         * 本地請求超時時間
         */
        private long mTimeOut;

        /**
         * 請求標(biāo)記
         */
        private Object mTag;

        /**
         * 回調(diào)
         */
        private IRequestCallBack mCallBack;

        /**
         * 請求方式
         */
        private int mReqType;

        public Builder() {
        }

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

        public Builder body(Map<String, String> params) {
            this.mParams = params;
            return this;
        }

        public Builder headders(Map<String, String> headers) {
            this.mHeader = headers;
            return this;
        }

        public Builder timeout(long time) {
            this.mTimeOut = time;
            return this;
        }

        public Builder tag(Object tag) {
            this.mTag = tag;
            return this;
        }

        public Builder callback(IRequestCallBack callBack) {
            this.mCallBack = callBack;
            return this;
        }

        /**
         * @param reqType {@link IRequest.RequestType}中常量
         * @return
         */
        public Builder type(int reqType) {
            this.mReqType = reqType;
            return this;
        }

        public abstract AbsRequest build();
    }
}

接口我們看一下 修改后的Volley Request

public class VolleyRequest extends AbsRequest {

    private static RequestQueue requestQueue;
    private Request mRequest;

    private VolleyRequest(Builder builder) {
        super(builder);
    }

    @Override
    protected void get() {
        realRequest(Request.Method.GET);
    }

    @Override
    protected void post() {
        realRequest(Request.Method.POST);
    }

    private void realRequest(final int reqType) {
        int tReqType = Request.Method.GET;
        String tUrl = mUrl;
        switch (tReqType) {
            case Request.Method.GET:
                tReqType = Request.Method.GET;
                tUrl = generateUrl(mUrl, mParams);
                break;
            case Request.Method.POST:
                tReqType = Request.Method.POST;
                break;
        }

        mRequest = new StringRequest(tReqType, tUrl, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                mCallBack.onSuccess(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                mCallBack.onFailure(error);
            }
        }) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> superHeader = super.getHeaders();
                if (mHeader != null && mHeader.size() > 0) {
                    superHeader = mHeader;
                }
                return superHeader;
            }

            // 設(shè)置Body參數(shù)
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> tParams = super.getParams();
                if (mParams != null && mParams.size() > 0 && reqType == Request.Method.POST) {
                    tParams = mParams;
                }
                return tParams;
            }
        };

        // 設(shè)置此次請求超時時間
        if (mTimeOut > 1000) {
            mRequest.setRetryPolicy(new DefaultRetryPolicy((int) mTimeOut, 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        }
        mRequest.setTag(mTag);
        requestQueue.add(mRequest);
    }

    @Override
    public void cancel() {
        if (mRequest != null) {
            mRequest.cancel();
        } else if (mTag != null) {
            requestQueue.cancelAll(mTag);
        }
    }

        // 實現(xiàn)建造者
    public static class Builder extends AbsRequest.Builder {

        private Context mCtx;
                // volley request 為單例
        private VolleyRequest sRequest;

        public Builder(Context ctx) {
            this.mCtx = ctx;
        }

        @Override
        public AbsRequest build() {
            if (sRequest == null) {
                synchronized (VolleyRequest.class) {
                    if (sRequest == null) {
                        sRequest = new VolleyRequest(this);
                        requestQueue = Volley.newRequestQueue(mCtx);
                    }
                }
            }
            return sRequest;
        }
    }
}

客戶端的測試代碼(簡潔的鏈?zhǔn)骄幊?:

 Context appContext = InstrumentationRegistry.getTargetContext();
 AbsRequest req = new VolleyRequest.Builder(appContext).url("http://www.jd.com")
                .timeout(2000).tag("hello")
                .callback(new IRequestCallBack() {
                    @Override
                    public void onSuccess(Object o) {
                        Log.e("volley", o.toString());
                    }

                    @Override
                    public void onFailure(Throwable e) {
                        Log.e("volley", e.toString());
                    }
                }).build();
        req.request();

        SystemClock.sleep(300);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末馆衔,一起剝皮案震驚了整個濱河市瘟判,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌角溃,老刑警劉巖拷获,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異减细,居然都是意外死亡匆瓜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陕壹,“玉大人质欲,你說我怎么就攤上這事】饭荩” “怎么了嘶伟?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長又碌。 經(jīng)常有香客問我九昧,道長,這世上最難降的妖魔是什么毕匀? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任铸鹰,我火速辦了婚禮,結(jié)果婚禮上皂岔,老公的妹妹穿的比我還像新娘蹋笼。我一直安慰自己,他們只是感情好躁垛,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布剖毯。 她就那樣靜靜地躺著,像睡著了一般教馆。 火紅的嫁衣襯著肌膚如雪逊谋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天土铺,我揣著相機與錄音胶滋,去河邊找鬼。 笑死悲敷,一個胖子當(dāng)著我的面吹牛究恤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播镀迂,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼购对,長吁一口氣:“原來是場噩夢啊……” “哼悍引!你這毒婦竟也來了饶火?” 一聲冷哼從身側(cè)響起蔓罚,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤钟病,失蹤者是張志新(化名)和其女友劉穎屯掖,沒想到半個月后裸删,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體噪径,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡棍掐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年藏雏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片作煌。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡掘殴,死狀恐怖赚瘦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奏寨,我是刑警寧澤起意,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站病瞳,受9級特大地震影響揽咕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜套菜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一亲善、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逗柴,春花似錦蛹头、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至于购,卻和暖如春袍睡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肋僧。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工斑胜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嫌吠。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓止潘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辫诅。 傳聞我的和親對象是個殘疾皇子凭戴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,085評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)炕矮,斷路器么夫,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • Java中的String類可以被繼承么? 答:不能肤视,因為它是一個final類档痪,同樣的還有Integer,F(xiàn)loat...
    gyymz1993閱讀 3,984評論 2 104
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法邢滑,類相關(guān)的語法腐螟,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法乐纸,線程的語...
    子非魚_t_閱讀 31,624評論 18 399
  • 今生緣wuxianping閱讀 202評論 0 0