Demo包架构
** data:jason entity class
** demosaarsapp:demo app的主体包,其中包含了App activity部分以及MVP部分的实体类
** service:Retrofit的Annotation类,封装了http request的相关api
** res:android 资源类
Demo代码架构
MVP架构设计
** IBaseView**
定义loading状态的api
public interface IBaseView {
void onLoading();
void cancelLoading();
void showErrorMsg(String e);
}
** IBasePresenter**
定义l获取view的api
public interface IBasePresenter{
void attachView(V inV);
void deattachView();
V getAttacchView();
}
** IBaseModel**
呵呵呵...
public interface IBaseModel {
}
上面是MVP三元素的interface,针对project中不同的组合,需要自行衍生出java class
例如在Demo中,我们需要针对RecycleView的下拉刷新,上拉加载做客制化
因此需要额外创建IDemoView:
public interface IDemoView extends IBaseView {
void loadMoreData(ArrayList inListDataBean);
void refreshData(ArrayList inListDataBean);
}
Implement for MVP
View : DemoActivity
public class DemoActivity extends AppCompatActivity implements IDemoView {
……
}
Model: DemoModel
public class DemoModel implements IBaseModel {
……
}
Presenter: DemoPresenter
public class DemoPresenter implements IBasePresenter {
……
}
至此,大体思路就已经成型了。
Activity作为View,主要负责呈现xml中的layout,并接收外界事件丢给Presenter处理
Presenter会与View进行绑定,处理来自View的各种事件,并与Model进行互动
Model会与Presenter发生互动,当View或Model单方面发生变化时,需要通过Presenter来进行协调,确保两者行为变更的一致
Android现有框架说明:
网络加载框架:Retrofit
图片加载框架:Picasso
响应式编程框架:Rxjava
带有下拉刷新,上拉加载的RecyclerView:XRecycleView
JSON数据的解析:Gson
Gradle配置如下:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile project(':xrecyclerview')
compile 'io.reactivex:rxandroid:1.2.0'
compile 'io.reactivex:rxjava:1.1.5'
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta3'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.1'
}
代码解析
View:DemoActivity
View:负责UI的显示
- 初始化
因为我们借助了Activity来做为View的部分,所以MVP中View是第一个被创建的
鉴于此,在Activity的onCreate中我们需要把Presenter与View的联系表现出来
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mPresenter = new DemoPresenter();
mPresenter.attachView(this);
……
}
根据视图显示的要求,我们选用RecycleView来实现list滑动
在网络上我们发现有现成的XRecycleView
使用的方法比较简单,可以近似认为跟RecycleView没有什么不同。
这里封装了两种行为 下拉刷新 上拉加载:
也就是当list处于最顶部时,继续进行下拉就会触发刷新的callback
而如果上拉到一定程度,继续进行上拉就会触发加载的callback
代码段如下:
public interface XRecyclerView.LoadingListener{
void onRefresh();
void onLoadMore();
}
在onCreate中XRecycleView的初始化:
@Override
protected void onCreate(Bundle savedInstanceState){
……
mRecyclerView = (XRecyclerView)findViewById(R.id.recyclerview);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//设置layout样式
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
//设置RecycleView的Adapter
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setArrowImageView(R.drawable.ic_loading_rotate);
//设置Listener
mRecyclerView.setLoadingListener(new XRecyclerView.LoadingListener() {
@Override
public void onRefresh() {
mPresenter.refreshData();
}
@Override
public void onLoadMore() {
mPresenter.loadMoreData(mLoadTimes);
}
});
}
可以看到XRecycleView的事件处理其实都是被wrap到了Presenter中。
通过这样的解耦动作,View的整体设计就会变得比较简单,只需要处理显示相关的逻辑
而至于那些click动作,数据变化等非显示相关,就通通交给Present
从而解决原本Activity臃肿庞大,后期难以维护的问题
补充一下,之前有提到Activity会作为IDemoView的实体存在,代码如下:
- implement
public class DemoActivity extends AppCompatActivity implements IDemoView {
……
@Override
public void loadMoreData(ArrayList inListDataBean) {
mSaarsBean.clear();
mSaarsBean.addAll(inListDataBean);
mAdapter.notifyDataSetChanged();
mRecyclerView.loadMoreComplete();
}
@Override
public void refreshData(ArrayList inListDataBean) {
mSaarsBean.clear();
mSaarsBean.addAll(inListDataBean);
mAdapter.notifyDataSetChanged();
mRecyclerView.refreshComplete();
}
……
}
至此,View的部分就介绍完了。
思考:
XRecycleView的Adapter能否一并做成MVP架构?
Presenter:DemoPresenter
Presenter:处理来自View与Model的请求
public class DemoPresenter implements IBasePresenter {
private V mView;
……
public int refreshData() {
mModel.getDataList(new Subscriber>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ArrayList listDemoEntiry) {
getAttacchView().refreshData(listDemoEntiry);
}
},0);
return 0;
}
public void loadMoreData(int mLoadTimes){
mModel.getDataList(new Subscriber>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ArrayList listDemoEntiry) {
getAttacchView().loadMoreData(listDemoEntiry);
}
},mLoadTimes);
}
出于Demo需要,Presenter只处理了两个事件,View的refreshData和loadMoreData
在这里,我们可以看到Presenter需要继续call Model的api来完成
因为RefreshData和loadMoreData都会牵扯到数据的变化,所以需要由Presenter来串起View->Model这边在实现上使用了RxJava的框架,在call Model的时候传入了一个Subscriber对象
void getDataList(final Subscriber> inSubscriber, int page)
- 传入Subscriber对象的意义在于可以在model内部回调到Presenter,完成了一系列类似注册-回调这样的机制。
在Subscriber的onNext中,我们是ByPass了model的诉求,即数据改变了,View也要跟着改变了!
因此需要继续调用View的api
使用Rxjava只是一种尝试,是否还有其他的方式可以简洁明了的完成这一实现呢?
Model:DemoModel
- Model的部分代码写的比较啰嗦,其主体思想如下
- 需要针对Presenter(其源头是View)的数据改变请求,去做数据改变
- 当数据发生改变后,需要通知Presenter去做View的改变
可以看到这两种case下,前者是Model的”被动接收”,后者是Model“主动出击”。
前面我们有提到Presenter在操作Model的时候,传入了Rxjava的Subscriber,如果没有传入这个Subscriber,那么在做2)这一步的时候,是不是需要持有Presenter对象的引用才可以做到呢?
- 基于上述认知,我们来看一下具体的代码:
PS:目前已知的数据来源是远端server...
public class DemoModel implements IBaseModel{
……
void getDataList(final Subscriber> inSubscriber, int page) {
if(page == 0) {
mSaarsApiService = RetrofitUtils.getRetrofitInstance().create(SaarsApiService.class);
mSaarsApiService.getDataList("", "LeJianMovie", "rec_0603",
"2.1.5.0118", "zh_CN", "16",
"", "mobile", "", "lejian", "868918020040915")
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Log.e("TAG", e.getMessage());
}
@Override
public void onNext(SaarsBean saarsBean) {
List list = saarsBean.getRec();
for (int i = 0; i < list.size(); i++) {
DemoEntity entiry = new DemoEntity();
entiry.setText(list.get(i).getData().getName());
entiry.setBitmapURL(list.get(i).getData().getMini_poster());
mModel.add(entiry);
updateData(inSubscriber,0);
}
}
});
}
else
{
updateData(inSubscriber,page);
}
}
}
Wait,这里出现的RetrofitUtils又是什么鬼?
- Retrofit:android上的网络加载框架
Retrofit英文官网
Retrofit中文版介绍
ok, Let's read the fucking source code
public class RetrofitUtils {
public static final String BASE_URL = "http://sarrs.go.letv.com/sarrs/";
private static RetrofitUtils mRetrofitUtils;
private Retrofit mRetrofit;
private RetrofitUtils() {
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
public static RetrofitUtils getRetrofitInstance() {
if (mRetrofitUtils == null) {
synchronized (RetrofitUtils.class) {
mRetrofitUtils = new RetrofitUtils();
}
}
return mRetrofitUtils;
}
public T create(Class service) {
return mRetrofit.create(service);
}
}
- 这里的一些用法都是比较基础的Retrofit教程,注意,因为我们要配合Rxjava来使用,所以...有一个比较特别的地方:
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
//请注意下面这一行
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
//请注意上面这一行
.build();
当增加了Rxjava的adapter以后,service中返回的类型都会是Observable
当然了,所谓的service就是...
public interface SaarsApiService{
/**
* 获取福利列表
*
* @param pageSize
* @param currentPage
* @return
*
* "http://gank.io/api/";
* @GET("data/福利/{pageSize}/{currentPage}")
*(@Path("pageSize") int pageSize, @Path("currentPage") int currentPage);
* http://sarrs.go.letv.com/sarrs/
*
* "http://sarrs.go.letv.com/sarrs/
* apirec_json.so?
* platform=
* &versiontype=LeJianMovie
* &area=rec_0603
* &version=2.1.5.0118
* &lang=zh_CN
* &cid=16
* &uid=
* &from=mobile
* &sso_tk=
* &variant=lejian
* &lc=868918020040915"
*/
@GET("apirec_json.so?")
Observable getDataList(@Query("platform") String platform,
@Query("versiontype") String versiontype,
@Query("area") String area,
@Query("version") String version,
@Query("lang") String lang,
@Query("cid") String cid,
@Query("uid") String uid,
@Query("from") String from,
@Query("sso_tk") String sso_tk,
@Query("variant") String variant,
@Query("lc") String lc
);
}
上述的定义可以在Retrofix的中文介绍中看到具体的含义,这里就不再赘述了。
再回到Model的getDataList,配合Retrofit的定义再来看~先前的问题应该就可以迎刃而解了
//创建了用于跟HTTP做交互的"Service"
mSaarsApiService = RetrofitUtils.getRetrofitInstance().create(SaarsApiService.class);
//通过创建的"Service“直接做Http request
//通过查看define的定义,我们这里会直接去做request:
//http://sarrs.go.letv.com/sarrs/apirec_json.so?
//platform=&
//versiontype=LeJianMovie&
//area=rec_0603&
//version=2.1.5.0118&
//lang=zh_CN&
//cid=16&
//uid=&
//from=mobile&
//sso_tk=&
//variant=lejian&
//lc=868918020040915
//即:http://sarrs.go.letv.com/sarrs/apirec_json.so?platform=&versiontype=LeJianMovie&area=rec_0603&version=2.1.5.0118&lang=zh_CN&cid=16&uid=&from=mobile&sso_tk=&variant=lejian&lc=868918020040915
mSaarsApiService.getDataList("", "LeJianMovie", "rec_0603",
"2.1.5.0118", "zh_CN", "16",
"", "mobile", "", "lejian", "868918020040915")
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(……)
至此,我们就完成了一个新的Subscribe,它的作用是检测HTTP Request是否成功,如果成功就会进入到我们注册的Subscribe,而在这个Subscribe中我们就会完成数据的变更,并回调到Presenter,再通过Presenter去call View的api,从而达到View的变更。