MVP简单实作

Demo包架构

Demo包架构

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


Demo代码架构

DemoApp架构

MVP架构设计
** IBaseView**

定义loading状态的api

public interface IBaseView {
    void onLoading();
    void cancelLoading();
    void showErrorMsg(String e);
}

** IBasePresenter**

定义l获取view的api

public interface IBasePresenter<V extends IBaseView>{
    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<DemoEntity> inListDataBean);
    void refreshData(ArrayList<DemoEntity> 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<DemoEntity> inListDataBean) {
        mSaarsBean.clear();
        mSaarsBean.addAll(inListDataBean);
        mAdapter.notifyDataSetChanged();
        mRecyclerView.loadMoreComplete();
    }

    @Override
    public void refreshData(ArrayList<DemoEntity> inListDataBean) {
        mSaarsBean.clear();
        mSaarsBean.addAll(inListDataBean);
        mAdapter.notifyDataSetChanged();
        mRecyclerView.refreshComplete();
    }
    ……
}

至此,View的部分就介绍完了。

思考:
XRecycleView的Adapter能否一并做成MVP架构?

Presenter:DemoPresenter

Presenter:处理来自View与Model的请求

public class DemoPresenter<V extends IDemoView> implements IBasePresenter<V> {
    private V mView;
    ……
    public int refreshData() {
        mModel.getDataList(new Subscriber<ArrayList<DemoEntity>>() {
            @Override
            public void onCompleted() {
            }
            @Override
            public void onError(Throwable e) {
            }
            @Override
            public void onNext(ArrayList<DemoEntity> listDemoEntiry) {
                getAttacchView().refreshData(listDemoEntiry);
            }
        },0);
        return 0;
    }
    public void loadMoreData(int mLoadTimes){
        mModel.getDataList(new Subscriber<ArrayList<DemoEntity>>() {
            @Override
            public void onCompleted() {
            }
            @Override
            public void onError(Throwable e) {
            }
            @Override
            public void onNext(ArrayList<DemoEntity> 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<ArrayList<DemoEntity>> 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<ArrayList<DemoEntity>> 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<SaarsBean>() {
                        @Override
                        public void onCompleted() {
    
                        }
    
                        @Override
                        public void onError(Throwable e) {
                            Log.e("TAG", e.getMessage());
                        }
    
                        @Override
                        public void onNext(SaarsBean saarsBean) {
                            List<SaarsBean.RecBean> 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又是什么鬼?

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> T create(Class<T> 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<SaarsBean> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy阅读 5,442评论 7 62
  • 项目启发来自谷歌的同类框架项目 android-architecture利用一个相同的项目,使用不同的框架实现之...
    boredream阅读 4,191评论 9 67
  • 接近尾声的时间 午夜的时分 请你安静下来 我要为她 写一首 风华正茂的诗 那片星空下 照耀着 我衷心的祝福 请你闭...
    班乐阅读 554评论 2 5
  • 本文包括:1、Filter简介2、Filter是如何实现拦截的?3、Filter开发入门4、Filter的生命周期...
    廖少少阅读 7,235评论 3 56