前言
由于有使用的朋友提到 XSnow
框架信息量有点大,希望能有篇文章详细介绍框架中每个模块的细节,所以本文会围绕该框架将每一个模块的相关构思和重要技术点做一个详细的剖析,让使用该框架的朋友对 XSnow
有一个清晰的认识。
准备
由于 XSnow
框架是基于 RxJava2
和 Retrofit2
打造的,其中也依赖了如网络基础库 OkHttp
、图片加载库 Glide
、数据库基础库 GreenDao
,所以使用该项目的朋友需要对这几个基础框架有基本的认识,下面将对这几个框架做一个简单的说明。
RxJava
熟悉的朋友可能知道该框架是属于 ReactiveX
编程库的一员,这是一种新的编程思想,一般都把它叫做响应式编程思想,对于使用该思想的优势在这里就不赘述了,感兴趣的朋友可以看看这篇文章 ReactiveX 文档中文翻译。
RxJava
主要的模式就是观察者模式,何为观察者模式,可以简单的理解为:两个对象观察者与被观察者,其中观察者与被观察者建立了订阅关系,如果被观察者发生了变化那么需要及时通知观察者,观察者收到通知后做出相应的处理。描述可能会有点绕,举个通俗易懂的例子吧,比如说:顾客去买蛋糕这样一个场景(把顾客和蛋糕店店员分别看做观察者与被观察者),由于蛋糕是现做需要一定时间才能完成,顾客在购买时一般会先付钱给店员拿到一个付款凭证后离开去忙其他事,这样他们就建立了购买关系(建立订阅关系),店员在做完蛋糕后(被观察者发生变化),店员就会通知顾客来拿(消息通知到观察者),顾客在收到店员通知后前来拿蛋糕(观察者收到通知后做出相应的处理)。到这里,一个完整的观察者模式场景就讲解完毕,希望能帮助朋友们加深对观察者模式的理解。
下面继续讲解对 RxJava
的理解,先想象一个这样的场景,有一台万能机器,它可以生产任何东西,它有一个输入端、一个输出端,中间有很多核心组件,比如说转换器、转移器等,它们对外部都是隐藏的,但是外部有一个控制终端,可以输入任何想要的规则,机器就会根据规则,将输入端的东西通过终端制定的规则输送到输出端。RxJava
就是这样一台万能机器,它拥有数据输入端、数据输出端,中间也有很多可以操控数据的规则,比如说各种操作符、线程转换,它们都有一个目的,就是如何让数据源通过一定规则进行输出。
以上的讲解只是为了让你对 RxJava
有一个清晰的认识,不牵扯具体的技术细节,如果想详细了解的可以去看看给初学者的 RxJava2.0 教程,这是一个系列,看完这些文章,基本就可以使用 RxJava
做一些基础的功能了,这个系列是针对 RxJava2
的,如果想了解 RxJava
第一版,想看看两个版本到底发生了什么改变,那么可以看看给 Android 开发者的 RxJava 详解以及 RxJava2 vs RxJava1.
Retrofit
Retrofit
简单来说就是一个基于 OkHttp
的 RESTful 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
对象转换成其他对象,如转换成支持RxJava
的Observable
对象。
Retrofit
进行网络请求的过程:
- 通过
Retrofit
对象和Method
对象获取callAdapter
、responseType
以及responseConverter
三个对象; - 通过解析
Method
中的注解以及进行一系列的检查得到中心管理对象ServiceMethod
; - 通过
ServiceMethod
对象获取实际执行的Call
对象执行Http
请求。
以上讲解只是对 Retrofit
的核心功能做了相关的解释,如果想更详细的了解 Retrofit
可以看看拆轮子系列:拆 Retrofit
OkHttp
OkHttp
是一个高效的 HTTP
库,它的总体设计图如下(图片来源:泡在网上的日子):
OkHttp
的请求由 OkHttpClient
统一管理,采用的是门面模式,OkHttpClient
拥有子模块的所有配置和参数,并将请求分发到相应的子系统。它由以下几个核心子系统组成:路由、连接协议、拦截器、代理、安全性认证、连接池以及网络适配。主要是通过 Dispatcher
不断从 RequestQueue
中取出请求(Call
),根据是否已缓存调用 Cache
或 Network
这两类数据来获取某个接口,再从内存缓存或是服务器取得请求的数据。该引擎有同步和异步请求,同步请求通过Call.execute()
直接返回当前的 Response
,而异步请求会把当前的请求(AsyncCall
)Call.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
该包提供的是一些核心功能类,目前包含缓存处理类ApiCache
、Cookie
管理类ApiCookie
以及网络请求订阅管理类ApiManager
。缓存采用磁盘缓存方式,支持定制各种缓存策略,策略将在strategy
包下进行讲解。Cookie
采用SharedPreferences
存储,存储对象以十六进制进行保存。 -
exception
该包提供网络请求相关异常类,目前提供了请求异常统一处理类ApiException
,可用来判定请求失败的原因。 -
func
该包提供一些数据转换类,目前提供了由ResponseBody
对象转T
的ApiFunc<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
字符串、上传表单等方式,而如果是上传文件则提供了添加文件、添加字节数组和添加流的方式。
由于带缓存请求和不带缓存请求返回的结果不一样,所以需要分开处理,故有cacheExecute
和execute
的区分。 -
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
的特殊性,该方法的实现类不能在框架中搭建,所有数据库操作都可以参考 Demo
中 DbHeDlper
类实现自己的数据库操作管理类,不同的 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
来进行权限申请回调处理,提供了一系列权限申请的方法,有多个权限单独处理回调和统一处理回调的方式,分别是requestEach
和request
方式。 - 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
并分享给身边的朋友,让更多的朋友参与进来,谢谢大家!
网友评论
HttpLogInterceptor.log(HttpLogInterceptor.java:41): <-- 200 OK http://app.******.in//index.php/api/
HttpAPI$1.onFail(HttpAPI.java:132): request errorCode:1000,errorMsg:UNKNOWN