本文首发于微信公众号——世界上有意思的事,搬运转载请注明出处,否则将追究版权责任。微信号:a1018998632,交流qq群:859640274
4.外观模式实现数据处理引擎框架暴露出来的api
我们在使用各种开源框架的时候,大多数时候都不会对框架内部的实现进行细究,所以一个好的框架需要一个简单的入口来对整个框架进行使用,我接下来就是要讲解本框架的入口类。
public class DataEngine {
public static String TAG="DataEngine";
public static Context nowContext;
public static ObservableBoolean nowIsShowProgressBar;
public static final List<Interceptor> mInterceptors=new ArrayList<>();
public static final DataEngine sInstance;
static {
synchronized (DataEngine.class){
sInstance=new DataEngine();
}
}
private final Service memoryCacheService=new MemoryCacheServiceImpl(true);
private final Service mLocalDataService =new LocalDataServiceImpl(true) ;
private final Service networkService=new NetworkServiceImpl(true);
private DataEngine() {
mInterceptors.add(new MemoryCacheInterceptor(memoryCacheService));
mInterceptors.add(new NewThreadInterceptor());
mInterceptors.add(new LocalDataInterceptor(mLocalDataService));
mInterceptors.add(new NetworkInterceptor(networkService));
}
/**
* 所有的数据请求,都是从这个方法开始。本方法在{@link LocalDataEngine}和{@link NetworkDataEngine}中使用。
* @param request 传入的数据请求
* @return
*/
public Observable<Response> request(final Request request){
final RealInterceptorChain realInterceptorChain=RealInterceptorChain.obtain(request);
Object response;
try {
response = realInterceptorChain.proceed();
} catch (Exception e) {
e.printStackTrace();
// 此时抛出的异常,那么就是请求失败了,应该是内存缓存服务有问题,由于发生了未知的错误所以不应该继续运行这个请求了。
FLog.e(TAG,"应该是内存缓存服务有问题",e);
return Observable.just(Response.getFailedResponse(e))
.compose(((RxAppCompatActivity)nowContext).<Response>bindToLifecycle())
.filter(new Func1<Response, Boolean>() {
@Override
public Boolean call(Response appConfigObjectObjectResponse) {
//此时拦截链已经调用完毕,所以可以将RealInterceptorChain回收
realInterceptorChain.recycle();
Toast.makeText(nowContext, "操作失败,发生未知错误", Toast.LENGTH_SHORT).show();
if (nowIsShowProgressBar!=null)nowIsShowProgressBar.set(false);
return appConfigObjectObjectResponse.isSuccess();
}
});
}
Observable<?> finalResponseObservable;
if (response instanceof Observable){
// 这里表示需要从其他线程获取数据数据,一般是本地储存或者网络上的数据
finalResponseObservable = (Observable<?>) response;
} else {
// 这里表示直接从本线程获取数据,一般是内存中的数据
finalResponseObservable = Observable.just(response);
}
return finalResponseObservable
.compose(((RxAppCompatActivity)nowContext).bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.filter(new Func1<Object, Boolean>() {
@Override
public Boolean call(Object o) {
//此时拦截链已经调用完毕,所以可以将RealInterceptorChain回收
realInterceptorChain.recycle();
if (o instanceof Response){
// 这里表示在 本地储存或者网络请求或者想内存缓存存数据的时候 发生了问题,所以直接返回了请求错误的结果,
// 由于发生了未知的错误所以不应该继续运行这个请求了。
Toast.makeText(nowContext, "操作失败,发生未知错误", Toast.LENGTH_SHORT).show();
if (nowIsShowProgressBar!=null)nowIsShowProgressBar.set(false);
return ((Response) o).isSuccess();
}
return true;
}
}).map(new Func1<Object, Response>() {
@Override
public Response call(Object o) {
FLog.v(TAG, request.getRequestFlag()+"请求成功");
FLog.v(" "," ");
return Response.getCommonResponseOne(o);
}
});
}
}
由于本框架适配了Rxjava,所以需要大家对Rxjava比较熟悉
- 1.前三个静态变量不需要细究,List<Interceptor> mInterceptors是静态final变量,在DataEngine单例创建的时候会把拦截器放入其中。DataEngine sInstance是静态final单例,使用同步的方式在类被加载的时候初始化,由于DataEngine的构造器是private的,所以这个类不能再创建其他对象了。
- 2.我们由代码可以看出,所有的Service和List<Interceptor> mInterceptors都是被多线程共用的,所以此时就会出现线程安全问题。由于mInterceptors中的拦截器都是不可变类,所以这个不需要担心。但是所有Service的实现就需要对某些操作进行同步了,Service的实现和同步会在后面讲到。
- 3.再下来我们就可以看见,惟一一个public的方法request(final Request request):
- 1.方法首先会去RealInterceptorChain的对象池中获取一个对象,然后调用realInterceptorChain#proceed()获取结果。注意这里的调用都是在主线程进行的,如果大家对前面的MemoryCacheInterceptor还有印象的话,就会知道这里要么返回数据类Object,要么返回一个Observable。数据类Object是从内存中获取的不会在其他线程,而Observable并没有调用subscribe(),所以其只是对一个请求的封装,真正的请求还没被调用。
- 2.在获取到response之后,要判断这个response到底是数据类Object还是Observable,如果是数据类Object将其再用Observable封装。
- 3.再进行了上面的操作之后,返回的请求就都变成Observable了,此时我们先将线程切换为主线程,然后用一个filter过滤掉出现异常的请求,最后将成功获取的数据映射成一个Response。注意:此时返回的是Observable,真正的调用会由客户端触发
5.服务的实现
前面我们在讲拦截链的时候,使用的都是Service接口,那么每个拦截器的具体实现是怎么样的呢?接下来我就来讲解一下我们app登录时,每个服务的实现。
@ThreadSafe
public class MemoryCacheServiceImpl implements Service{
private final MemoryCache<CacheKey,Object> mainMemoryCache=new CountingLruMapMemoryCacheImpl<CacheKey, Object>(200,new DefaultCacheListener());
private final MemoryCache minorMemoryCache=null;
private boolean enable;
public MemoryCacheServiceImpl(boolean enable) {
this.enable = enable;
}
@Override
public Object in(Request request, Object in) throws Exception {
return mainMemoryCache.cache(request.getCacheKey(),in==null?request.getParam():in);
}
@Override
public Object out(Request request) throws Exception {
return mainMemoryCache.get(request.getCacheKey());
}
@Override
public boolean isEnabled() {
return enable;
}
@Override
public void setEnable(boolean enable) {
this.enable=enable;
}
}
-
1.MemoryCacheServiceImpl是内存服务的实现类。
- MemoryCache<CacheKey,Object> mainMemoryCache:是一个接口,由于我们可以使用第三方类库实现缓存,也可以写一个自制缓存,如果在内存缓存服务中使用实际的类,那么在以后要进行更换内存缓存实现的时候就需要改动很多地方。而MemoryCache<CacheKey,Object>就是我定义了一组内存缓存组件的标准,只要实现了这个标准的内存缓存组件都能用于这个内存缓存服务。
- 2.MemoryCache minorMemoryCache:是副缓存,目前没有赋值。
- 3.接下来的四个方法就是对Service的实现,可以看见对于in()代码中就直接调用的是缓存组件的cache()来对,传入的参数或者返回的结果进行缓存。out()中就是直接从缓存组件中根据Request中的CacheKey获取缓存的数据。
代码
public interface ToLocalDataRequest<OUT,IN> { OUT updateAndGetAppConfig(Request request, IN in)throws Exception; OUT getAppConfig(Request request, IN in)throws Exception ; OUT updateAndGetUserConfig(Request request, IN in) throws Exception; OUT getUserConfig(Request request, IN in)throws Exception ; } public interface ToNetworkRequest<OUT,IN> { OUT patrolAccountAction_login(Request request, IN in)throws Exception ; OUT initAppData(Request request, IN in)throws Exception ; }
-
2.在讲本地储存服务和网络服务的时候,先来介绍两个接口。ToLocalDataRequest<OUT,IN>和ToNetworkRequest<OUT,IN>。我们都知道内存缓存中存取数据是比较简单的,但是到了硬盘和服务器上就不一样了。有时我们会用数据库实现硬盘缓存,有时候会用文件系统实现硬盘缓存,在这个时候对于不同种类的请求要进行的操作是不同的,同样对于向服务器的请求也是一样的。所以我们需要对本地存储请求和服务器请求分别创建接口,然后让本地存储服务和网络请求服务去实现他们,最后根据不同的请求来调用不同的被覆盖的方法。
@ThreadSafe
public class LocalDataServiceImpl implements Service,ToLocalDataRequest,ToNetworkRequest{
private static String TAG="LocalDataServiceImpl";
private final DaoMaster daoMaster = new DaoMaster(new DaoMaster.DevOpenHelper(MyApplication.myApplication, "my.db", null).getWritableDb());
private final DiskCache<SimpleCacheKey> sharePreferenceDiskCache =new SharePreferenceDiskCacheImpl<>(MyApplication.myApplication,new DefaultCacheListener());
private boolean enable;
public LocalDataServiceImpl(boolean enable) {
this.enable = enable;
}
@Override
public Object in(Request request,Object in) throws Exception {
return TransformDataMethodDispatch.dispatch(this,this,request,in);
}
@Override
public Object out(Request request) throws Exception {
return TransformDataMethodDispatch.dispatch(this,this,request,null);
}
@Override
public Object patrolAccountAction_login(Request request, Object in) throws IOException {
return null;
}
@Override
public Object initAppData(Request request, Object in) throws IOException {
if (in==null)return null;
FlushData flushData=(FlushData)in;
ArrayList<MissionInfoEntity> mInfoEntityArrayList=flushData.getFlushData().getInfoEntityArrayList();
ArrayList<PolicePeopleEntity> mPeopleArrayList=flushData.getFlushData().getPeopleArrayList();
ArrayList<UnitBaseEntity> mUnitBaseArrayList=flushData.getFlushData().getUnitBaseArrayList();
if (mInfoEntityArrayList!=null&&mInfoEntityArrayList.size()!=0){
for (MissionInfoEntity m:mInfoEntityArrayList){
daoMaster.newSession().getMissionInfoDao().insert(new MissionInfo(m));
}
}
if (mPeopleArrayList!=null&&mPeopleArrayList.size()!=0){
for (PolicePeopleEntity p :mPeopleArrayList){
daoMaster.newSession().getPolicePeopleDao().insert(new PolicePeople(p));
}
}
if (mUnitBaseArrayList!=null&&mUnitBaseArrayList.size()!=0){
for (UnitBaseEntity u :mUnitBaseArrayList){
daoMaster.newSession().getUnitBaseDao().insert(new UnitBase(u));
}
}
request.setCacheToMemory(false);
FLog.v(TAG,"向数据库初始化数据成功");
return in;
}
@Override
public Object updateAndGetAppConfig(Request request, Object in) throws Exception {
if (request.getParam()==null){
FLog.e(TAG,"appConfig插入时为null");
throw new NullPointerException();
}
AppConfig appConfig=(AppConfig)request.getParam();
List<DBMap> dbMapList=appConfig.transformToDBMap();
daoMaster.newSession().getDBMapDao().insertOrReplaceInTx(dbMapList);
FLog.v(TAG,"向数据库更新appConfig成功");
return appConfig;
}
@Override
public Object getAppConfig(Request request, Object in){
AppConfig appConfig = null;
List<DBMap> dbMapList=daoMaster.newSession().getDBMapDao().loadAll();
if (dbMapList==null||dbMapList.size()==0)return null;
appConfig=new AppConfig(dbMapList);
FLog.v(TAG,"从数据库获取的appConfig");
return appConfig;
}
@Override
public Object updateAndGetUserConfig(Request request, Object in) throws Exception {
if (request.getParam()==null){
FLog.e(TAG,"userConfig插入时为null");
throw new NullPointerException();
}
UserConfig userConfig=(UserConfig) request.getParam();
sharePreferenceDiskCache.cache((SimpleCacheKey) request.getCacheKey(),JsonUtil.objectToJson(userConfig));
FLog.v(TAG,"向sharePreferenceDiskCache更新userConfig成功");
return userConfig;
}
@Override
public Object getUserConfig(Request request, Object in) throws IOException {
UserConfig userConfig= JsonUtil.jsonToObject((String) sharePreferenceDiskCache.get((SimpleCacheKey) request.getCacheKey()),UserConfig.class);
FLog.v(TAG,"从sharePreferenceDiskCache获取的userConfig");
return userConfig;
}
@Override
public boolean isEnabled() {
return enable;
}
@Override
public void setEnable(boolean enable) {
this.enable=enable;
}
}
- 3.LocalDataServiceImpl是本地存储服务的实现类,可以看见这个类实现了Service,ToLocalDataRequest,ToNetworkRequest这三个接口,Service不必多说。可能有人要问为什么要实现ToNetworkRequest呢?那是因为在一些向服务器的请求之中,可能在本地中已经有缓存了,那么此时并不需要去服务器中取数据。我们在前面的本地储存拦截器中也有介绍到这一点。
- 1.可以看见这个类中有GreenDao(一种数据库的ORM)和DiskCache<SimpleCacheKey> sharePreferenceDiskCache这两个实现,GreenDao内部实现不是由我们实现的所以不需要抽象为接口。而sharePreferenceDiskCache是一个本地储存抽象出来的接口和前面讲的内存缓存接口是一个道理。我目前用的是sharePreference,当然以后有需要还可以换成用文件系统实现,换起来也很方便。
- 2.接下来是实现了Service的in()和out()方法,本来是需要在这两个方法中根据请求的种类使用switch将请求分派到各个方法中的,这里我使用了TransformDataMethodDispatch来分派,内部实现是一样的。
- 3.接下来的几个方法就是对ToLocalDataRequest,ToNetworkRequest这两个接口的实现了,这里需要根据具体的业务逻辑进行考虑。
@ThreadSafe
public class NetworkServiceImpl implements Service,ToNetworkRequest{
private boolean enable;
public NetworkServiceImpl(boolean enable) {
this.enable = enable;
}
@Override
public Object in(Request request, Object in) throws Exception {
return null;
}
@Override
public Object out(Request request) throws Exception {
return TransformDataMethodDispatch.dispatch(this,null,request,null);
}
@Override
public Object patrolAccountAction_login(Request request, Object in) throws IOException {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LoginEntity.Response response=new LoginEntity.Response(new LoginEntity.UserEntity(1,"13030512957","1",(byte) 1,"男"),"杭州",1,4,"");
return response;
}
@Override
public Object initAppData(Request request, Object in) throws IOException {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
public boolean isEnabled() {
return enable;
}
@Override
public void setEnable(boolean enable) {
this.enable = enable;
}
}
- 4.NetworkServiceImpl就是网络请求服务的实现了
- 1.这里我只是模拟了网络请求,大家可以使用任何网络框架比如:Retrofit Okhttp之类的
- 2.注意这里的in()方法是空实现,是不会被调用到的,前面在介绍拦截器的时候已经说过了,不再赘述。
- 3.和本地存储服务一样,out()方法中也使用了TransformDataMethodDispatch进行请求的分派。
以上就是Service的各个实现类,大家可能发现了,我并没有讲解类似于内存缓存,本地储存以及网络请求的具体实现,但是整个逻辑依旧是非常清晰的。所以看到这里我想有些同学已经理解了,到底该如何设计一个框架:框架的每个层次都需要使用接口进行抽象,能使用接口进行交互的地方就别使用实现类,在改变了一个具体子功能的实现之后其他模块并不会受到影响。 能做到前面说的几点,我想写出来的代码应该算是一个比较容易扩展的架构了。
6.更加上层的封装
我们前面将了DataEngine就算是我们的暴露给客户端的api了,但是这个类用起来还是有点麻烦的,比如说我们的Reponse的泛型参数还没有用起来,这样可能在某些地方需要我们手动进行类型转化,一不小心可能还会将类型转错了。所以接下来我们再在DataEngine之上封装一层api。
public class Transform<Response1,Response2 ,Response3> implements ToLocalDataRequest<Observable<Response<Response1,Response2,Response3>>,Observable<Response>>,
ToNetworkRequest<Observable<Response<Response1,Response2,Response3>>,Observable<Response>> {
public static <Response1> Observable<Response<Response1,Object,Object>> transformOne(Request request) {
Transform<Response1,Object,Object> transform=new Transform<>();
try {
return TransformDataMethodDispatch.dispatch(transform,transform,request, DataEngine.sInstance.request(request));
} catch (Exception e) {
e.printStackTrace();
return Observable.just(Response.<Response1,Object,Object>getFailedResponse(e));
}
}
public static <Response1,Response2> Observable<Response<Response1,Response2,Object>> transformTwo(Request request) {
Transform<Response1,Response2,Object> transform=new Transform<>();
try {
return TransformDataMethodDispatch.dispatch(transform,transform,request, DataEngine.sInstance.request(request));
} catch (Exception e) {
e.printStackTrace();
return Observable.just(Response.<Response1,Response2,Object>getFailedResponse(e));
}
}
public static <Response1,Response2,Response3> Observable<Response<Response1,Response2,Response3>> transformThree(Request request) {
Transform<Response1,Response2,Response3> transform=new Transform<>();
try {
return TransformDataMethodDispatch.dispatch(transform,transform,request, DataEngine.sInstance.request(request));
} catch (Exception e) {
e.printStackTrace();
return Observable.just(Response.<Response1,Response2,Response3>getFailedResponse(e));
}
}
@Override
public Observable<Response<Response1, Response2, Response3>> updateAndGetAppConfig(Request request, Observable<Response> responseObservable) throws Exception {
return responseObservable
.map(new Func1<Response, Response<Response1, Response2, Response3>>() {
@Override
public Response<Response1, Response2, Response3> call(Response response) {
return response;
}
});
}
@Override
public Observable<Response<Response1, Response2, Response3>> getAppConfig(Request request, Observable<Response> responseObservable) throws Exception {
return responseObservable
.map(new Func1<Response, Response<Response1, Response2, Response3>>() {
@Override
public Response<Response1, Response2, Response3> call(Response response) {
return response;
}
});
}
@Override
public Observable<Response<Response1, Response2, Response3>> updateAndGetUserConfig(Request request, Observable<Response> responseObservable) throws Exception {
return responseObservable
.map(new Func1<Response, Response<Response1, Response2, Response3>>() {
@Override
public Response<Response1, Response2, Response3> call(Response response) {
return response;
}
});
}
@Override
public Observable<Response<Response1, Response2, Response3>> getUserConfig(Request request, Observable<Response> responseObservable) throws Exception {
return responseObservable
.map(new Func1<Response, Response<Response1, Response2, Response3>>() {
@Override
public Response<Response1, Response2, Response3> call(Response response) {
return response;
}
});
}
@Override
public Observable<Response<Response1, Response2, Response3>> patrolAccountAction_login(Request request, Observable<Response> responseObservable) throws Exception {
return responseObservable
.map(new Func1<Response, Response<Response1, Response2, Response3>>() {
@Override
public Response<Response1, Response2, Response3> call(Response response) {
return response;
}
});
}
@Override
public Observable<Response<Response1, Response2, Response3>> initAppData(Request request, Observable<Response> responseObservable) throws Exception {
return responseObservable
.map(new Func1<Response, Response<Response1, Response2, Response3>>() {
@Override
public Response<Response1, Response2, Response3> call(Response response) {
return response;
}
});
}
}
- 1.这个类就是对DataEngine的封装,这个封装可以将DataEngine#request()中返回的数据类,转换成多个界面上可以直接使用的数据类。当由于不同种类的请求用到的数据类型和数量各不相同,所以这个类需要实现ToLocalDataRequest和ToNetworkRequest这两个接口,对不同的请求返回的结果,进行不同的转换。
- 1.transformOne()、transformTwo()、transformThree()这三个方法,分别表示最后转换的结果是一个、两个、三个。
- 2.后面的方法都是两个接口的实现,我举的例子中都是不需要转换的数据请求。
7.例子代码
我前面已经给出了一个app登录界面的整套运行流程,项目已经测试过了问题应该不大有问题可以加我QQ,内部有数据引擎的完整代码,项目是用MVVM+Rxjava+GreenDao写的,大家有兴趣可以去下载看看,能给个star就更好了,没兴趣的同学给大家欣赏一下我们登录界面的链式代码。不得不说Rxjava让代码逻辑变得非常清晰,而MVVM的databinding则完美的与数据引擎结合了起来。
` isShowProgressBar.set(true);
LocalDataEngine.getAppConfig()
.flatMap(new Func1<AppConfig, Observable<AppConfig>>() {
@Override
public Observable<AppConfig> call(AppConfig appConfig) {
//如果为null表示第一次打开app,所以进行全局数据初始化
if (appConfig==null) appConfig=new AppConfig(true,"13030512957",false,false);
FLog.v(TAG,String.valueOf(appConfig.isFirstOpenApp()));
//如果不是第一次打开app,将appConfig传入下面一个环节
appConfig.setAutoLogin(true);
appConfig.setRememberPassword(true);
if (appConfig.isFirstOpenApp()){
return LocalDataEngine.updateAndGetAppConfig(appConfig);
}else {
return Observable.just(appConfig);
}
}
}).flatMap(new Func1<AppConfig, Observable<AppConfig>>() {
@Override
public Observable<AppConfig> call(AppConfig appConfig) {
//如果是第一次打开app,那么去服务器拉去数据表
if (appConfig.isFirstOpenApp()){
return NetworkDataEngine.initAppData(appConfig);
}else {
//否则,将appConfig传入下面一个环节
return Observable.just(appConfig);
}
}
}).filter(new Func1<AppConfig, Boolean>() {
@Override
public Boolean call(AppConfig appConfig) {
//此时如果是第一次打开app,那么数据已经全部初始化完毕,更变第一次打开app的flag
//同时AppConfig已经准备好,可以进行接下来的操作了。
if (appConfig.isFirstOpenApp())
appConfig.setFirstOpenApp(false);
return true;
}
}).filter(new Func1<AppConfig, Boolean>() {
@Override
public Boolean call(AppConfig appConfig) {
//设置界面上的:记住密码 和 自动登录 的checkBox
isRememberPassword.set(appConfig.isRememberPassword());
isAutoLogin.set(appConfig.isAutoLogin());
//如果全局设置中,既不需要记住密码,也不需要自动登录,那么就不去获取默认的用户信息,直接显示登录界面。
if ((!appConfig.isRememberPassword())&&(!appConfig.isAutoLogin())){
isShowProgressBar.set(false);
return false;
}
return true;
}
}).flatMap(new Func1<AppConfig, Observable<UserConfig>>() {
@Override
public Observable<UserConfig> call(AppConfig appConfig) {
//根据全局配置获取默认用户的帐号,然后去SharePreference中获取默认用户数据
return LocalDataEngine.getUserConfig(appConfig.getNowUserPhone());
}
}).filter(new Func1<UserConfig, Boolean>() {
@Override
public Boolean call(UserConfig userConfig) {
// 如果默认用户数据为null,说明用户数据缓存被清空了,或者app是第一次被打开,此时app不需要自动登录。
if (userConfig==null){
//弹出toast说明自动登录失败的原因,并将登录界面显示出来
Toast.makeText(mBaseActivity, "用户缓存被清空,请手动登录", Toast.LENGTH_SHORT).show();
isShowProgressBar.set(false);
return false;
}else {
//数据准备就绪,进行自动登录
usernameObservable.set(userConfig.getRememberedPhone());
passwordObservable.set(userConfig.getRememberedPassword());
username=usernameObservable.get();
password=passwordObservable.get();
return true;
}
}
}).subscribe(new Action1<UserConfig>() {
@Override
public void call(UserConfig userConfig) {
login();
}
});`
本篇博客只是给大家一个启示,告诉大家如何实现一个数据引擎的架构,内部必然会出现一些纰漏,有兴趣的同学可以和我在QQ上交流。
最后大家对MVVM架构有兴趣的可以看看我的这两篇博客
不贩卖焦虑,也不标题党。分享一些这个世界上有意思的事情。题材包括且不限于:科幻、科学、科技、互联网、程序员、计算机编程。下面是我的微信公众号:世界上有意思的事,干货多多等你来看。
网友评论