谈谈我对 MVP 的理解
说实话,MVP 这种模式或者说设计思想也已经出来很久了,现在最新的使用的是 MVVM 设计模式,不断对于萌新来说,还是需要一步一步的向前走。毕竟,人不能一口吃成一个大胖子是吧。
本人大四,目前正在实习,其实接触研究 MVP 也有很久了,期间找过网上的老哥们帮忙写过 MVPDemo,也从 GitHub 上看过很多项目源码。也看过他们的设计思想等。不过一直对于 MVP 中的实现引用啊,数据怎么传递过去的很迷。也看过 Google 的官方 Demo,看完后感觉还不如其他的简单 Demo 容易明白。所以我这次结合自己写的小 Demo 和理解,完整的讲述下我眼中的 MVP。希望能帮到大家,如果有什么错误还希望各位大佬多多指教。
文末附有源码
话不多说,这个图是我盗来的,,,
其实对于 MVP 的好处我也不多和大家说了,说来说去也就那么几点:
1,松耦合
2,方便单元测试
3,代码整洁,容易修改
4,等等。。
先给大家展示下我的项目目录结构:
ApiService 目录:里面放了请求接口的方法
package com.example.root.mvp_demo.ApiInterface; import com.example.root.mvp_demo.bean.ArticleData; import io.reactivex.Observable; import retrofit2.http.GET; import retrofit2.http.Path; /** * author:Jiwenjie * email:Jiwenjie97@gmail.com * time:2018/10/21 * desc: 有关 Retrofit 和 RxJava 结合的功能接口 * version:1.0 */ public interface ApiService { @GET("/article/list/{page}/json") Observable<ArticleData> getArticle(@Path("page") int page); }
这里大家应该都可以理解,因为我请求网络使用的是 RxJava + Retrofit,所以这里的开头是 Observable,如果只是单纯的 Retrofit 的话那就改成 Call 就可以了,其他不必变化。
tips:这里有一个注意点就是 @Path 和 @Query 的区别。前者是在 URL 中间添加参数,上面代码有表示,需要用 @Path 代替的参数在链接中使用 { } 包裹起来,注意名称需要对应; 而后者是属于拼接参数,即 URL 输入完成后,在最后通过 ?和 & 连接起来的参数。相信我说的很明白了,不能明白的话就去看下你们经常写的 URL 格式你也就会明白。
base 目录:这里存放了 BaseView 和 BasePresenter,这是大家的习惯啦,我正常会把 BaseActivity,BaseFragment 等都放在 Base 目录下
package com.example.root.mvp_demo.base; /** * author:Jiwenjie * email:Jiwenjie97@gmail.com * time:2018/10/21 * desc: * version:1.0 */ public interface BasePresenter { void start(); void destroy(); }
BaseView
package com.example.root.mvp_demo.base; /** * author:Jiwenjie * email:Jiwenjie97@gmail.com * time:2018/10/21 * desc: * version:1.0 */ public interface BaseView { }
我这里给 BaseView 定义了空实现,给 Presenter 定义了 onStart 和 onDestroy 方法。这些不用多说,相信大家都能够看懂。
bean 目录:存放实体类
contact 目录:契约目录,这里存放契约类,一般 APP 的一个界面就是一个契约类,这也是 Google 的推荐写法
package com.example.root.mvp_demo.contact; import com.example.root.mvp_demo.base.BasePresenter; import com.example.root.mvp_demo.base.BaseView; import com.example.root.mvp_demo.bean.ArticleData; /** * author:Jiwenjie * email:Jiwenjie97@gmail.com * time:2018/10/21 * desc:首页文章的契约类 * version:1.0 */ public class ArtContact { public interface ArtView extends BaseView { void setData(ArticleData data); } public interface ArtPresenter extends BasePresenter { void requestData(); } }
顾名思义,就像签订契约,哪一个 View 和 哪一个 Presenter 相对应起来。注意 View 很重要,它是纽带。在具体使用的时候就需要 Activity 或者 Fragment 实现该 View,然后把参数传递给 Presenter。在Presenter 中做操作。
utils 目录:再看 Model 目录前先看 utils 目录,这里封装了网络请求方法
package com.example.root.mvp_demo.utils; import com.example.root.mvp_demo.ApiInterface.ApiService; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.CallAdapter; import retrofit2.Converter; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; /** * author:Jiwenjie * email:Jiwenjie97@gmail.com * time:2018/10/21 * desc: 有关网络请求的 Util * version:1.0 */ public class RetrofitManager { private static ApiService apiService; private static OkHttpClient okHttpClient; private static Converter.Factory gsonConverterFactory = GsonConverterFactory.create(); private static CallAdapter.Factory rxJaveCallAdapterFactory = RxJava2CallAdapterFactory.create(); private static HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); private static OkHttpClient getClient() { // 可以设置请求过滤的水平,body,basic,headers interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return okHttpClient = new OkHttpClient.Builder() .addInterceptor(interceptor) // 日志,所有请求响应都看到 .connectTimeout(60L, TimeUnit.SECONDS) .readTimeout(60L, TimeUnit.SECONDS) .writeTimeout(60L, TimeUnit.SECONDS) .build(); } public static ApiService getApiService() { if (apiService == null) { Retrofit retrofit = new Retrofit.Builder() .client(getClient()) .baseUrl("http://www.wanandroid.com/") .addConverterFactory(gsonConverterFactory) .addCallAdapterFactory(rxJaveCallAdapterFactory) .build(); apiService = retrofit.create(ApiService.class); } return apiService; } }
相信对于 Retrofit 有些了解的童鞋都知道其实 Retrofit 的实现就是 OkHttp 外面包裹了一层能够和 RxJava 连接的部分而已,所以这里的 Client 使用的是 okHttpClient。接口使用的是鸿洋大神的 玩 Android 接口。话说鸿洋老哥去头条工作了,,,大写的羡慕。都知道头条待遇好要求高。
大家可以根据我写的在做扩展。PS:其实这里有关获取 APiService 就可以修改扩展,使用 泛型和 Class 来处理代替。这样在 Model 调用的时候直接传入参数就可以。如果照我这样写,每个接口都要实现一个方法太多重复了。
Model 目录:存放 Model 用来具体操作数据的部分
package com.example.root.mvp_demo.model; import com.example.root.mvp_demo.bean.ArticleData; import com.example.root.mvp_demo.utils.RetrofitManager; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; /** * author:Jiwenjie * email:Jiwenjie97@gmail.com * time:2018/10/21 * desc: MVP 中的 model * version:1.0 */ public class ArticleModel { // 获取首页技术文章的 model public Observable<ArticleData> getArtData(int page) { return RetrofitManager.getApiService().getArticle(page) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } }
其实不论在 MVP 或者 MVC 模式中,Model 都是在操作数据的部分,这一点万年不变。MVVM 我还没看,不太了解,只知道好像是增加了一个 ViewModel,所以应该也是一样。
这上面从 22 行到 24 行代码是 RxJava 的功能体现,链式调用,可以说是牛逼了。他的意思是被观察者获取数据请求网络的时候在 IO 线程,所以被观察者解绑的时候也在 IO 线程。而观察者运行在 UI 线程。这里就体现了线程一行代码切换的强大。随便切,且不过来算我输,,,
如果对于这些不了解的童鞋可以去网上搜一搜,
这篇文章写的很好。
Presenter 目录:存放具体的 Prsenter 实现
大家都知道在 Activity 或者 Fragment 中需要持有 Presenter 的引用,在 Presenter 中对数据和 View 进行操作,所以我们还需要定义一个类来实现 Presenter 接口。
package com.example.root.mvp_demo.presenter; import android.content.Context; import android.util.Log; import android.widget.Toast; import com.example.root.mvp_demo.model.ArticleModel; import com.example.root.mvp_demo.bean.ArticleData; import com.example.root.mvp_demo.contact.ArtContact; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; /** * author:Jiwenjie * email:Jiwenjie97@gmail.com * time:2018/10/21 * desc: 实际的 Presenter,在这里需要持有 View 和 Model 的引用, * 通过 model 获取数据,把获取的数据显示在 Model 中 * version:1.0 */ public class Presenter implements ArtContact.ArtPresenter { private Context mContext; private ArticleModel mModel; private ArtContact.ArtView mView; // 防止内存泄漏部分 protected Disposable disposable; // 初始化部分 public Presenter(Context context, ArtContact.ArtView view) { this.mContext = context; this.mView = view; mModel = new ArticleModel(); } @Override public void requestData() { destroy(); disposable = mModel.getArtData(0) .subscribe(new Consumer<ArticleData>() { @Override public void accept(ArticleData articleData) throws Exception { Toast.makeText(mContext, "数据获取成功", Toast.LENGTH_SHORT).show(); mView.setData(articleData); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { Toast.makeText(mContext, "数据获取失败", Toast.LENGTH_SHORT).show(); } }); } @Override public void start() { Log.i("Presenter", "开始获取数据"); } @Override public void destroy() { if (disposable != null && !disposable.isDisposed()) { disposable.dispose(); } } }
这里我都写了注释,相信大家都能看懂,如果不明白的话可以私信或者 Google,如果实在不行的话,,,百度也行。注意这里我传递参数是 0,固定写死的,大家实际使用的时候需要修改一下,改成变量的形式就 OK 了。
MainActivity
重点来了,重点来了,前面说的再多都是铺垫,还要具体使用才行。先看布局,没啥好说,一个 TextView 和一个 Button。
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/tv_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="20dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/btn_start" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="开始" android:textSize="18dp" app:layout_constraintTop_toBottomOf="@id/tv_content" /> </android.support.constraint.ConstraintLayout>
这里给大家推荐下约束布局,真的很好用,而且上网搜一搜就能会。真的是嵌套好几层的页面他就一层搞定。对于优化性能有要求的话强烈推荐。不过没要求的话也就算了,因为写起来还有有些费事的,起码 Id 你就要想好多不重复的才行。
MainActivity 源码
package com.example.root.mvp_demo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.example.root.mvp_demo.bean.ArticleData; import com.example.root.mvp_demo.contact.ArtContact; import com.example.root.mvp_demo.presenter.Presenter; public class MainActivity extends AppCompatActivity implements ArtContact.ArtView { private Button btn_start; private TextView tv_content; private Presenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initEvent(); } private void initView() { btn_start = findViewById(R.id.btn_start); tv_content = findViewById(R.id.tv_content); presenter = new Presenter(getApplicationContext(), this); } private void initEvent() { btn_start.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.start(); presenter.requestData(); } }); } @Override public void setData(ArticleData data) { tv_content.setText(data.getData().getDatas().toString()); } @Override protected void onDestroy() { super.onDestroy(); presenter.destroy(); } }
实现了契约类中 View 的接口,重写了方法,把持了 Presenter 的引用。初始化 Presenter 的时候把 View
作为参数传递了过去。大致流程就是这样,也许真的是会了不难吧。我一开始总是不明白,现在怎么想都明白。。。希望这篇文章能帮到大家:
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
造了四个轮子之后,我们上路跑一跑
一、前言 最近相继发布了四个开源项目,都是比较轻量的项目。造完轮子,很自然的想到要弄个App来跑一下。在搬APP出来之前,先介绍下这几个轮子吧。 二、轮子 昨晚我在秋名山输给一辆五菱宏光, 他用惯性飘移过弯,他的车很快,我只看到他有个修楼房漏水的招牌。 如果知道他是谁的话,麻烦你们跟他说一声:礼拜六晚,我会在秋名山等他。 秋明山车神之所以开得快,不单是因为技术好,车神老爸对车子的调优也很重要,比如给车配了好轮子……编不下去了 -_- LightKV 项目地址:LightKV高性能key-value存储组件,相比SharedPreferences有全方位的改进。支持Kotlin属性委托,在Kotlin环境下使用,可以像读写变量一样读写配置。推荐指数: LightEvent 项目地址:LightEvent轻量级事件通信方案(类似于EventBus的作用),不到50行代码。简单到极致,使用也很方便。推荐指数: Task 项目地址:Task实用的线程调度框架,针对App开发的使用场景做了适用性封装。支持优先级,适配生命周期,支持任务分组,任务去重;可直接执行任务,可用于RxJava, 可替换A...
- 下一篇
Android&Java面试题大全—金九银十面试必备
声明本文由作者:Man不经心授权转载,转载请联系原文作者原文链接:https://www.jianshu.com/p/375ad14096b3, 类加载过程 Java 中类加载分为 3 个步骤:加载、链接、初始化。 加载。 加载是将字节码数据从不同的数据源读取到JVM内存,并映射为 JVM 认可的数据结构,也就是 Class 对象的过程。数据源可以是 Jar 文件、Class 文件等等。如果数据的格式并不是 ClassFile 的结构,则会报 ClassFormatError。 链接。 链接是类加载的核心部分,这一步分为 3 个步骤:验证、准备、解析。 验证。 验证是保证JVM安全的重要步骤。JVM需要校验字节信息是否符合规范,避免恶意信息和不规范数据危害JVM运行安全。如果验证出错,则会报VerifyError。 准备。 这一步会创建静态变量,并为静态变量开辟内存空间。 解析。 这一步会将符号引用替换为直接引用。 初始化。 初始化会为静态变量赋值,并执行静态代码块中的逻辑。 双亲委派模型 类加载器大致分为3类:启动类加载器、扩展类加载器、应用程序类加载器。 启动类加载器主要加载 jr...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主