去年就一直想搭建一个框架,可惜一直拖到年初才有时间。熟话说基础不牢地洞山摇。当然我们要先看项目的组织结构,既然是一个通用的库,就只能包括通用的模块。
项目模块划分
|- base
|-BaseApplication
|-BaseActivity
|-BaseFragment
|-BaseActivityLifeCircleCallbak
|-db
|-message
|-network
|-okhttp
|-retrofit
|-HttpManager
|-utils
|-ARouter
|-monitor
这里我们对于模块的组合采用了Router方式,对于ios或者前端的人士应该比较熟悉,对模块复用解耦比较简单,大体项目结构如上,有些小的类就没写了,以及一些模块持续更新中。下面的讲解就以我的添加顺序来了
Network
网络模块在一个App中属于大模块,而且相对独立,所以我选择首先建立。这里选取了Retrofit+Okhttp3的插件,本身都是square出品的Retrofit与其说是为了OkHttp而生的也不过分,关于OkHttp的分析请参考我的另一篇OkHttp核心理解,关于Retrofit是基于主流请求都是restful风格,使用请参考Retrofit使用没什么大的东西,主要是对一些注解的理解。
这里可操作性比较大的就在与我们的OkHttp里面,他的精髓大概就是Interceptor了吧。
因为网络模块可以分为日志,缓存,Https,授权(废弃)。
- 日志模块我们往OkHttpClient里加了一个ApplicationInterceptor,获取request以及Response的信息,这里日志我用了自己上传Jcenter的项目,比Logger难看点,附上github地址BullDog
- 缓存这边情况比较复杂,大概讲下遇到的困难,后来就放弃了第一种方案,本来如果设计合理的话,我们其实可以使用Interceptor直接实现无网络使用缓存,有网络更新的策略。但是由于OkHttp的默认缓存是只支持Get的与我们的实际情况有所出入,反正我也讲下合理情况下的方案。
首先我们需要了解下DiskLruCache,这个本来是一个开源项目,使用文件缓存,被google官方推荐,也被OkHttp内置了,区别就是OkHttp是用了Okio的流处理(Sink,SourceOkio使用,基本操作不难),需要了解DiskLruCache的请自行Google,由于本身的Api不太友好我进行了二次封装DiskLruCacheHelper
先讲下设计合理的处理,本身我们的请求可以带Cache-Control这个头,在无网条件下只要我们在request内加入CacheControl.FORCE_CACHE这个参数,OkHttp内部会自行查询有无该缓存进行返回
为什么说Get才需要缓存呢,其实我们缓存需要一个Key,如果是get的话我们直接可以把url作为key,但是如果是post其实我们区分key的是post body里面的信息,这样就比较难处理,比如一个人的信息,get的话是带uid这类标识符的,post的话我们就无法区分是哪个人的信息了。然后是解决方法,我这边的网络请求是加了一层壳的,我们知道Retrofit结合Okhttp返回的结果是Observable,所以我们在Subscriber里面做文章,在返回接过钱我们既可以捕捉异常,又可以加缓存,这边的请求我多了一个cacheKey的参数,可以自由配置我们需要缓存的信息。根据我们的策略我们先判断有无网,有网则更新缓存,无网则取出缓存返回。
- Retrofit模块,这个模块没什么好讲,主要是用于写请求的,以及一些基本配置
- 最后加了个BaseNetWorkCallbackBean用来规范我们的网络返回
总结,这边模块分的比较多,主要遵循组合大于继承,有利于以后的拆解阅读。
DB
Db模块以前我们用的大多是core data比如说greenDao,我用的这个realm在ios平台用的比较多,网上有关于这块区别的讲解,主要还是从效率上来讲,realm明显要高出core data这块很多,有点缺陷就是对bean的污染,每个bean都要继承RealmObject,照某网友的说法就是强奸代码。Realm官方网站。
我这边主要是对Realm进行了简单的封装,对一些常用操作简单化,比方说insert,query,delete由于Realm不支持sql语句查询,所以自由度相对也就差点,不过移动端不像后端需要复杂查询各种外键条件的,我封装的几个查询应该已经满足日常使用了,此外Realm还支持异步,但有一点千万要注意,Realm的操作类并非单例,你获取一个,他就线索树里面多一个引用,所以每次使用完记得释放,这里我是在BaseActivity的生命周期内控制的,只不过异步的情况要复杂一点,在api封装上也是讲realm对象作为参数传入,在最后进行释放。
Base
这个是比较基础的部分,为什么不选择先建是因为有些东西是随着模块的加入逐步完善的,比方说前面提到的Realm,还有后面的RxBus。
- BaseApplication,这个类的核心在于一些插件的初始化。涉及到这个如果我们的插件比较多,势必影响到APP的启动速度,所以我们最简单的处理就是启用线程池,引入异步初始化,但是需要注意有些操作是一开始就需要的只能放在同步操作里,比方说Log框架的加载。这里我封装了一个ApplicationInitializer,主要的就是使用cacheThreadPool来控制初始化,分离的目的也是为了单一职责。以下为一些类的初始化
BLog.init();
/* 其他初始化任务无时序要求可多线程处理 */
cachedThreadScheduler.execute(new Runnable() {
@Override
public void run() {
Realm.init(application);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
.name(DB_NAME)
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(realmConfiguration);
}
});
cachedThreadScheduler.execute(new Runnable() {
@Override
public void run() {
Fresco.initialize(application);
}
});
cachedThreadScheduler.execute(new Runnable() {
@Override
public void run() {
// LeakCanary.install(application);
}
});
cachedThreadScheduler.execute(new Runnable() {
@Override
public void run() {
// BlockCanary.install(application,new AppBlockCanaryContext()).start();
}
});
cachedThreadScheduler.execute(new Runnable() {
@Override
public void run() {
ARouter.init(application);
}
});
除了初始化这块,ICE_CREAM_SANDWICH以后我们可以通过在Application内注册ActivityLifeCircleCallbak监听activity的生命周期,后面可以了解到Arouter也可以检测到,或者第三种方案就是在Application内维持一个List<Activity> activities,BaseActivity生命周期内去更新这个变量。
- BaseActivity 这个作为跨页面控制比较重要的结构,可以封装一些比较常用的方法,这里我主要维护了一个List<Fragment> fragments,用来管理activity下面的fragment,因为当前app主流还是单activity多fragment的模式,还有就是保存一些经常使用的变量,比如Realm,Rxbus还有ioc框架的初始化,这里的ioc我也是自己写的 比较简单,只有click,longclick两个事件Potato
Utils
这个模块就大概讲解下,主要都是一些基本的工具包,各个项目通用,github有提供下载。
Rxbus
类似与EventBus,因为本身我们引入了RxJava,所以用rxjava来实现事件总线是更可取的方法。参考rxbus实现,在post中采用了tag,更符合eventbus的使用习惯,具体就是封装Event包裹了一个tag对用户透明,在返回的时候先filter筛选然后采用flatMap进行转化
MVP
关于mvp模式的实现,中间想了蛮久,主要是纠结于怎样使用范型,在BaseActivity进行P跟V的绑定,以及P跟M的关联,参照了google的mvp实现,使用了contracts使接口或者抽象类更加关系明确,集中。
这里我们实现了BasePresenter<T>这里的T是用来引入M的,而V的引入在BaseActivity<V extends BasePresenter>这样就形成了一个V初试化P,然后P初始化M的链,P就成为了关键,当V destroy的时候就P就解绑V,释放M,把业务逻辑放到P上,插句题外话,中间了解了下mvvm也就是m-vm-v的模式,直接在布局层就将数据跟v通过viewmodel紧密结合了,省掉了findViewById的工作,但个人不太喜欢这种模式,感觉可读性较差,分层不是那么明确
monitor
监视框架,已经引入了blockcanary,leakcanary,接下来要引入aop设计进行关键点检测,然后接入友萌,这个就是中国版的firebase当初去外企面试被问倒了,哈哈哈
test
测试框架还没开始,主要分单元测试,espresso ui测试,mock数据...测试框架蛮多的,最近再看,按顺序来吧
ARouter
路由机制,github上有两个下载量多的,之所一选择ali的完全是信仰吧,基本就是一些配置,使用说明官网有ARouter,采用这种机制也是偶然在android weekly上看到人家一个老司机用这种框架搭建中小型项目,也算是一种新的尝试吧。
2017/3/1:更新Mvp
文章未完待续,尽量月底前完成接下来的工程,欢迎喜欢这文章的朋友下载我的代码,帮我发现问题,完善框架SpaghettiFrame