MVP简单实作

Demo包架构

MVP简单实作_第1张图片
Demo包架构

** data:jason entity class
** demosaarsapp:
demo app的主体包,其中包含了App activity部分以及MVP部分的实体类
** service:Retrofit的Annotation类,封装了http request的相关api
** res:
android 资源类


Demo代码架构

MVP简单实作_第2张图片
DemoApp架构

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发生互动,当ViewModel单方面发生变化时,需要通过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的部分代码写的比较啰嗦,其主体思想如下
  1. 需要针对Presenter(其源头是View)的数据改变请求,去做数据改变
  2. 当数据发生改变后,需要通知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的变更。


Plugin 机制研究,To be continue

你可能感兴趣的:(MVP简单实作)