美文网首页Android开发Android开发
手撸一个简单的网络框架

手撸一个简单的网络框架

作者: changer0 | 来源:发表于2019-02-26 10:54 被阅读46次

开始前

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

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

实现方式:

  • 队列
  • 线程池

网络框架实现步骤

  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

相关文章

  • 手撸一个简单的网络框架

    开始前 网络访问框架关心的问题: 能并发接受多个请求,并返回"用户"需要的数据 重试机制 实现方式: 队列 线程池...

  • 起点

    打算自己手撸一个博客系统,练练手。后端使用go的gin框架+mysql,前端手撸js吧。 应该会挺丑,哈哈~~

  • 徒手撸一个简单的RPC框架(2)——项目改造

    徒手撸一个简单的RPC框架(2)——项目改造 在上一篇的徒手撸一个简单的RPC框架中再最后的服务器和客户端连接的时...

  • ArrayList

    手撸一个简单的arraylist。顺便说下ConcurrentModificationException,出现这个...

  • 手撸一个Koa框架

    为了更好的了解Koa实现的原理,我们会自己手写一个带核心功能的Koa框架。我们先看一下如果使用原生的node.js...

  • 手撸一个RPC框架

    如何调用他人的远程服务? 由于各服务部署在不同机器,服务间的调用免不了网络通信过程,服务消费方每调用一个服务都要写...

  • 徒手撸一个简单的RPC框架

    徒手撸一个简单的RPC框架 之前在牛逼哄哄的 RPC 框架,底层到底什么原理得知了RPC(远程过程调用)简单来说就...

  • 【手撸RPC框架】SpringBoot+Netty4实现RPC框

    【手撸RPC框架】SpringBoot+Netty4实现RPC框架 线程模型 Netty高性能架构设计[https...

  • 手撸 Android 网络库

    Android学习中网络是必不可少的一部分,甚至是重中之重。开发中可能我们会使用一些很成熟优秀的网络框架,如:Ok...

  • Android 实现 @ at , 中间,末尾整体删除,变色功能

    网上搜了搜有发现这个框架 MentionEditText,但不太适合项目需求,于是手撸一个. 需求:@*** 为蓝...

网友评论

    本文标题:手撸一个简单的网络框架

    本文链接:https://www.haomeiwen.com/subject/voaoyqtx.html