借鉴Volley框架思想,自己手写网络请求框架

   来来来,小板凳给大佬们准备好了,先坐下来看完这篇文章再去刺激战场吧,最后一个敌人留给你,最后一颗子弹留给你!!!!!!

看到这篇的文章的大佬,我想都是用过第三方开源框架了的,这是一种共享文化氛围,当然就也免不得是一种伤害,这样呢?并不是说有什么太大的问题,毕竟大多数时候可以提高我们开发效率,减轻我们的负担。但是,但是,但是,喜欢捣腾的大佬们就会陷入深深的思考中,他们不甘,他们会在酱油瓶打满的时候想,我要开始脱胎换骨了。于是啊,他们开始小心翼翼的写自己的轮子,试着摆脱掉停留在用别人轮子的水平阶段。好了,不跟你们皮了,到这里,开始写轮子前,我们要先弄明白几个事情:1.别人能写框架,我就一定也能写,至少要试着写在自己本工作中使用的框架,2.用第三方框架,出了Bug,md,很难得查。3.当框架大量使用时,会造成我们自己程序中大量的代码冗余,程序更加臃肿!

现在开始进入干货阶段,之前熟悉过Volley框架源码,其本身也有缺陷,比如不支持post大数据,不适合上传文件。不过Volley设计的初衷就是为频繁的、数据量小的网络请求而生,借鉴里面的思想,自己写了个解决工作上需求的小型网络请求框架,实现了多任务依次请求,并且完成线程切换操作。我相信这种需求很多公司中都会有,所以重写了Demo,分享出来,望各位大佬给出你们的宝贵建议。Volley的使用与原理解释,建议各位移步:https://www.jianshu.com/p/15e6209d2e6f

1.创建线程池管理类
面对调用层发出的网络请求,都是需要在子线程中执行,我们可以将其分为多个子线程任务(HttpTask),每个任务一个线程全部添加到我们的任务请求队列中,然后再使用线程池做为管理中心将队列中的请求依次执行,执行不成功的,使用jdk提供的RejectedExecutionHandlerrejectedExecutionHandler将超时未完成的任务线程重新添加到请求队列并再次进入线程池执行请求操作,源码看的多大佬们,可能还会想到,我线程池干嘛不自己写呢?对,确实可以自己写,说不定更能满足需求,也更稳定。这里就先用jdk自带就够用了,那种及其骚又让人窒息的操作,就留给大佬去秀吧!
不是有句话说的好么?“源码看的多,能写又会说”,对,没错,这话是我讲的,但是我不会!!!


package com.example.httpvolley.httpvolley;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.LinkedBlockingDeque;

import java.util.concurrent.RejectedExecutionHandler;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

/**

* Created by lvsimple23@163.com on 2018/3/9.

*/

public class ThreadPoolManager {

    //添加任务到请求队列

    private LinkedBlockingDequequeue =new LinkedBlockingDeque<>();

    //添加任务就是添加线程

    public void excute(Runnable runnable){

if (runnable !=null) {

try {

queue.put(runnable);

            }catch (InterruptedException e) {

e.printStackTrace();

            }

}

}

    //把任务放到线程池;

    private ThreadPoolExecutorthreadPoolExecutor;

    private ThreadPoolManager(){

threadPoolExecutor =new ThreadPoolExecutor(4,20,15, TimeUnit.SECONDS,new ArrayBlockingQueue(4));

        //开启传动带

        threadPoolExecutor.execute(runnable);

    }

    //拒绝策略,将超时的线程重新放回线程池;

    private RejectedExecutionHandlerrejectedExecutionHandler =new RejectedExecutionHandler() {

@Override

        public void rejectedExecution(Runnable runnable/*runnable 超时的线程*/, ThreadPoolExecutor threadPoolExecutor) {

try {

queue.put(runnable);

            }catch (InterruptedException e) {

e.printStackTrace();

            }

}

};

    private Runnablerunnable =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 ThreadPoolManagerourInstace =new ThreadPoolManager();

    public static ThreadPoolManagergetourInstace(){

    return  ourInstace;

    }

}

2.将请求与响应分别抽为接口;
请求接口中设置了请求Url,请求参数,请求执行,并回调了响应的结果,说明白点就是HttpService接口持有了HttpListener接口的引用

**
 * 封装响应
 */

public interface HttpListener {
    //接收请求接口中的流数据结果
    void onSuccess(InputStream inputStream);
    void onfalure();
}

/**
 * 封装请求
 */

public interface HttpService {
    void setUrl(String url);
    void setRequestData(byte[] requestData);
    void excute();

    //两个接口之间的关系
    void setHttpCallBack(HttpListener httpListener);
}

3.请求任务建立
请求任务实现Runnable接口,HttpTask为一个线程任务,持有两个接口的引用,因为当我们任务以对象方式封装时,我们并不能确定调用者的请求参数是以什么对象形式传进来的,使用泛型处理,能很好的避免这个问题。run()方法中只需要执行发送请求就ok了!到这里,我们的请求任务是形成了,可是,大家知道吗?任务有是有了,可并没有具体的实现类来操作啊?对不对?对不对?看到这里,明白的请点击下方的喜欢按钮,欢迎大家一起交流!那么我们现在就去写实现类!

/**
 * Created by lvsimple23@163.com on 2018/3/9.
 */

public class HttpTask<T> implements Runnable {
    //接口引用
    private HttpListener httpListener;
    private HttpService httpService;

    //请求参数以对象的形式发送,使用泛型
    public<T> HttpTask(T requestInfo,String url,HttpService httpService,HttpListener httpListener){
        this.httpService = httpService;
        this.httpListener = httpListener;
        httpService.setUrl(url);
        //是两个接口相关联
        httpService.setHttpCallBack(httpListener);
        if(requestInfo != null){
            String requestContent = JSON.toJSONString(requestInfo);
            //设置请求参数
            try {
                httpService.setRequestData(requestContent.getBytes("utf-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        httpService.excute();
    }
}

4.上面封装接口的步骤中是不是说的很清楚了,在这里,我们设置好参数,持有HttpListenter后 ,在哪里进行网络操作?大家都清楚了吧,我相信大家的基本功比我的扎实,这对大家来说并不是问题。怎么操作的?当然的是Android 原生HttpURLConnection,这个我想大家都用过吧,没用过,赶紧放下手里吃鸡的游戏,去补基本API的使用。这里,当我们把requestData以流的方式从网络上写出去后,得到的返回码为200时,拿回来的流数据通过监听返回到onSuccess中,返回后,我们监听接口这时候要做什么?是不是要在这个时候实现我们的监听接口中的方法,不然返回的流数据起什么效果呢?它并不是一个美丽的摆设,供我们欣赏的,我们需要的是使用它。

/**
 * Created by lvsimple23@163.com on 2018/3/9.
 */

public class JsonHttpService implements HttpService {

    private String url;
    private byte[] requestData;
    //持有另外一个接口的引用
    HttpListener httpListener;

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

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

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

    //真的网络操作实现方法
    @Override
    public void excute() {
        HttpconnectPost();
    }
    HttpURLConnection httpURLConnection = null;
    public void HttpconnectPost(){
        URL url = null;
        try {
            url = new URL(this.url);
            httpURLConnection = (HttpURLConnection)url.openConnection();
            httpURLConnection.setConnectTimeout(6000);
            httpURLConnection.setUseCaches(false);
            httpURLConnection.setInstanceFollowRedirects(true);
            httpURLConnection.setReadTimeout(3000);
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setDoInput(true);
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setRequestProperty("Content-Type","application/json; charset=UTF-8");
            httpURLConnection.connect();
            //使用字节流发送数据
            OutputStream outputStream = httpURLConnection.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(outputStream);
            //判断请求数据是否为空,并写入输出流
            if(requestData != null){
                bos.write(requestData);
            }
            bos.flush();
            outputStream.close();
            bos.close();
            //字符流写入数据
            if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK){
                InputStream inputStream = httpURLConnection.getInputStream();
                httpListener.onSuccess(inputStream);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5.实现监听接口,返回来的流数据,我是不是要将它转换为对象,拿到流数据我们首先把它转为String类型(流转String这种不够骚的操作我就不解释了),对不对?没毛病,那么接下来是不是就是要再继续转换为对象(我这里用的Json,也可以是XML.......),然后我们是不是在这个时候要通过写一个中间接口DataListner来堵住传送出来的对象,通过接口再传送会方便很多,对不对,对不对?写到这里问题就来了,我们在HttpTask中excute方法执行并在JsonHttpService类中实现这个方法后,返回来的数据,一直到最后传递给调用,这是不是都是在子线成操作的?不要担心,Handler,Handler ,Handler,Handler handler = new Handler(Looper.getMainLooper()); 这是无缝实现线程切换啊,简直就是万精油般的存在啊!

/**
 * Created by lvsimple23@163.com on 2018/3/10.
 */

public class JsonHttpListener<M> implements HttpListener {
    //响应结果转为对象
    Class<M> responseClass;
    //定义接口
    DataListener<M> dataListener;
    //线程切换
    Handler handler = new Handler(Looper.getMainLooper());

    public JsonHttpListener(Class<M> responseClass , DataListener dataListener){
        this.responseClass = responseClass;
        this.dataListener = dataListener;
    }
    @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);
                }
            }
        });
    }

    //流转字符串
    private String getContent(InputStream inputStream) {
        String content = null;
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder stringBuilder = new StringBuilder();
            String line = null;
            try {
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.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 stringBuilder.toString();
        }catch(Exception exception){
            exception.printStackTrace();
        }
        return content;
    }

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

/**
 * Created by lvsimple23@163.com on 2018/3/10.
 */

public interface DataListener<M> {
    //传回成功的泛型对象
    void onSuccess(M m);

    void onFalure();
}

6.调用类,我这里就只写一个json的请求,其余的大家可以参考这种思想去另写,或者去拓展。这里的请求与返回的结果不确定,最后也是使用了两个泛型。

/**
 * Created by lvsimple23@163.com on 2018/3/10.
 */

public class VolleyBrother {
    public static<T,M> void sendJsonRequest(T requestInfo,String url,Class<M> response,DataListener<M> /*回调响应数据接口*/dataListener){
        HttpService httpService = new JsonHttpService();
        HttpListener httpListener = new JsonHttpListener(response,dataListener);
        HttpTask<T> httpTask = new HttpTask<T>(requestInfo,url,httpService,httpListener);
        ThreadPoolManager.getourInstace().excute(httpTask);
    }
}

7.我们来看看,使用起来是否会方便一些!最后,大家可以自行去掉线程切换部分的代码测试一下,看看是否还能继续跑起来!Demo全部代码,已经提交到github,地址:https://github.com/guojie1992/HttpVolley

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private String url = "http://v.juhe.cn/weather/index?&cityname=长沙&key=自己的key";    /*相关的请求地址,自己去聚合网申请,种类繁多*/

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.textView);
    }

    public void onclick(View view) {
        VolleyBrother.sendJsonRequest(null, url, ResponsData.class, new DataListener<ResponsData>() {
            @Override
            public void onSuccess(ResponsData responsData) {
                textView.setText("城市:" + responsData.result.today.city + "今天有" + responsData.result.today.temperature + "度");
            }

            @Override
            public void onFalure() {

            }
        });
    }
}
图片.png

欢迎大家阅读,并提出建议!我在刺激战场等你们,等你们成为我的最后一个敌人,等你们与我用最后一颗子弹见分晓!

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 又到了期末季,从大学到小学再到中学,学生们依次迎来了努力一学期之后的考场验收。考试本身是很严肃的,不仅体现...
    王老牛阅读 952评论 0 0
  • 晴转多云,平淡。何为真理?这是古今中外所有聪明人都在试图解释的命题,以致留下浩如烟海的名著典籍,来阐述自以为是的个...
    生虎日记阅读 304评论 0 0
  • 这大过年的,说说应景的吧! 过年,除了鞭炮、年夜饭、新村祝福,自然少不了来自七大姑八大姨的亲切问候。 年终奖多少?...
    迷人的葱白阅读 332评论 6 5