Android 巧妙封装,基于Retrofit+RxJava网络框架“Leopard”---完整浅析

Leopard

《Android 巧妙封装,基于Retrofit+RxJava网络框架“Leopard”---完整浅析》

转载请注明来自 傻小孩b_移动开发
http://www.jianshu.com/users/d388bcf9c4d3
喜欢的可以关注我,不定期总结文章!您的支持是我的动力哈!

前言

Leopard 意为猎豹,在所有猫科动物中。猎豹体型最小,速度快、最稳定。这也是笔者想用这个名字命名这个Kit的原因。希望这个Kit能对部分开发者对于网络框架封装的一些思路有所帮助,笔者也在奋斗路上坚持总结进步中,共勉!最后,有任何问题可以提issuse给我,或者直接联系本人(QQ:708854877 傻小孩b),喜欢可以为我点个star,你们的支持是我最大的动力~谢谢!

Leopard,目前实现功能

提供一个满足日常需求的HTTP线程安全请求封装Library,底层由Retrofit+Okhttp+RxJava支持,通过构建者builder设计模式实现。目前实现POST、GET(支持自定义头文件、表单键值对请求、自定义数据源等基本请求)、文件上传管理(支持单文件上传与多文件上传,不限制文件类型)、文件下载管理(支持单文件下载与多文件下载、不限制文件类型、支持大文件下载与断点下载)

演示

Leopard演示.jpg

Leopard,引用方法:

注:目前只支持Android Studio版本引用。

1.1 版本 使用方法:

一、在application中的build.gradle引入:

repositories {
    maven { url = 'https://dl.bintray.com/yuancloud/maven/' }
    ...
}

compile 'cn.yuancloud.app:leopardkit:1.1'

详情使用方法可以看kit里面的sample工程,下面也会举例使用方法。

Leopard,源码浅析

一、构建者模式

通常,大部分开发者都会用单例模式去封装网络框架。的确,对于网络请求严重消耗内存的对象,单例模式很大程度减少了内存开销啊。但是,单例模式职责太单一,灵活性真的不高。所以在这里我强烈建议用构建者模式,需要什么资源只要通知单一职责构建者Builder即可,这样不仅仅可以减少内存开销、又可以灵活性构建需要的对象,一举两得。

二、7个Factory支持

这里的Factory,包括Converter.Factory与Interceptor支持。目前包括Retrofit底层已经实现的GsonConverterFactory与RxJavaCallAdapterFactory,Leopard 添加了额外5个Factory,下面具体简单说明下额外的5个Factory与Retrofit底层已经实现的Factory。
(1)GsonConverterFactory
Retrofit底层支持Gson,这个Factory提供当你需要调整json里面的一些格式的时候可以使用。
(2)RxJavaCallAdapterFactory
当你需要结合RxJava的时候,而不是仅仅使用原生Retrofit请求响应回调的Call。这时候你需要回调一个观察者(Observable)的时候,必须构造的时候添加这个Factory。
(3)RequestComFactory
默认必须要添加的Factory,为了拦截请求时候的request与response。
(4)RequestJsonFactory
当你需要向你的服务器请求非键值对,而是自定义对象转后后的json数据的时候。必须构造的时候添加这个Factory,底层会自动识别帮你转化。
(5)UploadFileFactory
顾名思义,当你需要进行上传文件的时候,在构造client的时需要添加这个Factory,底层文件类型头信息会自动生成。
(6)DownLoadFileFactory
顾名思义,当你需要进行下载文件的时候,在构造client的时需要添加这个Factory,底层会自动根据文件类型自动生成头信息,默认开启断点续传,下载过程可以通过DownLoadManager管理控制。
(7)HeaderAddFactory
当你需要自定义头文件的时候,,在构造client的时需要添加这个Factory。底层会自动帮你添加到请求头。

三、关于基本请求

RxJava的好处,可以保证线程执行安全。由于网络请求不能执行在主线程,因此在Leopard中,将所有网络执行都放在io线程中,确保线程执行安全。例如以下配置:

final Observable.Transformer schedulersTransformer = new Observable.Transformer() {
    @Override
    public Object call(Object observable) {
        return ((Observable) observable)
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                ;
    }
};

在Leopard中,有基本的已经写好的6个入口,开发者可以根据自己的需求去继承自定义入口

    @POST("{path}")
    Observable<ResponseBody> post(
            @Path(value = "path", encoded = true) String path,
            @QueryMap Map<String, Object> map);

    @GET("{path}")
    Observable<ResponseBody> get(
            @Path(value = "path", encoded = true) String path,
            @QueryMap Map<String, Object> map);

    @POST("{path}")
    Observable<ResponseBody> postJSON(
            @Path(value = "path", encoded = true) String path,
            @Body RequestBody route);

    @GET("{path}")
    Observable<ResponseBody> getJSON(
            @Path(value = "path", encoded = true) String path,
            @Body RequestBody route);

    @Multipart
    @POST("{path}")
    Observable<ResponseBody> uploadFile(
            @Path(value = "path", encoded = true) String url,
            @PartMap() Map<String, RequestBody> maps);

    //支持大文件
//    @Streaming
    @GET
    Observable<ResponseBody> downloadFile(
            @Url String fileUrl);

详细的源码解析会在笔者博客简书分析。

四、关于下载

对于Leopard的下载管理模式,借鉴了Android原生DownLoadManager下载管理的模式--DownLoadManager与DownLoadTask结合的机制。DownLoadTask负责单一职责,为每个下载 任务提供下载服务(缓存管理、开始、暂停、停止等功能)。DownLoadManager作为多DownLoadTask管理者,为多任务提供所有的下载服务。下面是下载的具体实现逻辑图。

Leopard Download.png

详细的源码解析会在笔者博客简书分析。

五、关于上传

常规做法,上传与键值对Reqtuest有类似的操作逻辑。将File做为键值对的value,将上传File的文件信息作为key,然后向服务器进行get请求即可。这里要处理的是对文件上传进度的监听,对于okhttp3底层封装的RequestBody,并没有对进度字节流作为缓存处理,因此在Leopard中需要重写RequestBody,才能对上传缓存做处理。下面是上传具体逻辑图。

LeoPard UpLoad.png

详细的源码解析会在笔者博客简书分析。

Leopard,使用方法(自动化构建)

为了减少使用者使用复杂度,leopard里面有集成好自动构建的对象,采用的是单例模式。不过笔者还是推荐根据需求自定义,使用方法如下:

0.初始化
// 初始化主机域名与上下文
// 建议传入getApplication
LeopardHttp.init("http://wxwusy.applinzi.com/leopardWeb/app/",this);
1.基本请求

首先在请求参数的时候必须定义一个model,并且继承Leopard里面的类BaseEnetity,具体定义的model如下例子:

public class RequestPostModel extends BaseEnetity{
    @Override
    public String getRuqestURL() {
        return "sample/post.php";//你的访问url
    }

  //请求参数
    private String data;
    private String time;

    public RequestPostModel(String data, String time) {
        this.data = data;
        this.time = time;
    }

  //必须构建get set方法
    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }
}
1.1 post与get键值对请求
1.1.1 POST
 /**
     *
     * @param type 请求类型,具体看HttpMethod,提供了四种请求类型
     * @param context 上下文
     * @param enetity 请求model
     * @param httpRespondResult 回调接口
     */
    LeopardHttp.SEND(HttpMethod.POST,getActivity(),new RequestPostModel("leopard", Utils.getNowTime()), new HttpRespondResult(getActivity()) {
            @Override
            public void onSuccess(String content) {//json
                resonseData.setText("onSuccess \n"+content);
            }

            @Override
            public void onFailure(Throwable error, String content) {
                resonseData.setText("onFailure \n"+content);
            }
        });
1.1.2 GET
 /**
     *
     * @param type 请求类型,具体看HttpMethod,提供了四种请求类型
     * @param context 上下文
     * @param enetity 请求model
     * @param httpRespondResult 回调接口
     */
    LeopardHttp.SEND(HttpMethod.GET,getActivity(),new RequestGetModel("leopard", Utils.getNowTime()), new HttpRespondResult(getActivity()) {
            @Override
            public void onSuccess(String content) {//json
                resonseData.setText("onSuccess \n"+content);
            }

            @Override
            public void onFailure(Throwable error, String content) {
                resonseData.setText("onFailure \n"+content);
            }
        });
1.2 post与get自定义Json对象请求
1.2.1 POST JSON
 /**
     *
     * @param type 请求类型,具体看HttpMethod,提供了四种请求类型
     * @param enetity 请求model
     * @param httpRespondResult 回调接口
     */
    LeopardHttp.SEND(HttpMethod.POST_JSON,getActivity(),new RequestPostJsonModel("leopard", Utils.getNowTime()), new HttpRespondResult(getActivity()) {
            @Override
            public void onSuccess(String content) {//json
                resonseData.setText("onSuccess \n"+content);
            }

            @Override
            public void onFailure(Throwable error, String content) {
                resonseData.setText("onFailure \n"+content);
            }
        });
1.3post与get请求,带头部

POST 带头部请求


        HashMap<String,String> headers = new HashMap<>();
        headers.put("apikey","dqwijotfpgjweigowethiuhqwqpqeqp");
        headers.put("apiSecret","ojtowejioweqwcxzcasdqweqrfrrqrqw");
        headers.put("name","leopard");

  /**
     *
     * @param type 请求类型,具体看HttpMethod,提供了四种请求类型
     * @param context 上下文
     * @param enetity 请求model
     * @param header 请求头文件
     * @param httpRespondResult 回调接口
     */
        LeopardHttp.SEND(HttpMethod.POST,getActivity(),new RequestPostModel("leopard", Utils.getNowTime()),headers, new HttpRespondResult(getActivity()) {
            @Override
            public void onSuccess(String content) {
                resonseData.setText("onSuccess \n"+content);

            }

            @Override
            public void onFailure(Throwable error, String content) {
                resonseData.setText("onFailure \n"+content);
            }
        });

GET 带头部请求


        HashMap<String,String> headers = new HashMap<>();
        headers.put("apikey","dqwijotfpgjweigowethiuhqwqpqeqp");
        headers.put("apiSecret","ojtowejioweqwcxzcasdqweqrfrrqrqw");
        headers.put("name","leopard");

  /**
     *
     * @param type 请求类型,具体看HttpMethod,提供了四种请求类型
     * @param context 上下文
     * @param enetity 请求model
     * @param header 请求头文件
     * @param httpRespondResult 回调接口
     */
        LeopardHttp.SEND(HttpMethod.GET,getActivity(),new RequestPostModel("leopard", Utils.getNowTime()),headers, new HttpRespondResult(getActivity()) {
            @Override
            public void onSuccess(String content) {
                resonseData.setText("onSuccess \n"+content);

            }

            @Override
            public void onFailure(Throwable error, String content) {
                resonseData.setText("onFailure \n"+content);
            }
        });

2.上传管理

首先在上传的时候必须先看下上传封装实体类FileUploadEnetity(部分代码)

    .....
   private String url;
    private List<File> files = new ArrayList<>();

    public FileUploadEnetity(String url, File file) {
        this.url = url;
        this.files.add(file);
        initSize();
    }

    public FileUploadEnetity(String url, List<File> files) {
        this.url = url;
        this.files = files;
        initSize();
    }
    .....

在上传的时候,允许开发者上传你一个或多个文件。如果是上传一个文件的时候,构建的时候只需要传入上传地址与File类型的文件;反之上传多文件,构建的时候只需要传入上传地址与List<File> 类型的文件队列类型。

2.1基本上传
 /**
     * 上传入口
     *
     * @param FileUploadEnetity 上传封装实体类
     * @param IProgress 上传进度回调接口
     */
    LeopardHttp.UPLOAD(new FileUploadEnetity("upload.php",fileList), new IProgress() {
                    @Override
                    public void onProgress(long progress, long total, boolean done) {
                         // progress 返回当前上传的进度 ,total 返回当前上传的总文件大小
                        if (done){
                            progressDialog.dismiss();
                            Toast.makeText(getActivity(),"所有图片上传成功!!",Toast.LENGTH_SHORT).show();
                        }
                    }
                });

3.下载管理

首先底层断点信息数据库存储由Greendao数据库框架支持。其次,在下载的时候必须看下实体封装类DownloadInfo ,在进行添加下载Task的时候,在构建DownloadInfo 对象的时候,至少初始化下载地址、存储文件名。例如以下代码:

String url = "http://f1.market.xiaomi.com/download/AppStore/03f82a470d7ac44300d8700880584fe856387aac6/cn.wsy.travel.apk";
            DownloadInfo info = new DownloadInfo();
            info.setUrl(url);
            info.setFileName("IRecord_" + i + ".apk");
3.1基本下载(添加任务Task与启动下载)
    /**
     * 下载入口
     *
     * @param downloadInfo
     * @param iProgress
     * @return 拥有Task的下载实体
     */
   LeopardHttp.DWONLOAD(info, new IProgress() {
            @Override
            public void onProgress(long progress, long total, boolean done) {
                // progress 返回当前上传的进度 ,total 返回当前上传的总文件大小
                //按钮更新
                if (info.getState() == DownLoadManager.STATE_WAITING) {
                    holder.downBtn.setText("下载");
                }

                if (info.getState() == DownLoadManager.STATE_PAUSE) {
                    holder.downBtn.setText("继续");
                }

                if (info.getState() == DownLoadManager.STATE_DOWNLOADING) {
                    holder.downBtn.setText("暂停");
                }
                if (done) {
                    //下载完成...
                }
            }
        });
3.2 DownLoadTask 单任务下载管理
3.2.1 开始下载
// 传入true表示从头开始下载
downloadInfo.getDownLoadTask().downLoad(true);
3.2.2 暂停下载
downloadInfo.getDownLoadTask().pause();
3.2.3 恢复下载
downloadInfo.getDownLoadTask().resume();
3.2.4 停止下载
downloadInfo.getDownLoadTask().stop();
3.2.5 重新下载
downloadInfo.getDownLoadTask().restart();
3.3 DownLoadManager 多任务下载管理
3.3.1 删除所有下载任务
DownLoadManager.getManager().removeAllTask();
3.3.2 暂停所有任务
DownLoadManager.getManager().pauseAllTask();
3.3.3 停止所有任务
DownLoadManager.getManager().stopAllTask();
3.3.4 开始所有任务
DownLoadManager.getManager().startAllTask();

Leopard,使用方法(手动构建)

前面封装的单例模式LeopardHttp,是为了开发者可以更加方便调用,自动化构建好了LeopardClient对象并且配置了相对应需要的属性。不过还是那句话,为了能够让读者可以更加灵活配置LeopardClient,所以建议还是用构建者模式构建,可以根据需求再自定义拦截器或者自定义Factroy。

现在举例子手动构建的部分代码,目前支持的功能有:

1.基本请求

1.1 POST与 GET 键值对请求
 LeopardClient.Builder()
                .addRequestComFactory(new RequestComFactory(respondResult))
                .addGsonConverterFactory(GsonConverterFactory.create())
                .addRxJavaCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(ADDRESS)
                .build()
                .GET(context, enetity, httpRespondResult);//get请求
             // .POST(context, enetity, httpRespondResult);//post请求
1.2 POST与 GET 自定义Json请求
 LeopardClient.Builder()
                .addRequestComFactory(new RequestComFactory(respondResult))
                .addGsonConverterFactory(GsonConverterFactory.create())
                .addRequestJsonFactory(RequestJsonFactory.create())
                .addRxJavaCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(ADDRESS)
                .build()
                .GET(context, enetity, httpRespondResult);//get请求
             // .POST(context, enetity, httpRespondResult);//post请求

2.下载管理

LeopardClient.Builder()
                .addRxJavaCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addDownLoadFileFactory(DownLoadFileFactory.create(this.fileRespondResult, this.downloadInfo))
                .build()
                .downLoadFile(this.downloadInfo, callBack.fileRespondResult, downLoadTask);

3.上传管理

 LeopardClient.Builder()
                .addRequestComFactory(new RequestComFactory(respondResult))
                .addGsonConverterFactory(GsonConverterFactory.create())
                .addRxJavaCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(ADDRESS)
                .addUploadFileFactory(UploadFileFactory.create())
                .build()
                .upLoadFiles(uploadEnetity,respondResult)

目前扩展性,允许开发者进行继承性扩展的有:
1.自定义接入接口,允许开发者根据需求接入新的接口。
继承BaseServerApi接口即可
2.自定义扩展Builder,允许开发者自定义添加拦截器与Factory
继承LeopardClient.Builder与LeopardClient即可
3.允许下载管理任务功能扩展
允许开发者可以扩展下载管理任务功能,底层基本的开始、暂停等方法不允许修改。开发者可以额外扩展其他自定义功能。

版本迭代中...

最后希望

后期我会针对基本请求、下载管理、上传管理做深入分析。

希望有读者阅读的时候能够看到笔者这句话:
“写框架的原因不在于有多少人能使用,希望是有更多人能够参与进来,思考封装抽象思维,提高代码抽象编写能力。”

源码地址:

Android 基于Retrofit+RxJava 封装 Leopard 网络框架-完整浅析
傻小孩b mark共勉,写给在成长路上奋斗的你
喜欢就为我点下喜欢、给我个github的star吧~ 感谢各位读者阅读。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,598评论 25 707
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,430评论 6 342
  • [姑娘,你讲话这么不负责任是要被踢的!] 写作是件异常孤独的事儿。写作的人都是异常矛盾的人。写作的时候我们想要绝对...
    古月叨叨阅读 522评论 7 2
  • HTML辅助器方法:负责渲染控件。将模型数据转化为html信息。 Get请求:点击A页面的一个链接,就会触发一个由...
    余生筑阅读 268评论 0 0