打造Android轻量级框架XSnow的后继之路

前言

由于有使用的朋友提到 XSnow 框架信息量有点大,希望能有篇文章详细介绍框架中每个模块的细节,所以本文会围绕该框架将每一个模块的相关构思和重要技术点做一个详细的剖析,让使用该框架的朋友对 XSnow 有一个清晰的认识。

准备

由于 XSnow 框架是基于 RxJava2Retrofit2 打造的,其中也依赖了如网络基础库 OkHttp 、图片加载库 Glide 、数据库基础库 GreenDao ,所以使用该项目的朋友需要对这几个基础框架有基本的认识,下面将对这几个框架做一个简单的说明。

RxJava

熟悉的朋友可能知道该框架是属于 ReactiveX 编程库的一员,这是一种新的编程思想,一般都把它叫做响应式编程思想,对于使用该思想的优势在这里就不赘述了,感兴趣的朋友可以看看这篇文章 ReactiveX 文档中文翻译

RxJava 主要的模式就是观察者模式,何为观察者模式,可以简单的理解为:两个对象观察者与被观察者,其中观察者与被观察者建立了订阅关系,如果被观察者发生了变化那么需要及时通知观察者,观察者收到通知后做出相应的处理。描述可能会有点绕,举个通俗易懂的例子吧,比如说:顾客去买蛋糕这样一个场景(把顾客和蛋糕店店员分别看做观察者与被观察者),由于蛋糕是现做需要一定时间才能完成,顾客在购买时一般会先付钱给店员拿到一个付款凭证后离开去忙其他事,这样他们就建立了购买关系(建立订阅关系),店员在做完蛋糕后(被观察者发生变化),店员就会通知顾客来拿(消息通知到观察者),顾客在收到店员通知后前来拿蛋糕(观察者收到通知后做出相应的处理)。到这里,一个完整的观察者模式场景就讲解完毕,希望能帮助朋友们加深对观察者模式的理解。

下面继续讲解对 RxJava 的理解,先想象一个这样的场景,有一台万能机器,它可以生产任何东西,它有一个输入端、一个输出端,中间有很多核心组件,比如说转换器、转移器等,它们对外部都是隐藏的,但是外部有一个控制终端,可以输入任何想要的规则,机器就会根据规则,将输入端的东西通过终端制定的规则输送到输出端。RxJava 就是这样一台万能机器,它拥有数据输入端、数据输出端,中间也有很多可以操控数据的规则,比如说各种操作符、线程转换,它们都有一个目的,就是如何让数据源通过一定规则进行输出。

以上的讲解只是为了让你对 RxJava 有一个清晰的认识,不牵扯具体的技术细节,如果想详细了解的可以去看看给初学者的 RxJava2.0 教程,这是一个系列,看完这些文章,基本就可以使用 RxJava 做一些基础的功能了,这个系列是针对 RxJava2 的,如果想了解 RxJava 第一版,想看看两个版本到底发生了什么改变,那么可以看看给 Android 开发者的 RxJava 详解以及 RxJava2 vs RxJava1.

Retrofit

Retrofit 简单来说就是一个基于 OkHttpRESTful API 请求工具。RESTful 是一种架构风格,它的特点是资源、统一接口、URI 和无状态,如果想更详细的了解 RESTful 可以看看RESTful 架构风格概述

Retrofit 在使用时其实就充当了一个适配器(Adapter)的角色,主要是将一个 Java 接口翻译成一个 HTTP 请求对象,然后用 OkHttp 去发送这个请求。其中核心思想就是动态代理机制,什么是动态代理,就是当你要调用某个 Class 的方法前或后,插入你想要执行的代码。通俗来讲,就是你要执行某个操作的前后需要增加一些操作,比如查看用户个人信息前需要判断用户是否登录,用户访问数据库后想清除用户的访问记录等操作。

Retrofit 的设计非常插件化且轻量级,高内聚且低耦合。
Retrofit 主要定义了 4 个接口:

  • Callback<T>:请求数据的返回;
  • Converter<F, T>:对返回数据进行解析,一般用 GSON
  • Call<T>:发送请求,Retrofit 默认的实现是 OkHttpCall<T>,也可以依需自定义 Call<T>
  • CallAdapter<T>:将 Call 对象转换成其他对象,如转换成支持 RxJavaObservable 对象。

Retrofit 进行网络请求的过程:

  • 通过 Retrofit 对象和 Method 对象获取 callAdapterresponseType 以及 responseConverter 三个对象;
  • 通过解析 Method 中的注解以及进行一系列的检查得到中心管理对象 ServiceMethod
  • 通过 ServiceMethod 对象获取实际执行的 Call 对象执行 Http 请求。

以上讲解只是对 Retrofit 的核心功能做了相关的解释,如果想更详细的了解 Retrofit 可以看看拆轮子系列:拆 Retrofit

OkHttp

OkHttp 是一个高效的 HTTP 库,它的总体设计图如下(图片来源:泡在网上的日子):

OkHttp总体设计图

OkHttp 的请求由 OkHttpClient 统一管理,采用的是门面模式,OkHttpClient 拥有子模块的所有配置和参数,并将请求分发到相应的子系统。它由以下几个核心子系统组成:路由、连接协议、拦截器、代理、安全性认证、连接池以及网络适配。主要是通过 Dispatcher 不断从 RequestQueue 中取出请求(Call),根据是否已缓存调用 CacheNetwork 这两类数据来获取某个接口,再从内存缓存或是服务器取得请求的数据。该引擎有同步和异步请求,同步请求通过Call.execute()直接返回当前的 Response,而异步请求会把当前的请求(AsyncCallCall.enqueue 添加到请求队列中,并通过回调(Callback)的方式来获取最后结果。

OkHttp 中的 Interceptor(拦截器)方式对于整体的设计提供了很大的帮助,它采用的是责任链模式,它不只是负责拦截请求进行一些额外的处理(例如增加请求头),实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor.Chain,环环相扣,最终圆满完成一次网络请求。

以上讲解只是对 OkHttp 的流程做了相关的解释,如果想更详细的了解 OkHttp 可以看看拆轮子系列:拆 OkHttp

Glide

Glide 是为图片加载而生,一行代码Glide.with(this).load(url).into(imageView);就搞定图片的加载。使用非常简单,如果想详细了解的,这里推荐郭霖的 Glide 源码解析,Android 图片加载框架最全解析,这是一个系列,看完后对于 Glide 基本就能知道怎么用和为啥要这样用了。

GreenDao

GreenDao 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。如果想更详细的了解 GreenDao 可以看看Android 数据存储之 GreenDao 3.0 详解

注:

  • 以上对于基础库的讲解只是做了简单的介绍,让你对 XSnow 框架利用的相关技术有个直观的感受,如果想详细了解的,上面在每个基础库后面都备注了个人认为比较好的文章,可以当做学习该框架的参考。
  • 以下介绍都以包名作为每个模块的标题,这样也表明模块之间充分解耦,也方便读者对照代码进行理解,分析起来逻辑更清晰。

Http(网络模块,包含网络请求,上传下载)

该模块是 XSnow 框架的核心功能,其核心思想就是将请求分离和基于动态配置,采用门面模式,上层不用关系具体实现细节,只需要简单配置相关的请求信息就可以达到完整的网络请求功能。该模块相对于其他模块代码量比较大,下面将会对该模块下的每一个包进行详细拆分讲解,现在我们首先来看看 ViseHttp 类,该类相当于该模块的门面类,也是网络请求的唯一入口类,所有的请求都是由该类构建,如:

  • BASE:传入自定义请求对象,方便外部根据自己的需求自定义请求;
  • GET:获取 GET 方式请求对象;
  • POST:获取 POST 方式请求对象;
  • HEAD:获取 HEAD 方式请求对象;
  • PUT:获取 PUT 方式请求对象;
  • PATCH:获取 PATCH 方式请求对象;
  • OPTIONS:获取 OPTIONS 方式请求对象;
  • DELETE:获取 DELETE 方式请求对象;
  • UPLOAD:获取上传文件请求对象,支持传入上传回调;
  • DOWNLOAD:获取下载文件请求对象。

该类还提供了根据 tag 中断单个网络请求以及中断所有网络请求功能,也提供了根据 key 删除缓存和清除所有网络缓存功能。必须注意的是,在应用初始化时必须调用该类的初始化方法

ViseHttp.init(this);

以及相关的网络配置

ViseHttp.CONFIG()
        //配置请求主机地址
        .baseUrl("http://10.8.4.39/")
        //配置全局请求头
        .globalHeaders(new HashMap<String, String>())
        //配置全局请求参数
        .globalParams(new HashMap<String, String>())
        //配置读取超时时间,单位秒
        .readTimeout(30)
        //配置写入超时时间,单位秒
        .writeTimeout(30)
        //配置连接超时时间,单位秒
        .connectTimeout(30)
        //配置请求失败重试次数
        .retryCount(3)
        //配置请求失败重试间隔时间,单位毫秒
        .retryDelayMillis(1000)
        ......;

这样才能在应用中调用相关的网络请求功能。如果没有初始化,在调用网络请求时该模块会抛出如下异常信息:

Please call ViseHttp.init(this) in Application to initialize!

切记!

下面来分别讲解该模块下每个包的功能:

  • api
    该包提供的是请求的 API,目前只有一个类 ApiService,主要提供的是 Retrofit 进行网络请求的请求方法。

  • body
    该包提供的是相关的请求和响应 body,目前只有一个上传进度展示的请求实体类 UploadProgressRequestBody,通过传入 UCallback 来处理上传文件的进度回调。

  • callback
    该包提供的是相关的回调类,目前包含上传回调 UCallback 和请求 API 回调 ACallback

  • config
    该包提供的是配置相关类,目前只有请求全局配置类 HttpGlobalConfig,该类提供了很丰富的配置方法,提供该类的目的是想将配置与请求分离。

  • core
    该包提供的是一些核心功能类,目前包含缓存处理类 ApiCacheCookie 管理类 ApiCookie 以及网络请求订阅管理类 ApiManager。缓存采用磁盘缓存方式,支持定制各种缓存策略,策略将在 strategy 包下进行讲解。Cookie 采用 SharedPreferences 存储,存储对象以十六进制进行保存。

  • exception
    该包提供网络请求相关异常类,目前提供了请求异常统一处理类 ApiException,可用来判定请求失败的原因。

  • func
    该包提供一些数据转换类,目前提供了由 ResponseBody 对象转 TApiFunc<T> 类以及由 Observable<? extends Throwable>Observable<?>ApiRetryFunc 类。ApiRetryFunc 类主要用来处理网络超时重试机制,可传入重试次数和重试间隔时间,这些都可以在配置类中进行配置。

  • interceptor
    该包提供的是一系列的拦截器类,这个包也算是该模块的核心包,基本大部分功能都可以采用拦截器的方式来提供,目前该包下面包含如下拦截器:
    1、GzipRequestInterceptor:包含Gzip压缩的请求拦截;
    2、HeadersInterceptor:请求头拦截;
    3、HttpLogInterceptor:Http日志打印拦截;
    4、NoCacheInterceptor:无缓存拦截;
    5、OfflineCacheInterceptor:离线缓存拦截;
    6、OnlineCacheInterceptor:在线缓存拦截;
    7、UploadProgressInterceptor:上传文件进度展示拦截。

  • mode
    该包提供的是相关的实体类。

  • request
    该包提供的是所有请求相关的类,所有的网络请求都需要创建一个请求对象,该包提供了一个基础请求类 BaseRequest<R extends BaseRequest>,该类需将 R 写成实际请求类,这样才能获取对应请求类的对象来进行相关的请求信息配置,如果请求配置与全局配置冲突,那么优先请求配置,意思就是局部请求配置会替换掉相同的全局配置。
    请求类中提供了一系列请求头配置、请求参数配置等信息,如果是 POST 请求还提供了上传 JSON 字符串、上传表单等方式,而如果是上传文件则提供了添加文件、添加字节数组和添加流的方式。
    由于带缓存请求和不带缓存请求返回的结果不一样,所以需要分开处理,故有 cacheExecuteexecute 的区分。

  • strategy
    该包提供的是缓存相关的策略,包含如下几种策略:
    1、CacheAndRemoteStrategy:先加载缓存数据后加载网络数据;
    2、FirstCacheStrategy:优先加载缓存数据;
    3、FirstRemoteStrategy:优先加载网络数据;
    4、OnlyCacheStrategy:只加载缓存数据;
    5、OnlyRemoteStrategy:只加载网络数据。
    缓存策略采用面向接口编程原则,定义了一个缓存策略接口 ICacheStrategy<T>,并将具体的策略实现交由各个子类。

  • subscriber
    该包提供的是相关的订阅者,目前包含 API 请求的统一订阅者 ApiSubscriber<T>、包含回调的订阅者 ApiCallbackSubscriber<T> 以及包含下载回调的订阅者 DownCallbackSubscriber<T>

Cache(缓存)

该模块提供了几种缓存方式,分别是内存缓存、磁盘缓存以及 SharedPreferences 存储。该模块主要思想是面向接口编程,提供了 ICache 接口,主要包含添加缓存、获取缓存、删除缓存、清除所有缓存以及判断是否包含该缓存这几个能力。下面将对每个缓存方式做详细的解释说明:

  • 内存缓存:内存缓存采用单例模式管理缓存对象,缓存对象为 LruCache,缓存算法采用 Lru 算法(Least Recently Used 近期最少使用算法),缓存大小为最大内存的八分之一。
  • 磁盘缓存:磁盘缓存 KEY 采用 MD5 加密,可定制缓存时长,没有定制则默认永久存储,缓存对象为 DiskLruCache,缓存算法也是 Lru 算法,缓存位置和缓存大小都可以定制,如果没有定制就会使用默认值,默认的缓存位置为该应用缓存目录下的 disk_cache 目录,优先存储到 SD 卡中,默认的缓存大小为 20M
  • SharedPreferences 存储:SharedPreferences 存储对缓存对象进行 Base64 加密存储,可定制缓存文件名,如果没有定制则使用默认文件名 sp_cache

Event(事件总线)

该模块使用 Rx 思想实现了 RxBus 功能,其 Bus 的设计思想与 EventBus 类似。其主要由以下几个核心类组成:

  • EventBase :事件处理基类,包含粘性事件 Map 以及普通事件 Subject,也定义了获取 Flowable 以及删除粘性事件和删除粘性事件 Map 的方法。
  • EventComposite :事件复合类,就是将需要接收事件的类中所有事件组合到一起进行处理,并提供了一个发送粘性事件的方法。
  • EventSubscriber :事件订阅者,提供了订阅事件和分发事件的方法。
  • EventFind :根据注解查找事件接收方法,由此得到 EventSubscriber,最后组合到一起得到 EventComposite
  • ThreadMode :线程模型,包含如主线程、IO线程等,通过 getScheduler(ThreadMode thread) 来获取线程调度者。

事件接收采用注解方式类进行管理,事件订阅后依据注解来查找对应的事件接收地。

该模块为了能将事件总线统一,定义了 IBus 接口,提供了如下四个方法:

void register(Object object);

void unregister(Object object);

void post(IEvent event);

void postSticky(IEvent event);

也提供了 IEvent 接口,所有事件都实现该接口,这样就可以将具体的事件实现类抽离,其实也是面向接口编程。

该模块也提供了插件化思想,上层可以将如 EventBus 的实现类注入该模块,那么事件的处理就会采用 EventBus 实现的策略,但这里有一个问题,由于事件接收采用的是注解方式,而 EventBus 中的注解是不同的,所以还是需要把注解事件进行统一替换,耦合性太高,目前没有发现更好的方式,如果哪位朋友有好的去耦合方式,欢迎留言交流!

Loader(图片加载)

该模块针对图片加载做了二次封装,面向接口编程,每个实现就是一种图片加载策略,默认采用 Glide 图片加载框架,上层也可以依需自定义实现接口 ILoader,比如 Demo 中提供的 Fresco 图片框架的实现类 FrescoLoader,其主要思想就是插件化,外部可注入任何加载策略,这样可达到高内聚低耦合。

接口中提供了如下四种加载图片的方式:

  • 加载网络图片
void loadNet(ImageView target, String url, Options options);
  • 加载资源图片
void loadResource(ImageView target, int resId, Options options);
  • 加载Assets中的图片
void loadAssets(ImageView target, String assetName, Options options);
  • 加载本地图片
void loadFile(ImageView target, File file, Options options);

默认的 Glide 框架采用 provided 方式依赖,这样就只是编译时依赖,运行时不依赖,上层如果确定使用 GlideLoader加载策略,那么还需要自己使用 compile 进行依赖,这样运行时才不会报错,GlideLoader 在初始化时也增加了如下验证机制

try {
    Class.forName("com.bumptech.glide.Glide");
} catch (ClassNotFoundException e) {
    throw new IllegalStateException("Must be dependencies Glide!");
}

如果没有依赖 Glide 库则会抛出异常。

Database(数据库)

该模块将 GreenDao 作为底层数据库,定义了数据库的操作接口 IDatabase<M, K>,统一由 DBManager<M, K> 抽象类管理,由于每个实体类对应的 Dao 不一样,所以定义了抽象方法 getAbstractDao()。由于 GreenDao 的特殊性,该方法的实现类不能在框架中搭建,所有数据库操作都可以参考 DemoDbHeDlper 类实现自己的数据库操作管理类,不同的 Dao 实现对应的 getAbstractDao() 方法就行。

Permission(权限管理)

该模块利用 Rx 思想统一管理权限的申请,一行代码搞定权限的申请问题。

PermissionManager.instance().with(this).request(onPermissionCallback, Manifest.permission.CALL_PHONE);

该模块很简洁,就几个类,下面分别对它们进行介绍:

  • OnPermissionCallback:权限申请回调接口,在接口实现类中调用具体的业务逻辑。
  • Permission:权限实体类,包含权限名称、是否授予权限以及是否显示权限申请理由变量。
  • PermissionManager:权限管理类,也是权限申请的唯一入口,采用单例模式,需要通过 with 将当前的 Activity 对象传进去,调用 request 方法就可以进行权限申请,需要传入回调和权限列表,权限列表采用可变数组方式传入,使用示例如下:
PermissionManager.instance().with(this).request(new OnPermissionCallback() {
    @Override
    public void onRequestAllow(String permissionName) {
        DialogUtil.showTips(mContext, getString(R.string.permission_control),
                getString(R.string.permission_allow) + "\n" + permissionName);
    }

    @Override
    public void onRequestRefuse(String permissionName) {
        DialogUtil.showTips(mContext, getString(R.string.permission_control),
                getString(R.string.permission_refuse) + "\n" + permissionName);
    }

    @Override
    public void onRequestNoAsk(String permissionName) {
        DialogUtil.showTips(mContext, getString(R.string.permission_control),
                getString(R.string.permission_noAsk) + "\n" + permissionName);
    }
}, Manifest.permission.CALL_PHONE);
  • RxPermissions:权限管理核心类,该类通过传入 Activity 获取一个 Fragment 来进行权限申请回调处理,提供了一系列权限申请的方法,有多个权限单独处理回调和统一处理回调的方式,分别是 requestEachrequest 方式。
  • RxPermissionsFragment:权限申请回调处理 Fragment

UI(UI模块,包含万能适配器、视图切换)

该模块包含万能适配器和试图切换功能。适配器部分采用 ViewHolder 来管理数据的装载和展示,将数据与展示分离,提供了 DataHelper 接口来装载数据,ViewHelper 接口来处理 UI 的展示。其中的 HelperAdapter 提供了适配器的常用方法,基本能满足适配器的常用需求。
视图切换部分由 StatusLayoutManager 统一管理,通过传入相关配置进行视图展示处理。内部提供了 OnRetryListener 重试监听和 OnStatusViewListener 试图切换监听,并定义了一个自定义视图 StatusLayout 用来展示以下五种视图:

  • CONTENT:内容视图;
  • LOADING:加载视图;
  • EMPTY:空视图;
  • NETWORK_ERROR:网络错误视图;
  • OTHER_ERROR:其他错误视图。

最后

到此,XSnow 框架的所有模块就介绍完毕了,不知各位朋友是否对该框架有了更深入的了解。

以上描述有些部分可能讲解比较简单,这是因为有些模块本身不是很复杂,所以就没有做过多的讲解,如果想更清晰的了解该框架,最好的办法是直接 down 下源码观察,如果在看源码过程中有哪里不理解或觉得实现有问题而你有更好的方案都可以留言交流!

源码地址:https://github.com/xiaoyaoyou1212/XSnow,源码地址中提供了详细的使用文档,里面有版本介绍以及QQ群等信息,如果喜欢该框架不妨点点 star 并分享给身边的朋友,让更多的朋友参与进来,谢谢大家!

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

推荐阅读更多精彩内容