手擼一個簡單的網絡框架

開始前

網絡訪問框架關心的問題:

  • 能并發(fā)接受多個請求,并返回"用戶"需要的數(shù)據
  • 重試機制

實現(xiàn)方式:

  • 隊列
  • 線程池

網絡框架實現(xiàn)步驟

  1. 創(chuàng)建線程池管理類(隊列,線程池)
  2. 封裝請求參數(shù)
  3. 封裝響應數(shù)據
  4. 封裝請求任務
  5. 封裝"使用工具"
  6. 添加重試機制

創(chuàng)建線程池管理類

創(chuàng)建 ThreadPoolManager.java 類,負責管理請求隊列和線程池

//1. 創(chuàng)建隊列,用來保存異步請求任務
private LinkedBlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<>();//LinkedBlockingQueue FIFO
//2. 添加異步任務到隊列中
public void addTask(Runnable runnable) {
    try {
        if (runnable != null) {
            mQueue.put(runnable);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
//3. 創(chuàng)建線程池
private ThreadPoolExecutor mThreadPoolExecutor;
//4. 創(chuàng)建隊列與線程池的"交互"線程
public Runnable communicateThread = new Runnable() {
@Override
public void run() {
    Runnable runnable = null;
    while (true) {
        try {
            runnable = mQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //執(zhí)行線程池中的線程任務
        mThreadPoolExecutor.execute(runnable);
    }
}
};

[注] communicateThread 線程負責從 mQueue 隊列中獲取請求任務,并放到 mThreadPoolExecutor 線程池中執(zhí)行.

構造單例的 ThreadPoolManager,構造方法中初始化線程池并執(zhí)行 communicateThread 線程

private ThreadPoolManager() {

    mThreadPoolExecutor = new ThreadPoolExecutor(
            3, 10, 15, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            //處理被拋出來的任務(被拒絕的任務)
            addTask(r);
        }
    });
    mThreadPoolExecutor.execute(communicateThread);
}

[注] 線程池的設定依據具體項目而定.

RejectedExecutionHandler回調, 任務拒絕后,重新添加到隊列之中.

封裝請求參數(shù)

定義接口 IHttpRequest.java 實現(xiàn)必要的參數(shù)

public interface IHttpRequest {

    /**
     * 協(xié)議地址
     * @param url
     */
    void setUrl(String url);

    /**
     * 設置請求參數(shù)
     */
    void setData(byte[] bytes);

    /**
     * 數(shù)據數(shù)據回調
     * @param callbackListener
     */
    void setListener(CallbackListener callbackListener);

    /**
     * 執(zhí)行請求
     */
    void execute();
}

execute 方法負責具體的任務執(zhí)行.

例如我們的請求類型為JSON, 我們可以實現(xiàn)一個JSON的請求

public class JsonHttpRequest implements IHttpRequest {
    
    // 省略其他實現(xiàn)方法
    
    @Override
    public void execute() {
        URL url = null;
        HttpURLConnection urlConnection = null;
        try {
            url = new URL(this.url);
            //省略HttpURLConnection請求參數(shù)
            if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {//得到服務器返回碼是否連接成功
                InputStream in = urlConnection.getInputStream();
                mCallbackListener.onSuccess(in);
            } else {
                throw new RuntimeException("請求失敗");
            }
        } catch (Exception e) {
            throw new RuntimeException("請求失敗");
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }

    }
}

封裝響應數(shù)據

從上面可以看到有一個 CallbackListener 接口, 負責數(shù)據的成功和失敗回調

public interface CallbackListener {

    /**
     * 成功回調
     * @param inputStream
     */
    void onSuccess(InputStream inputStream);

    /**
     * 失敗
     */
    void onFailed();
}

特別的,如果我們請求的是JSON格式的數(shù)據, 我們可以自己實現(xiàn)一個Callback, JsonCallbackListener 用于數(shù)據的獲取和解析


public class JsonCallbackListener<T> implements CallbackListener {

    private Class<T> resposeClass;
    private IJsonDataListener jsonDataListener;
    Handler handler = new Handler(Looper.getMainLooper());

    public JsonCallbackListener(Class<T> responseClass, IJsonDataListener listener) {
        this.resposeClass = responseClass;
        this.jsonDataListener = listener;
    }

    @Override
    public void onSuccess(InputStream inputStream) {
        String response = getContent(inputStream);
        Log.d(TAG, "onSuccess: response: " + response);
        final T clazz = new Gson().fromJson(response, resposeClass);
        handler.post(new Runnable() {
            @Override
            public void run() {
                jsonDataListener.onSuccess(clazz);
            }
        });
    }

    private String getContent(InputStream inputStream) {
        String content = "";
        //省略解析過程
        return content;
    }

    @Override
    public void onFailed() {
    
    }
}

封裝請求任務

添加一個 HttpTask 繼承自 Runnable, 作為請求任務

public class HttpTask<T> implements Runnable {

    private IHttpRequest mHttpRequest;

    public HttpTask(T requestData, String url, IHttpRequest httpRequest, CallbackListener callbackListener) {
        mHttpRequest = httpRequest;
        httpRequest.setUrl(url);
        httpRequest.setListener(callbackListener);
        Log.d(TAG, "HttpTask: url: " + url);
        String content = new Gson().toJson(requestData);
        try {
            httpRequest.setData(content.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // implements Runnable
    ///////////////////////////////////////////////////////////////////////////

    @Override
    public void run() {
        try {
            mHttpRequest.execute();
        } catch (Exception e) {
            //....
        }
    }


}

在構造方法中獲取請求參數(shù), run 方法中執(zhí)行 IHttpRequest 中的 execute 獲取網絡數(shù)據

封裝使用工具

為方便使用方使用,有必要封裝成工具類

添加 LuOkHttp.java 作為請求工具類

public class LuOkHttp {

    /**
     * 發(fā)送網絡請求
     */
    public static<T, M> void sendJsonRequest(T request, String url,
                                             Class<M> response, IJsonDataListener listener) {
        IHttpRequest httpRequest = new JsonHttpRequest();
        JsonCallbackListener<M> mJsonCallbackListener = new JsonCallbackListener<>(response, listener);
        HttpTask<T> httpTask = new HttpTask<>(request, url, httpRequest, mJsonCallbackListener);
        ThreadPoolManager.getInstance().addTask(httpTask);
    }
}

至此,基本的請求已經實現(xiàn), 可以運行試一下了.

添加重試機制

網絡訪問在很多情況下會失敗,例如通過隧道,坐電梯等,所以有必要在框架層實現(xiàn)重試機制.

首先,需要在我們的線程池管理類 ThreadPoolManager 中添加延時隊列

// 創(chuàng)建延時隊列
private DelayQueue<HttpTask> mDelayQueue = new DelayQueue<>();

//添加到延時隊列
public void addDelayTask(HttpTask httpTask) {
    if (httpTask != null) {
        httpTask.setDelayTime(3000);
        mDelayQueue.offer(httpTask);
        Log.d(TAG, "addDelayTask: ");
    }
}

同樣的, 也需要一個線程來負責將延時隊列中的任務放到線程池中.

public Runnable delayThread = new Runnable() {
    @Override
    public void run() {
        HttpTask ht = null;
        while (true) {
            try {
                ht = mDelayQueue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (ht != null && ht.getRetryCount() < 3) {
                mThreadPoolExecutor.execute(ht);
                ht.setRetryCount(ht.getRetryCount() + 1);
                Log.d(TAG, "run: 重試機制: " + ht.getRetryCount());
            } else {
                Log.d(TAG, "run: 重試機制:超出次數(shù) ");
            }
        }
    }
};

另外,不要忘記在 ThreadPoolManager 的構造方法中執(zhí)行這個線程.

private ThreadPoolManager() {
    //...
    mThreadPoolExecutor.execute(delayThread);
}

現(xiàn)在, 你可以斷網測試一下我們的重試機制是否生效.

源碼地址

https://github.com/changer0/OkHttpDemo

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咐吼,隨后出現(xiàn)的幾起案子赖舟,更是在濱河造成了極大的恐慌擒滑,老刑警劉巖淹禾,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異君仆,居然都是意外死亡,警方通過查閱死者的電腦和手機牲距,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門返咱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人牍鞠,你說我怎么就攤上這事咖摹。” “怎么了难述?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵萤晴,是天一觀的道長。 經常有香客問我胁后,道長店读,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任攀芯,我火速辦了婚禮屯断,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己裹纳,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布紧武。 她就那樣靜靜地躺著剃氧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阻星。 梳的紋絲不亂的頭發(fā)上朋鞍,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音妥箕,去河邊找鬼滥酥。 笑死,一個胖子當著我的面吹牛畦幢,可吹牛的內容都是我干的坎吻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼宇葱,長吁一口氣:“原來是場噩夢啊……” “哼瘦真!你這毒婦竟也來了?” 一聲冷哼從身側響起黍瞧,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诸尽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后印颤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體您机,經...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年年局,在試婚紗的時候發(fā)現(xiàn)自己被綠了际看。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡矢否,死狀恐怖仿村,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情兴喂,我是刑警寧澤蔼囊,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站衣迷,受9級特大地震影響畏鼓,放射性物質發(fā)生泄漏。R本人自食惡果不足惜壶谒,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一云矫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汗菜,春花似錦让禀、人聲如沸挑社。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痛阻。三九已至,卻和暖如春腮敌,著一層夾襖步出監(jiān)牢的瞬間阱当,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工糜工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弊添,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓捌木,卻偏偏與公主長得像油坝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刨裆,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內容