1. 程式人生 > >Volley手寫屬於自己的萬能網絡訪問框架

Volley手寫屬於自己的萬能網絡訪問框架

info 鏈表實現 fas getname 字符串轉換成對象 gets 等等 stc exe

用戶在調用層(Activity或Service中),發起一個網絡請求,該請求肯定包含url,請求參數(requestParameter),以及我們需要給調用層提供一個請求成功或失敗以後回調監聽的接口dataListener(這一點與Volley類似)。

在框架層,每一次用戶請求可以看做一個Http任務,這些任務我們可以用一個請求隊列存儲起來,框架工作時不斷地從請求隊列中取出任務放到處理中心中,處理中心是一個線程池ThreadPool。使用線程池可以帶來3個好處:
1.降低資源消耗:通過重用已經創建的線程來降低線程創建和銷毀的消耗
2.提高響應速度:任務到達時不需要等待線程創建就可以立即執行。


3.提高線程的可管理性:線程池可以統一管理、分配、調優和監控。

由於用戶請求的數量是不確定的,所以請求隊列的長度應該沒有限制,所以這裏的數據結構,我們應該使用鏈表。正好,java給我們提供了一個阻塞式隊列LinkedBlockingQueue,它是一個單向鏈表實現的無界阻塞隊列,先進先出,支持多線程並發操作。

再仔細考慮一下框架層的操作,用戶不斷發請求產生HttpTask,處理中心不斷從請求隊列中去任務,這和那個生產者消費者模式是不是很像?所以我們還需要考慮並發和同步問題,而LinkedBlockingQueue是線程安全的,實現了先進先出等特性,是作為生產者消費者的首選。LinkedBlockingQueue 可以指定隊列的容量,也可以不指定,不指定的話,默認最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在隊列滿的時候會阻塞直到有隊列成員被消費,take方法在隊列空的時候會阻塞,直到有隊列成員被放進來。

至於處理中心的線程池,我們使用jdk裏的ThreadPoolExecutor,具體用法可以自行百度,唯一需要註意的就是其中的拒絕策略。

技術分享圖片

代碼編寫

首先,我們需要一個線程池的管理類,來管理請求隊列和處理中心處理任務。由於系統中僅有一個線程池管理的類,所以該類應該設計成單例模式

ThreadPoolManager.java

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/** 1. 線程池管理類 */ public class ThreadPoolManager { //1.將請求任務放到請求隊列中 //通過阻塞式隊列來存儲任務 private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); //添加任務 public void execute( Runnable runnable ){ if( runnable != null ) { try { queue.put(runnable); } catch (InterruptedException e) { e.printStackTrace(); } } } //2.把隊列中的任務放到線程池 private ThreadPoolExecutor threadPoolExecutor ; private ThreadPoolManager(){ threadPoolExecutor = new ThreadPoolExecutor(4,20,15, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4),rejectedExecutionHandler); //運行線程池 threadPoolExecutor.execute(runnable); } //當線程數超過maxPoolSize或者keep-alive時間超時時執行拒絕策略 private RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() { /** * @param runnable 超時被線程池拋棄的線程 * @param threadPoolExecutor */ @Override public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) { //將該線程重新放入請求隊列中 try { queue.put(runnable); } catch (InterruptedException e) { e.printStackTrace(); } } }; //3.讓他們開始工作起來 //整個的工作線程 private Runnable runnable = new Runnable() { @Override public void run() { while(true){ Runnable runnable = null ; //不斷從從請求隊列中取出請求 try { runnable = queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } //如果不為空,放入線程池中執行 if( runnable != null ){ threadPoolExecutor.execute(runnable); } } } }; //單例模式 private static ThreadPoolManager singleInstance = new ThreadPoolManager(); public static ThreadPoolManager getSingleIntance(){ return singleInstance; } }

接下來我們需要將寫兩個接口將用戶的網絡訪問操作分成兩部分(參考架構圖),一個是請求(IHttpRequest),一個是響應(IHttpListener),
在請求接口中,我們需要做的事有三個

  1. 設置url
  2. 設置請求參數
  3. 執行請求

IHttpRequest.java

/**
 * 封裝請求
 */
public interface IHttpRequest {
    void setUrl(String url);
    void setRequestData( byte[] requestData );
    void execute();
    //需要設置請求和響應兩個接口之間的關系
    void setHttpCallBack( IHttpListener httpListener );
}

在響應接口中,我們需要做的事也很簡單

  1. 處理結果
  2. 回調應用層

IHttpListener.java

import java.io.InputStream;

/**
 * 封裝響應
 */
public interface IHttpListener {
    //接受上一個接口的結果
    void onSuccess(InputStream inputStream);
    void onFailure();
}

接下來我們編寫代表請求任務的類HttpTask,讓它實現Runnable接口,並且維護IHttpRequest和IHttpListener兩個接口的引用,在HttpTask的構造方法中,進行一些初始化操作,設置請求的url,請求參數,設置監聽器等等。在將請求參數對象轉換成字符串的過程中,我們使用了阿裏的fastjson這個jar包。最後在run方法中,執行httpRequest的請求。另外註意這裏泛型的使用。

HttpTask.java

import com.alibaba.fastjson.JSON;

import java.io.UnsupportedEncodingException;


public class HttpTask<T> implements Runnable {
    private IHttpRequest httpRequest;
    private IHttpListener httpListener;
    public<T> HttpTask( T requestInfo , String url , IHttpRequest httpRequest, IHttpListener httpListener){
        this.httpRequest = httpRequest;
        this.httpListener = httpListener;
        //設置url
        this.httpRequest.setUrl(url);
        //設置響應回調
        this.httpRequest.setHttpCallBack(httpListener);
        //設置請求參數
        if( requestInfo != null ){
            //將用戶發送的請求參數對象轉換成字符串
            String requestContent = JSON.toJSONString(requestInfo);
            //字符串轉byte數組
            try {
                this.httpRequest.setRequestData(requestContent.getBytes("utf-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

    }
    @Override
    public void run() {
        httpRequest.execute();
    }
}

接下來我們編寫IHttpRequest的實現類,JsonHttpRequest , 在重寫的execute方法中,使用原生的HttpURLConnection執行網絡請求,請求成功後,回調IHttpListener的onSuccess方法。

如果想要傳送其他數據(圖片,文件等),同樣實現IHttpRequest該接口即可,所以這個框架的擴展性十分良好。

JsonHttpRequest.java

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

/**
 * Json版Http請求
 */

public class JsonHttpRequest implements IHttpRequest{

    private String url ;
    private byte[] requestData ;
    private IHttpListener httpListener;

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

    @Override
    public void setRequestData(byte[] requestData) {
        this.requestData = requestData;
    }

    //原生的網絡操作在這裏實現
    @Override
    public void execute() {
        httpUrlconnPost();
    }

    private HttpURLConnection urlConnection = null ;

    public void httpUrlconnPost(){
        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);
            //設置請求的方式
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            urlConnection.connect();

            //使用字節流發送數據
            OutputStream out = urlConnection.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(out);
            if( requestData != null ){
                //把字節數組的數據寫入緩沖區
                bos.write(requestData);
            }
            //刷新緩沖區,發送數據
            bos.flush();
            out.close();
            bos.close();

            //獲得服務器響應
            if( urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK){
                InputStream in = urlConnection.getInputStream();
                //回調監聽器的listener方法
                httpListener.onSuccess(in);
            }
        }catch ( Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void setHttpCallBack(IHttpListener httpListener) {
        this.httpListener = httpListener;
    }
}

接下來是IHttpListener的Json版本實現類,在該類中要註意的事就是回調結果給用戶時要進行線程的切換(使用Handler),並且要將返回的json字符串轉換成泛型對象(該對象由用戶自定義)。

JsonHttpListener.java

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;

/**
 * Json版本的httpListener
 */

public class JsonHttpListener<M> implements IHttpListener{
    Class<M> responseClass;
    private IDataListener<M> dataListener;

    public JsonHttpListener(Class<M> responseClass, IDataListener<M> dataListener) {
        this.responseClass = responseClass;
        this.dataListener = dataListener;
    }

    //用於切換線程
    Handler handler = new Handler(Looper.getMainLooper());
    @Override
    public void onSuccess(InputStream inputStream) {
        //獲取響應結果,把byte數據轉換成String數據
        String content = getContent(inputStream);
        //將json字符串轉換成對象
        final M response = JSON.parseObject(content,responseClass);
        //把結果傳送到調用層
        handler.post(new Runnable() {
            @Override
            public void run() {
                if( dataListener != null ){
                    dataListener.onSuccess(response);
                }

            }
        });

    }

    @Override
    public void onFailure() {
        handler.post(new Runnable() {
            @Override
            public void run() {
                if( dataListener != null ){
                    dataListener.onFailure();
                }

            }
        });

    }

    /**
     * 將流轉換成字符串
     * @param inputStream
     * @return
     */
    private String getContent(InputStream inputStream){
        String content = null ;
        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 ){
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }
}

最後兩步

  1. 請求成功後,返回結果給調用層時,我們還要寫一個數據返回的統一接口IDataListener來給用戶使用(跟Volley一樣)
  2. 不能讓用戶自己去創建HttpTask,用戶請求也需要一個統一接口。

這樣我們框架的封裝性會更好。

IDataListener.java

/**
 * 回調調用層的接口,數據返回的統一接口
 */

public interface IDataListener<M> {
    void onSuccess( M m );
    void onFailure();
}

Volley.java

/**
 * 用戶請求的統一接口
 */
public class Volley {
    public static<T,M> void sendJsonRequest( T requestInfo , String url , Class<M> response , IDataListener<M> dataListener){
        IHttpRequest httpRequest = new JsonHttpRequest();
        IHttpListener httpListener = new JsonHttpListener<>(response,dataListener);
        HttpTask<T> httpTask = new HttpTask<T>(requestInfo,url,httpRequest,httpListener);
        ThreadPoolManager.getSingleIntance().execute(httpTask);
    }
}

最後在MainActivity中測試

public void onClick(View view) {
                //Student為自己定義的javaBean
                Volley.sendJsonRequest(null, url, Student.class, new IDataListener<Student>() {
                    @Override
                    public void onSuccess(Student student) {
                        Toast.makeText(MainActivity.this,student.getName(),Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onFailure() {
                        Toast.makeText(MainActivity.this,"請求失敗",Toast.LENGTH_SHORT).show();
                    }
                });
            }

想要拓展更多的功能,請求更多類型的數據,我們只需要實現IHttpRequest和IHttpListener這兩個接口就行了。

參考:https://www.jianshu.com/p/396ada3885cc

Volley手寫屬於自己的萬能網絡訪問框架