仿照Android開源框架OkHttp而寫的一個基於HttpURLConnection網絡請求的demo包含Basic認證和重試機制

廢話前言

我們在平時使用第三方框架的時候總覺得就那樣使用就行了,然而如果第三方框架不存在了,就會感覺不知所措了。其實我們應該瞭解第三方訪問框架的設計思路,進而學習其中的思想,不管到哪裏,用什麼語言都能應對。

市面上常用的網絡訪問框架是okhttp。他的優點是接收多個請求,重試機制。他在請求的時候將請求放入請求隊列。使用線程池來響應每一個請求。線程池如果有空閒的線程時去相應請求。否則就等待。線程池有默認的幾個任務線程。任務線程是負責處理請求的,線程池中有一個核心線程專門負責不停的去隊列中獲取請求,把請求交給任務線程去處理。這樣任務線程跟請求關聯起來。

網絡訪問框架的請求設計包含url,params,listener又包括post和get請求。還包括Basic認證等。這些都是對HttpUrlConnection的封裝。

對高併發的理解

最原始的處理方式

1、一個activity有多個請求,假設10個,先存起來這些請求。先後順序往server發送。服務器對客戶端的相應來一個請求就會創建一個線程,然而server假設最多能承受10萬個請求。如果數量多了話服務器受不了。

2、後來解決方法是服務器建立線程池,將線程交給線程池處理,去優化thread。如果請求越來越多還是承受不了,

3、分發,中央處理器,請求分發到不同的服務器。一臺服務器承受不了,擴展服務器,中央處理器將請求分發到機房的服務器。用來相應請求。

對服務器的優化,Nio。

高併發最終解決唯一的就是加服務器。代碼對高併發的處理是節省服務器的硬件成本。

正文

廢話少說,上代碼

定義請求接口封裝數據

package com.tydfd.tydfdokhttp.util;


import com.tydfd.tydfdokhttp.http.UserBean;

/**
 * @author liudo
 */
public interface IHttpRequest {
    /**
     * 封裝請求接口
     * @param url
     */
    void setUrl(String url);

    /**
     * 設置數據
     * @param data
     */
    void setData(byte[] data);

    /**
     * 設置回調
     * @param callbackListener
     */
    void setListener(CallbackListener callbackListener);

    /**
     * 執行線程
     */
    void execute(UserBean userBean);



}

設置回調接口

package com.tydfd.tydfdokhttp.util;

import java.io.InputStream;

public interface CallbackListener {

    void onSuccess(InputStream inputStream);

    void onFailure();

}

線程池設計

package com.tydfd.tydfdokhttp.util;

import android.util.Log;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author liudo
 */
public class ThreadPoolManager {

    /**
     * 創建隊列,用來保存異步請求任務
     */
    private LinkedBlockingDeque<Runnable> mQueue = new LinkedBlockingDeque<>();
    /**
     * 添加異步任務到隊列中
     * @param runnable
     */
    public void addTask(Runnable runnable){
        if(runnable != null){
            try {
                mQueue.put(runnable);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 創建延遲隊列
     */
    private DelayQueue<HttpTask> mDelayQueue = new DelayQueue<>();

    public void addDelayTask(HttpTask ht){
        if(ht != null){
            ht.setDelayTime(3000);
            mDelayQueue.offer(ht);
        }
    }

    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.getRetryCount() < 3){
                    mThreadPoolExecutor.execute(ht);
                    ht.setRetryCount(ht.getRetryCount()+1);
                    Log.e("=== 重試機制 ===",ht.getRetryCount() + "");
                }else{
                    Log.e("=== 重試機制 ===","執行次數超限,放棄");
                }
            }
        }
    };

    /**
     *  3 創建線程池
     */
    private ThreadPoolExecutor mThreadPoolExecutor;

    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);
        mThreadPoolExecutor.execute(delayThread);
    }

    /**
     * 創建 隊列與線程池的"交互"線程
     */
    public Runnable communicateThread = new Runnable() {
        @Override
        public void run() {
            Runnable ruun = null;
            while (true){
                try {
                    ruun = mQueue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    mThreadPoolExecutor.execute(ruun);
            }
        }
    };

    private static ThreadPoolManager threadPoolManager = new ThreadPoolManager();

    public static ThreadPoolManager getInstance(){
        return threadPoolManager;
    }

}

請求實現類

package com.tydfd.tydfdokhttp.util;


import android.util.Base64;


import com.tydfd.tydfdokhttp.http.UserBean;

import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * @author liudo
 */
public class JsonHttpRequest implements IHttpRequest{

    private String url;
    private byte[] data;
    private CallbackListener mCallbackListener;
    private HttpURLConnection urlConnection;

    @Override
    public void setUrl(String url) {
        this.url = url;
    }

    @Override
    public void setData(byte[] data) {
        this.data = data;
    }

    @Override
    public void setListener(CallbackListener callbackListener) {
        this.mCallbackListener = callbackListener;
    }

    @Override
    public void execute(UserBean userBean) {
        URL url = null;
        try {
            url = new URL(this.url);
            /**
             * 打開http連接
             */
            urlConnection = (HttpURLConnection) url.openConnection();
            /**
             * 連接的超時時間
             */
            urlConnection.setConnectTimeout(6000);
            /**
             * 不使用緩存
             */
            urlConnection.setUseCaches(false);
            /**
             * 是成員函數,僅作用於當前函數,設置這個連接是否可以被重定向
             */
            urlConnection.setInstanceFollowRedirects(true);
            /**
             * 響應的超時時間
             */
            urlConnection.setReadTimeout(3000);
            /**
             * 設置這個連接是否可以寫入數據
             */
            urlConnection.setDoInput(true);
            /**
             * 設置這個連接是否可以輸出數據
             */
            urlConnection.setDoOutput(true);
            /**
             * 設置請求的方式
             */
            if(data==null){
                urlConnection.setRequestMethod("GET");
            }else {
                urlConnection.setRequestMethod("POST");
            }
            /**
             * 設置Basic認證
             */
            if(userBean!=null||userBean.getUsername()!= ""||userBean.getPassword()!=""){
            String userMsg = userBean.getUsername() + ":" + userBean.getPassword();
            String base64UserMsg = Base64.encodeToString(userMsg.getBytes(),Base64.DEFAULT);
            final String tokenStr = "Basic " + base64UserMsg;
            urlConnection.addRequestProperty("Authorization", tokenStr);

            }


            /**
             * 設置消息的類型
             */
            urlConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            /**
             * 連接,從上述至此的配置必須要在connect之前完成,實際上它只是建立了一個與服務器的TCP連接
             */
            urlConnection.connect();
            /**
             * -------------使用字節流發送數據--------------
             */
            OutputStream out = urlConnection.getOutputStream();
            /**
             * 緩衝字節流包裝字節流
             */
            BufferedOutputStream bos = new BufferedOutputStream(out);
            /**
             * 把這個字節數組的數據寫入緩衝區中
             */
            bos.write(data);
            /**
             * 刷新緩衝區,發送數據
             */
            bos.flush();
            out.close();
            bos.close();
            /**
             * ------------字符流寫入數據------------
             */

            /**
             * 得到服務端的返回碼是否連接成功
             */
            if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                InputStream in = urlConnection.getInputStream();
                mCallbackListener.onSuccess(in);
            }else{
                // 訪問失敗,重試
                throw new RuntimeException("請求失敗");
            }
    }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("請求失敗");
        }finally{
            urlConnection.disconnect();
        }
    }



}

響應請求泛型封裝

package com.tydfd.tydfdokhttp.util;

import android.os.Handler;
import android.os.Looper;

import com.alibaba.fastjson.JSON;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * @author liudo
 */
public class JsonCallbackListener<T> implements CallbackListener{

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

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

    @Override
    public void onSuccess(InputStream inputStream) {
        String response = getContent(inputStream);
        final T clazz = JSON.parseObject(response,responseClass);
        handler.post(new Runnable() {
            @Override
            public void run() {
                mIJsonDataListener.onSuccess(clazz);
            }
        });

    }

    private String getContent(InputStream inputStream){
        String content=null;
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

            StringBuilder sb = new StringBuilder();

            String line = null;
            try {
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }

            } catch (IOException e) {

                System.out.println("Error=" + e.toString());

            } finally {

                try {

                    inputStream.close();

                } catch (IOException e) {

                    System.out.println("Error=" + e.toString());

                }

            }
            return sb.toString();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return content;
    }

    @Override
    public void onFailure() {

    }

}

請求任務

package com.tydfd.tydfdokhttp.util;



import com.alibaba.fastjson.JSON;
import com.tydfd.tydfdokhttp.http.UserBean;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * @author liudo
 */
public class HttpTask<T> implements Runnable, Delayed {

    private IHttpRequest mIHttpRequest;
    private UserBean mUserBean;

    public HttpTask(T requestData, String url, UserBean userBean,IHttpRequest httpRequest, CallbackListener callbackListener){
        this.mIHttpRequest = httpRequest;
        this.mUserBean = userBean;
        httpRequest.setUrl(url);
        httpRequest.setListener(callbackListener);
        String content = JSON.toJSONString(requestData);
        try {
            httpRequest.setData(content.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try{
            mIHttpRequest.execute(mUserBean);
        }catch (Exception e){
            /**
             * 將失敗的任務添加到重試隊列中
             */
            ThreadPoolManager.getInstance().addDelayTask(this);
        }

    }

    private long delayTime;
    private int retryCount;

    public int getRetryCount(){
        return retryCount;
    }

    public void setRetryCount(int retryCount) {
        this.retryCount = retryCount;
    }

    public long getDelayTime() {
        return delayTime;
    }

    public void setDelayTime(long delayTime) {
        // 設置延遲時間  3000
        this.delayTime = System.currentTimeMillis()+delayTime;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.delayTime - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return 0;
    }
}
IJsonDataListener
package com.tydfd.tydfdokhttp.util;

public interface IJsonDataListener<T> {

    /**
     * 請求成功
     * @param m
     */
    void onSuccess(T m);

    /**
     * 請求失敗
     */
    void onFailure();

}

請求方法封裝

package com.tydfd.tydfdokhttp.http;


import com.tydfd.tydfdokhttp.util.CallbackListener;
import com.tydfd.tydfdokhttp.util.HttpTask;
import com.tydfd.tydfdokhttp.util.IHttpRequest;
import com.tydfd.tydfdokhttp.util.IJsonDataListener;
import com.tydfd.tydfdokhttp.util.JsonCallbackListener;
import com.tydfd.tydfdokhttp.util.JsonHttpRequest;
import com.tydfd.tydfdokhttp.util.ThreadPoolManager;

/**
 * @author liudo
 */
public class NeOkHttp {
    /**
     *
     * @param requestData 請求數據
     * @param url 請求url
     * @param userBean Basic認證
     * @param response
     * @param listener
     * @param <T>
     * @param <M>
     */
    public static<T,M> void sendJsonRequest(T requestData, String url,UserBean userBean,
                                            Class<M> response, IJsonDataListener listener){
        IHttpRequest httpRequest = new JsonHttpRequest();
        CallbackListener callbackListener = new JsonCallbackListener<>(response,listener);
        HttpTask httpTask = new HttpTask(requestData,url,userBean,httpRequest,callbackListener);
        ThreadPoolManager.getInstance().addTask(httpTask);
    }


}

MainActivity

package com.tydfd.httputils;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.util.TimeUtils;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.tydfd.tydfdokhttp.http.NeOkHttp;
import com.tydfd.tydfdokhttp.http.ResponseBean;
import com.tydfd.tydfdokhttp.http.TimeDateUtils;
import com.tydfd.tydfdokhttp.http.UserBean;
import com.tydfd.tydfdokhttp.util.IJsonDataListener;


/**
 * @author liudo
 */
public class MainActivity extends AppCompatActivity {
    private Button mButton;
    private TextView mTextView;
    /**
     * http://www.mxnzp.com/api/holiday/single/20181208 獲取今日運勢http://www.mxnzp.com/api/holiday/single/
     */
    private String url = "http://www.mxnzp.com/api/holiday/single/";
//    private String url2 = "http://192.168.120.194:8080/Demo/ResponseData";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = findViewById(R.id.button);
        mTextView = findViewById(R.id.textView);
        String time = TimeDateUtils.getCurrentDateStr(TimeDateUtils.FORMAT_TYPE_1);
        url = url+ time;
        mButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "點擊了", Toast.LENGTH_SHORT).show();
                UserBean userBean = new UserBean();
                userBean.setUsername("");
                userBean.setPassword("123456");
                NeOkHttp.sendJsonRequest(null, url,userBean, ResponseBean.class, new IJsonDataListener<ResponseBean>() {
                    @Override
                    public void onSuccess(final ResponseBean rb) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mTextView.setText(rb.getData());
                            }
                        });
                        Log.i("===> ",rb.toString());
                    }

                    @Override
                    public void onFailure() {

                    }
                });
            }
        });


    }


    public void Test(){
        TimeUtils.getTimeZoneDatabaseVersion();
    }

}

該http請求框架使用線程池控制線程訪問隊列中的請求,如果請求失敗使用延遲隊列將失敗的請求加入,進而重新發起請求。大致思路就是上面的代碼。不明白的歡迎交流。

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