手擼一個簡單的網絡框架

開始前

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

  • 能併發接受多個請求,並返回"用戶"需要的數據
  • 重試機制

實現方式:

  • 隊列
  • 線程池

網絡框架實現步驟

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

創建線程池管理類

創建 ThreadPoolManager.java 類,負責管理請求隊列和線程池

//1. 創建隊列,用來保存異步請求任務
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. 創建線程池
private ThreadPoolExecutor mThreadPoolExecutor;
//4. 創建隊列與線程池的"交互"線程
public Runnable communicateThread = new Runnable() {
@Override
public void run() {
    Runnable runnable = null;
    while (true) {
        try {
            runnable = mQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //執行線程池中的線程任務
        mThreadPoolExecutor.execute(runnable);
    }
}
};

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

構造單例的 ThreadPoolManager,構造方法中初始化線程池並執行 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回調, 任務拒絕後,重新添加到隊列之中.

封裝請求參數

定義接口 IHttpRequest.java 實現必要的參數

public interface IHttpRequest {

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

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

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

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

execute 方法負責具體的任務執行.

例如我們的請求類型爲JSON, 我們可以實現一個JSON的請求

public class JsonHttpRequest implements IHttpRequest {
    
    // 省略其他實現方法
    
    @Override
    public void execute() {
        URL url = null;
        HttpURLConnection urlConnection = null;
        try {
            url = new URL(this.url);
            //省略HttpURLConnection請求參數
            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();
            }
        }

    }
}

封裝響應數據

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

public interface CallbackListener {

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

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

特別的,如果我們請求的是JSON格式的數據, 我們可以自己實現一個Callback, JsonCallbackListener 用於數據的獲取和解析


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) {
            //....
        }
    }


}

在構造方法中獲取請求參數, run 方法中執行 IHttpRequest 中的 execute 獲取網絡數據

封裝使用工具

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

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

public class LuOkHttp {

    /**
     * 發送網絡請求
     */
    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);
    }
}

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

添加重試機制

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

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

// 創建延時隊列
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: 重試機制:超出次數 ");
            }
        }
    }
};

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

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

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

源碼地址

https://github.com/changer0/OkHttpDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章