搭建一个简单的Http请求框架

作者: thinkChao | 来源:发表于2017-06-25 11:19 被阅读777次

    目前Android常用的网络请求框架有很多,比如Volley和Rettofit,功能强大且调用方便,学习这些框架的使用还算简单。不过只会调用这些API,其实跟学习Android系统框架层自带的API是一样的,无非是多用几次就熟悉了。API会的再多,对自己的开发水平的提高作用甚少。这就跟学英语类似,多学会几个API,就跟多学几个单词一样,单词是学不完的,而且学而不用,很快就会忘掉。

    重点还是培养自己的思维方式,学习利用各种设计模式和编程原则,如何搭建一个框架,如何封装,如何解耦合,如何给用户留下方便易用的API来供调用。

    这两天跟着stay的一个项目学习,尝试着搭建一个网络请求框架出来,项目名称我定为SimpleHttp。这篇文章对开发思路和过程做一个简单的整理,源码也跟随进度上传到Github,每次更新对应着不同的tag,tag名称为版本号,方便下载查看。

    设计方式:在对一个框架的架构进行设计时,我的理解是应该遵循自顶向下的原则,要事先设计好有那些功能模块、要预留哪些API供开发者调用、要有哪些类、哪些方法,并设计出UML类图作为指导。

    不过作为经验不足的程序员来说,架构的设计难度还是挺大的,也不知道应该从何做起。所以这个项目使用的方法是:先实现最基本的功能,然后再一步步进行封装、功能的扩展以及优化。

    封装HttpUrlConnection

    对应代码版本:V1.0

    1、首先新建一个HttpUrlConnectionUtil类,先分别封装实现最常用的GETPOST方法,其它的例如PUTDELETE暂时不实现。

    2、第一层封装:我们发现,在用GETPOST方法进行Http请求时,方法中需要输入许多参数,包括请求行(请求方法、URL)、头(Header)、体(请求参数)。GETPOST的区别就是,GET方法的请求参数是写在URL里的,POST的参数是写在请求体中的,然后与url直接拼接在一起。

    我们发现,调用这两个方法有时需要输入多个参数,这样调用起来感觉很不友好,代码看起来也乱,所以我们可以想到将这些参数都封装在一个类中,这里就新建一个Request类来封装这些请求参数,如下:

        public enum RequestMethod{GET,POST,PUT,DELETE}
        public RequestMethod method;
        public String url;
        public Map<String,String> header;
        public String content;
    

    为了测试方便,暂时先将成员变量都设置成public的。

    3、封转完请求参数后,我们就可以直接调用SimpleHttp.get()SimpleHttp.post()方法来进行Http请求了。但是问题也很明显,因为除了GETPOST,我们还有其它方法没实现呢,这样不仅暴露的API多,开发人员需要记住的API也多,所以这里我们还需要做进一步的封装。

    所以我们在HttpUrlConnectionUtil类中新建一个execute()方法,来统一处理请求,通过一个分支判断来实现,如下:

        public static String execute(Request request) throws IOException {
            switch (request.method){
                case GET:
                    return get(request);
                case POST:
                    return post(request);
                //TODO:后面还可以添加其它请求方法
            }
            return null;
        }
    

    做到这一步我们已经对Http请求做了一个基础的封装,当然还有很多瑕疵,这个就需要仔细考虑一些细节问题了。这里主要是学习一下整体的框架搭建的思路,所以不再深究一些细节问题。

    实现将请求切换到子线程

    对应代码版本:V1.1

    Android系统要求,网络请求不能在主线程中进行操作,必须切换到子线程,所以这里我们要考虑如何实现在子线程中进行操作。Android线程间的通信也是一个非常重要的模块,主角是Handler,不过让我们自己用Handler来实现线程间的通信还是很麻烦的,那又是一个需要深入学习的功能模块,所以我们这里借用系统封装好的框架AsyncTask来实现。

    1、我们新建一个RequestTask类,并继承AsyncTask,然后把HttpUrlConnectionUtil类中的execute()方法放到doInBackground()方法中去执行即可。

    @Override
        protected Object doInBackground(Object[] params) {
            try {
                return HttpUrlConnectionUtil.execute(request);
            } catch (IOException e) {
                return e;
            }
        }
    

    2、执行结束后,要如何获取请求返回的结果呢?如果熟悉AsyncTask,我们知道子线程的执行操作在doInbackGround()方法中执行,而它返回的数据会传给onPostExecute()方法,所以我们可以在这个方法中获取返回数据。

    但问题是当onPostExecute()方法获得这个返回的数据后,还要将结果传给程序的最上层,就是在我们写的测试方法中取得这个数据,这个要怎么做呢?答案是使用回调。这里大体介绍一下回调的实现,具体可以参考代码:

    1)首先建立一个回调接口ICallback,该接口有两个回调方法OnSuccessOnFailure

    public interface ICallback {
        void onSuccess(String result);
        void onFailure(Exception e);
    }
    

    2)在Request类中设置一个Callback方法:

        public ICallback iCallBack;
    
        public void setCallback(ICallback iCallback) {
            this.iCallBack = iCallback;
        }
    

    3)在OnPostExecute()方法中调用ICallback的两个方法,并传入在doInBackground()中得到的结果:

     @Override
        protected void onPostExecute(Object o) {
            super.onPostExecute(o);
            if(o instanceof Exception){
                request.iCallBack.onFailure((Exception) o);
            }else {
                request.iCallBack.onSuccess((String) o);
            }
        }
    

    4)在我们的测试方法中调用Callback方法,得到结果:

    request.setCallback(new ICallback() {
                @Override
                public void onSuccess(String result) {
                    Log.e("Test result3:",result);
                }
                @Override
                public void onFailure(Exception e) {
                    e.printStackTrace();
                }
            });
    

    切换到子线程的操作就基本完成了。

    处理返回的数据,实现解析Json数据

    对应代码版本:V1.2

    之前统一返回数据的都是String类型,这很明显不能满足需求,比如还有常用的Json数据、文件等等,这节就先实现Json相关的功能。

    1、在之前的实现当中,我们的ICallback接口中的onSuccess()方法里的参数是String格式的,所以这就导致我们只能返回String格式的数据,如果我们想返回其它的任意我们想要的格式(比如Json、Xml)要怎么做呢?答案是使用泛型,也就是在ICallback接口中使用泛型:

    public interface ICallback<T> {
        void onSuccess(T result);
        void onFailure(Exception e);
        T parse(HttpURLConnection conn) throws Exception;
    }
    

    这样一来,接口当中的T我们声明为什么格式,那么Onsuccess()参数中的T就是什么格式的。

    2、当我们能够接收Json数据了,拿到Json数据后,我们还要进行解析的操作,例如将Json解析成我们自定义的JavaBean,不同的数据格式对应不同的解析操作,这一步要在哪里完成呢?

    先看我们之前在只接收String格式的数据时是在哪里做的,之前是直接在HttpURLConnection类中返回String结果。但现在数据类型多了,我们既要返回不同的数据类型,又要对不同的类型进行相应的解析。但是我们不可能将这些操作都放在HttpURLConnection类中,因为根据单一职责原则,这个类只负责请求,接收和解析操作就放到别的类中去吧,那如何来做呢?

    之前这个类返回的格式是String,现在返回的格式有多种,但是一个方法必须要有一个统一的返回格式吧,所以我们可以考虑提取get()post()方法中的共性,来达到返回相同数据格式的目的。查看代码就很明了了,这两个方法的共性就是都会先建立connection,那么我们就返回connection,也就是HttpUrlConnection格式,把接收数据的操作移到别的类中。这里我们移到了Callback类中,它实现了ICallback接口:

    public abstract class Callback<T> implements ICallback<T> {
    
        @Override
        public T parse(HttpURLConnection conn) throws Exception {
            //发起请求
            conn.connect();
            //获取结果
            InputStream in = conn.getInputStream();
            InputStreamReader isReader = new InputStreamReader(in);//将字节转成字符,适配器模式
            BufferedReader reader = new BufferedReader(isReader);
            StringBuilder result = new StringBuilder();
            String line;
            while((line = reader.readLine()) != null){
                result.append(line);
            }
            in.close();
            //返回结果
            return parseResult(result.toString());
        }
    

    3、接收数据后,还要针对不同的数据格式进行不同的解析操作,格式不同,解析不同。我们自然想到,针对不同的格式,我们可以通过继承Callback类,在子类中实现不同的解析,例如我们这里就实现了一个JsonCallback子类来对Json数据进行解析:

    public abstract class JsonCallback<T> extends Callback<T> {
        @Override
        protected T parseResult(String result) throws Exception {
            JSONObject json = new JSONObject(result);
            JSONObject data = json.optJSONObject("data");
            Gson gson = new Gson();
            Type type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            return gson.fromJson(data.toString(), type);
        }
    
    }
    

    这一小节整理的有些啰嗦,也没有讲很清楚,还是要多思考,仔细想想应该如何来实现。现在发现,要把自己理解的东西讲明白,也是一件不容易的事,更何况自己的理解也不是很到位,只能是多实践、多总结吧。

    实现文件下载和进度更新

    对应代码版本:V1.3

    实现文件下载功能与实现返回Json或其它数据格式类似,就是实现一个FileCallback子类来返回相应的结果。

    不过文件下载的过程就是一个接收返回数据的过程,数据接收完了,文件也就下载完了,它不需要返回什么结果,更不需要解析。所以我们这里返回一个path(下载路径)就可以了,要不然没什么可返回的。

    这里整理一下大体过程,具体参考代码。

    1、首先我们要在Callback中设置一个setPath()方法,因为我们要传入一个下载路径吧。

    2、然后在接收数据的方法中做一个判断,判断path是否为空,如果为空,则是请求的其它格式的数据,则做相应的操作。如果非空则是下载文件,则执行下载操作。

    3、实现进度更新,这里也用到了回调,注意这里要给是否需要进度更新加一个判断,因为如果不是下载文件,是不需要这个功能的。具体参考代码:V1.5,因为这个功能是后来添加上的,是在添加异常处理之后,所以对应代码版本为V1.5。

    异常处理

    对应代码版本:V1.4

    1、先看都有哪些地方可能会出现异常,需要处理:

    1)在HttpUrlConnectionUtil中请求Http时,我们想返回status,要如何拿到呢?

    2)如何处理TimeOut,出现TimeOut的可能的地方:请求时、解析数据时。(暂时还没实现)

    2、需要返回哪些数据

    1)status

    2)ResponseMessage

    3、如何来做?

    1)出现异常也就是Exception,服务器会返回status(200、403等等),但是Exception里没有status,所以我们可以考虑自己封装一个HttpException,在里面加入status,这样我们就可以i在回调的时候取得这个状态码了。

    2)将所有的Exception都替换成我们自定义的HttpException。

    取消Http请求的实现

    待更……

    相关文章

      网友评论

        本文标题:搭建一个简单的Http请求框架

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