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

来源:https://www.cnblogs.com/ganchuanpu/archive/2018/05/24/9083000.html
-Advertisement-
Play Games

用戶在調用層(Activity或Service中),發起一個網路請求,該請求肯定包含url,請求參數(requestParameter),以及我們需要給調用層提供一個請求成功或失敗以後回調監聽的介面dataListener(這一點與Volley類似)。 在框架層,每一次用戶請求可以看做一個Http任 ...


用戶在調用層(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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 操作環境:Centos 問 題:The table 'XXX' is full !!! 當碰到此資料庫報錯提示的時候,,, 我,,,有點懵,,, 登錄伺服器查看,,發現 我了個去,,我絕不承認這個伺服器是我搭的,,(手動滑稽X2) MySQL資料庫的位置放到了分區/dev/mapper/centos ...
  • 本文說明瞭redis的基本用法,python與redis交互以及與mysql交互使用 ...
  • 1為什麼要做性能優化? 手機性能越來越好,不用糾結這些細微的性能? Android每一個應用都是運行的獨立的Dalivk虛擬機,根據不同的手機分配的可用記憶體可能只有(32M、64M等),所謂的4GB、6GB運行記憶體其實對於我們的應用不是可以任意索取 詳情:http://10.158.0.33/bbs ...
  • 在開發中,有時需要兩個或多個APP版本,每個版本的改動,不是很多,但是需要另外打包,那麼我們就有兩套方案: 1.重新開發,把代碼複製一遍,然後在修改; 2.用一套代碼,根據需求生成不同的包; 我們一般會用第二個方案,這時就需要根據不同的需求,創建不同的target;通過編譯打包不同的target,做 ...
  • (1)安裝Node.js 首先您需要安裝 Node.js,後續會使用到其中的 NPM 工具。 (2)安裝JDK 需要安裝JDK,官網下載安裝。命令視窗中輸入 java -Xmx2048m -version 進行檢查。32位系統環境下無法運行Xmx2048m的記憶體設置。 系統變數→新建 JAVA_HO ...
  • 一、知識介紹 ①res資源圖片是放在項目res文件下的資源圖片 ②BitMap點陣圖,一般文件尾碼為BMP,需要編碼器編碼,如RGB565,RGB8888等。一種逐像素的顯示對象,其執行效率高,但缺點也很明顯,存儲效率低。 ③Drawable,通用的圖形對象,它可以裝載常用的圖像,GIF,PNG,JP ...
  • "iOS_系統原生分享 CSDN博客" "通過UIActivityViewController實現更多分享服務 簡書" "UIActivity UIKit _ Apple Developer Documentation" ...
  • 享受高清晰影院般的大屏幕電影帶來的快樂,單純的iOS設備實現這些是不可能的。蘋果有一套解決方案,iOS設備把這些視頻和音效數據無線傳輸(WiFi或藍牙)Apple TV,然後由Apple TV將視頻和音效數據輸出到與它連接的高清電視和高保真音響設備。 通過AirPlay可以將iOS和Mac設備屏幕與 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...