手撸一个简单的网络框架

开始前

网络访问框架关心的问题:

  • 能并发接受多个请求,并返回"用户"需要的数据
  • 重试机制

实现方式:

  • 队列
  • 线程池

网络框架实现步骤

  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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容