为了学习MVP+Retrofit+RxAndroid(RxJava)+dagger2框架我找了不少文章也看了不少的开源项目,这一个是我认为较为完备的,在争取到作者的同意之后我决定单独写几篇博客将之前的笔记整理出来,供自己查阅和学习。
应用的名字叫做易读,github地址是:https://github.com/laotan7237/EasyReader,作者本人的博客地址是:http://blog.csdn.net/laotan7237/article/details/68946797,感谢作者的支持!
易读应用使用的框架主要有“dagger2”,“ButterKnife”(依赖注入框架),“retrofit2”,“okHttp”(异步网络请求框架),rxAndroid(响应式编程框架),rxBus(封装在rxJava上的事件总线框架),“Glide”(图片框架),编程模式是MVP模式(分为Model,View,Presenter三层)。
ButterKnife框架是很常见的代替findViewById的把控件采用@注解方式注入View层的框架,易用,使用API文档能解决大部分问题。官方文档地址是:http://jakewharton.github.io/butterknife/
我们把rxBus和Glide放到后面探究,将dagger,retrofit2+okHttp+rxAndroid,MVP模式这个整体,通过易读应用里的豆瓣高分电影详情这个界面的整个流程(创建-->>注入-->>请求-->>响应式处理返回数据-->>更新页面)来分析这个融为一体的框架的编写思路。
为了后续分析方便,我们先将易读应用的MVP模式实现方式做一个深入的剖析,如果你有过尝试去读优秀开发者的开源代码的经验,你应该知道我们通常都使用先从某一个具体的Activity,然后依次向下寻找继承的父类和实现的接口,这样的方式去阅读;同时也有从接口读起,然后去使用findUsager去查看哪些地方实现了它从下至上的读法。
因为这是一篇阅读总结式的文章,我描述的顺序是先沿着第一种方式自上往下读,然后最后回过头来总结一下有哪些接口,哪些地方实现它,该在什么时候实现,我自己认为这种方法适合我去总结的原因是第一种方法更加利于我读懂整个框架的运行流程,而读完之后第二种的总结更像是我自己编程时的使用顺序(先写接口,再写实现)。
首先我找到MoviewTopDetailActivity,它继承了一个泛型为DoubanMoviewDetailPrensterImpl的LoadingBaseActivity,并且实现了一个DoubanMovieDetailPresenter.View的接口,LoadingBaseActivity应该是一个带有载入(loading)功能的基础(base)活动页(activity),从泛型来看它应该是一个为豆瓣电影详情(DoubanMovieDetail)而实现的Presenter(P层)具体实现(Impl),联系到MVP模式的最佳实践,我们猜测,LoadingBaseActivity里携带有一个T泛型的Presenter,然后DoubanMovieDetailPresenterImp是这个Presenter的一种实现,DoubanMoviewDetailPresenter.View这个接口不容易推断出来,但是从我们可以从MVP最佳实践猜测,这就是View接口,可能会带有类似于updateData一类的方法。
public classMovieTopDetailActivity extendsLoadingBaseActivity<DoubanMovieDetailPresenterImpl>implements DoubanMovieDetailPresenter.View
猜完了,我们可以进去看看具体情况了,先看LoadingBaseActivity,它继承自BaseActivity,这应该是一个基础(base)活动页(activity),实现了Stateful接口,这个接口我们没办法推断用途,我们并不意外的看到泛型T是继承自BasePresenter,同时LoadingBaseActivity确实持有一个T型实例mPresenter,我们马上意识到MoviewTopDetailActivity中就会持有一个DoubanMovieDetailPresenterImpl类型的实例mPresenter了,这里看到在这个mPresenter上有一个@Inject,后面我们会着重的探讨这个@注解的用法,同时我们可以注意到这个类有一个变量类型为LoadingPage的实例,根据我们使用过的应用,大胆猜测一下这是一个可以显示“空”、“加载失败”、“加载中”,“加载成功”几种不同界面的一个控件,因为要层叠的放置这几个界面,它应该是继承自FrameLayout的。
public abstract classLoadingBaseActivity<T extendsBasePresenter> extendsBaseActivity implementsStateful
protectedLoadingPage mLoadingPage;//一个实现了冲成功加载界面,加载中,加载失败,空界面的FrameLayout
@Inject
protected TmPresenter;//使用Dagger2注入的P层实例,类型为BasePresenter的子类
读到这里,我们还想尝试看一看这个LoadingBaseActivity进行了哪些操作,按照生命周期顺序,首先看onCreate方法,这里面使用到了initUI()、initInject()、setFrameLayoutId()、initView()、loadData()全部是抽象方法,这说明这些方法在我们刚刚看到的DoubanTopMovieDetailActivity中都会有具体的实现,我们主要根据方法名来看一下整个调用顺序,initUI()这是对UI的初始化操作,initInject()不知道用法,mPresenter.attachView(this)我们可以想到BasePresenter或者它的父类应该是有这样一个方法的,从方法名上看应该是当P层粘附到V层上的时候就要调用的方法,从MVP模式最佳实践中我们猜测,P层获取V层实例应该在这个方法中实现,再往后把上面看到的LoadingPage的实例给构造出来并添加到当前的FrameLayout里了,这里我们可以看到LoadingPage应该有initView()、loadData()、getLayoutId()三个抽象方法需要实现,最后调用了LoadingBaseActivity的loadData()方法加载了数据
@Override
protected void onCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
initUI();
initInject();//实现注入的代码
mPresenter.attachView(this);
flBaseContent= (FrameLayout)findViewById(setFrameLayoutId());
if(mLoadingPage== null){
mLoadingPage= newLoadingPage(this){
@Override
protected voidinitView(){
bind= ButterKnife.bind(LoadingBaseActivity.this,contentView);
LoadingBaseActivity.this.initView();
}
@Override
protected voidloadData(){
LoadingBaseActivity.this.loadData();
}
@Override
protected intgetLayoutId(){
returnLoadingBaseActivity.this.getContentLayoutId();
}
};
}
flBaseContent.addView(mLoadingPage);
loadData();
}
除了onCreate外还有onDestroy和setState方法,可以看到onDestroy对应onCreate方法对Butterknife绑定的控件解绑,并调用了mPresenter的detachView方法(对应attachView方法)。
@Override
public void setState(intstate){
mLoadingPage.state= state;
mLoadingPage.showPage();
}
setState这时候我们想到应该就是Stateful接口需要实现的方法了,这里给LoadingPage设置了状态并且调用了showPage方法。分析到这里我们回头想想,接下来有哪些东西需要跟进去进一步看一看,第一BasePresenter是什么样的,第二是BaseActivity是什么样的,第三LoadingPage是不是我们想象的那样是一个显示这几种状态界面的一个FrameLayout,第四Stateful接口提供给的setState方法有什么用途。
@Override
protected void onDestroy(){
super.onDestroy();
if(bind!= null){
bind.unbind();
}
if(mPresenter!=null){
mPresenter.detachView();
}
}
我们先进到LoadingPage中,正如同我们所预想的,它继承自FrameLayout,在类里,持有空界面、错误界面,加载中界面和加载成功后的界面几个View,并且有一个int类型的变量State,是和Statrful接口中的setState对应的,在LoadingBaseActivity中就实现了这个方法,对mLoadingPage的state进行了设置,同时在类里还有一个AnimationDrawable类型的mAnimationDrawable和一个ImageView的img,这里我们可以猜到是用作在加载中时的动画效果,进一步使用findUsage的方式查找到使用到的地方,AnimationDrawable是通过(AninationDrawable)Imageview.getDrawable的方式初始化,然后使用start方法开始的,Imageview的src使用xml资源文件外层标签是<animation-list>。
public abstract classLoadingPage extendsFrameLayout
private View loadingView; //加载中的界面
private ViewerrorView; //错误界面
private ViewemptyView; //空界面
public ViewcontentView; //加载成功的界面
private AnimationDrawablemAnimationDrawable;
private ImageViewimg;
public int state= AppConstants.STATE_UNKNOWN;
LoadingPage里包含有创建时的init方法,这个方法里将所有View创建出来,然后调用了showPage()方法,这个方法根据当前的state开启、关闭动画,设置几个View的可见性,其中创建View时,LoadingView采用我们上面探讨到的方法对AnimationDrawable初始化,showPage的开始也针对state,对LoadingView进行了单独的初始化。
private voidinit(){
this.setBackgroundColor(getResources().getColor(R.color.colorPageBg));
//把loadingView添加到frameLayout上
if(loadingView== null){
loadingView= createLoadingView();
this.addView(loadingView,LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
}
//把emptyView添加到frameLayout上
if(emptyView== null){
emptyView= createEmptyView();
this.addView(emptyView,LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
}
//把errorView添加到frameLayout上
if(errorView== null){
errorView= createErrorView();
this.addView(errorView,LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
}
showPage();//根据状态显示界面
}
private View createLoadingView(){
loadingView= LayoutInflater.from(mContext).inflate(R.layout.basefragment_state_loading,null);
img= (ImageView)loadingView.getRootView().findViewById(R.id.img_progress);
//加载动画这边也可以直接用progressbar可以看看topnews页下拉刷新就是只用用progressbar控制动画
mAnimationDrawable= (AnimationDrawable)img.getDrawable();
//默认进入页面就开启动画
if(!mAnimationDrawable.isRunning()){
mAnimationDrawable.start();
}
returnloadingView;
}
public voidshowPage(){
if(loadingView!= null){
if(state ==STATE_UNKNOWN ||state ==STATE_LOADING){
loadingView.setVisibility(View.VISIBLE);
//开始动画
startAnimation();
}else {
//关闭动画
stopAnimation();
loadingView.setVisibility(View.GONE);
}
}
if(state ==STATE_EMPTY ||state ==STATE_ERROR ||state ==STATE_SUCCESS){
//关闭动画
stopAnimation();
}
接着我们看看BaseActivity,作为Activity的基础类,我们认为在BaseActivity里面应该封装了所有activity都会使用到的操作,并将每个activity差异的地方使用接口的方式留下来,以供子类实现,在阅读其他开源代码的时候曾经有将AppManager的功能封装到BaseActivity当中的,有机会我们可以单独对不同项目对BaseActivty和BaseFragement等的抽象做一个总结,看一看究竟怎么样的基类才能被称作“最佳实践”。
public abstract classBaseActivity extendsAppCompatActivity implementsLifeSubscription
//管理运行的所有的activity
public final staticList<AppCompatActivity> mActivities= newLinkedList<AppCompatActivity>();
public staticBaseActivity activity;
这里我们看到BaseActivity继承自AppCompatActivity,并实现了LifeSubsciption接口,这个地方我们还不知道为什么要实现LifeSubscription接口,从接口名称上我们自然而然的将它和响应式编程的Subscription概念联想到一起,在BaseActivity中我们还看到了一个LinkedList<AppCompatActivty>并且被设置为了静态(static),上面我们提到过这种处理思路,这是把AppManager糅合到了BaseActivity当中,按照我们在处理AppManager的方式,我们去onCreate里看一看,是不是将this添加到了这个链表里,在这里我们验证了猜测,特别的我们看到,框架作者将这个链表加上了锁,以保证不会存在同时对链表进行操作的情况,这是因为onDestroy里会对链表进行删除。
@Override
protected void onCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(getLayoutId());
mVelocityTracker= mVelocityTracker.obtain();
decorView= getWindow().getDecorView();
synchronized(mActivities){
mActivities.add(this);
}
}
跟进LifeSubscription接口中,我们看到这个接口定义了一个方法叫bindSubscription,通过findUsage,跟到调用这个方法的地方,在HttpUtils里,invoke传入的LifeSubscription类型的lifecycle调用了这个方法,我们继续使用findUsage跟进这个invoke方法使用的地方,使用的地方在BasePresenter里,可以看到传入的lifecycle是由一个mView强制转换为的LifeSubscription,根据MVP模式最佳实践,我们猜测这个就是P层持有的V层实例,在我们分析的这一整个流程中,而BasePresenter的实现是DoubanMovieDetailPresenterImpl,DoubanMovieDetailActivity本身就实现了一个View接口,所以说DoubanMovieDetailPresenterImpl中持有的就是DoubanMovieDetailActivity,而这个Actvity又是BaseActivity的孙子类(子类LoadingBaseActivity类),所以说它也就是LifeSubscription接口的实现了,BasePresenter(实际是DoubanMovieDetailPresenterImpl)调用HttpUtils的invoke方法对BaseActivity(实际是DoubanMovieDetailActivity)中bindSubscription方法的实现,这时候我们可以看一下BaseActivity里怎么实现的bindSubscription了,
void bindSubscription(Subscriptionsubscription);
public classHttpUtils
public static<T> voidinvoke(LifeSubscriptionlifecycle,Observable<T> observable,Callback<T> callback)
Subscriptionsubscription =observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(callback);
lifecycle.bindSubscription(subscription);
public classBasePresenter<T extendsBaseView>
protectedT mView;
protected<T> voidinvoke(Observable<T>observable,Callback<T> callback){
this.callback= callback;
HttpUtils.invoke((LifeSubscription)mView,observable,callback);
}
这里定义了一个CompositeSubscription,在第一次使用时对其初始化,在每次调用的时候都会讲HttpUtils里面构造出的Subscription添加进来,使用Subsriber和Observer以及Subscription进行响应式编程的方法,我们留在rxJava中系统探究。
public abstract classBaseActivity extendsAppCompatActivity implementsLifeSubscription
private CompositeSubscription mCompositeSubscription;//综合订阅
//用于添加rx的监听的在onDestroy中记得关闭不然会内存泄漏。
@Override
public void bindSubscription(Subscriptionsubscription){
if(this.mCompositeSubscription== null){
this.mCompositeSubscription= newCompositeSubscription();
}
this.mCompositeSubscription.add(subscription);
}
另外,BaseActivity实现了对ToolBar的控制且添加了一个向左滑动关闭当前界面的功能,并且设计了动画,对于这个动画效果的处理类似于自定义View,我们放到后面单独做分析。
接下来我们看BasePresenter,这个基础(base)P层(Presenter)的最主要方法我们刚才分析invoke方法的时候讲到了,在MVP最佳实践中,我们通常都将View层实例放在P层里,然后P层去做业务操作,并把结果反映到Model层(比如对数据库进行操作等),同时对View层实例进行update操作,这意味着我们的View可以定义成一个接口,留下update方法作为待实现方法。而我们把响应式编程加到整个框架中就是对P层的业务操作用观察者模式观察,一旦业务完成就去调用这个update方法,观察者模式的实现就是这里invoke所做的,这里传入的CallBack是业务的回调,当成功进入回调,则会进行update操作,在这里我们可以到CallBack里看一眼,具体这个响应式是怎么处理的,我们还是留在retrofit+okHttp+rxJava中进行深入的研究。
public voidonResponse(Tdata){
/**
* 如果喜欢统一处理成功回掉也是可以的。
*不过获取到的数据都是不规则的,理论上来说需要判断该数据是否为null或者list.size()是否为0
* 只有不成立的情况下,才能调用成功方法refreshView/()。如果统一处理就放在每个refreshView中处理。
*/
((BaseView)target).refreshView(data);
}
我们看到BasePresenter里持有一个View层实例,一个Reference类,mView继承自BaseView,根据我们刚才的分析这个BaseView应该就是View接口,有待实现update方法,Reference是一个弱引用,这里因为我不会使用,但是又不是重点所以暂时留在这里了,这个弱引用的作用是在attachView和detachView的时候拿到mView的,我理解它是P层和V层的关联(一个很模糊的概念),具体我们可以看到这两个方法。
public classBasePresenter<T extendsBaseView>
protected Reference<T>mReferenceView;//指的是界面,也就是BaseFragment或者BaseActivity
protected TmView;
private Callbackcallback;
public voidattachView(LifeSubscriptionmLifeSubscription){
this.mReferenceView= newWeakReference<>((T)mLifeSubscription);
mView= mReferenceView.get();
}
//P层示例被销毁的时候调用
public void detachView(){
if(mReferenceView!= null)
mReferenceView.clear();
mReferenceView= null;
if(mView!= null)
mView= null;
if(callback!= null){
callback.detachView();
}
}
使用findUsage的方法去查找调用这个方法的地方,发现其实我们前面已经见到过了,在LoadingBaseActivity里init的时候mPresenter调用了这个方法,传入了this作为LifeSubscription,因为LoadingBaseActivity是BaseActivity的子类,所以它确实是一个LIfeSubscription的实现。
最后剩下BaseView,我们跟进去看一下这个接口,果不其然,里面有一个待实现的“update”(完成刷新操作)。
public interfaceBaseView<T> {
voidrefreshView(TmData);//获取数据成功调用该方法。
}
这时候我们回归到最前面的DoubanTopDetailActivity解决最后的问题,首先是DoubanMovieDetailPresenterImpl,这个BasePresenter的实现类,我们要一谈究竟,它应该会调用到BasePresenter的invoke方法进行具体的业务请求,fetchMovieDetail(String id)这个方法就是具体的业务,从方法名和参数我们大胆的猜测这是一个传入movie的id号获取详情的方法,这里的@Inject注解和之前在LoadingBaseActivity里的BasePresenter上的@Inject对应。
public classDoubanMovieDetailPresenterImplextends BasePresenter<DoubanMovieDetailPresenter.View>implements DoubanMovieDetailPresenter.Presenter{
privateDoubanServicemDoubanService;
@Inject
publicDoubanMovieDetailPresenterImpl(DoubanServicemDoubanService){
this.mDoubanService= mDoubanService;
}
@Override
public voidfetchMovieDetail(Stringid){
invoke(mDoubanService.fetchMovieDetail(id),newCallback<MovieDetailBean>(){
});
}
}
然后为了能让整个MVP框架完备,我们分析DoubanTopDetailActivity和DoubanMovieDetailPresenterImpl的继承和实现关系,DoubanTopDetailActivity实现的并不是BaseView接口,而是DoubanMovieDetailPresenter.View,DoubanMovieDetailPresenterImpl里给父类BasePresenter传入的泛型T也是这个View类型,并且实现了同一个接口DoubanMovieDetailPresenter的子接口Presenter,这里错综的关系很容易让初次使用这个框架的人混乱。
public classMovieTopDetailActivity extendsLoadingBaseActivity<DoubanMovieDetailPresenterImpl>implements DoubanMovieDetailPresenter.View
public classDoubanMovieDetailPresenterImplextends BasePresenter<DoubanMovieDetailPresenter.View>implements DoubanMovieDetailPresenter.Presenter
其实框架作者是巧妙的将View和将待实现的业务fetchMovieDetail方法同时封装到了一个接口DoubanMovieDetailPresenter中,这样我们很容易的就能看到业务的结构,我们跟到这个接口里,Presenter定义好了待实现的与DoubanMovieDetail相关的业务方法,也定义好了继承自BaseView并指定好泛型为业务相关的Bean的BaseView,这样只要DoubanTopDetailActivity实现这个接口(即DoubanTopDetailActivity是DoubanMovieDetailPresenter.View的实现),DoubanTopDetailActivity中又持有继承自BasePresenter(P层)的DoubanMovieDetailPresenterImpl,且DoubanMovieDetailPresenterImpl里持有的View(V层)就是类型为DoubanMovieDetailPresenter.View的DoubanTopDetailActivity,这样就实现了V层和P层的互相持有,并且将V层和P层的接口绑定在了新接口里方便使用和阅读。
public interfaceDoubanMovieDetailPresenter{
interfaceView extendsBaseView<MovieDetailBean> {
}
interfacePresenter {
voidfetchMovieDetail(Stringid);
}
}
到这里我们就看完了整个MVP框架,在下一节我们将会继续分析retrofit+okHttp进行网络请求和使用rxjava进行响应式编程。