一文说透 Android 应用架构 MVC、MVP、MVVM 和 组件化

}
}

OK,上面我们已经完成了Model的定义和View及Presenter的规范的定义。下面,我们就需要具体去实现View和Presenter。

首先是Presenter,下面是我们的HomePresenter的定义。在下面的代码中,为了更加清晰地展示其中的逻辑,我删减了一部分无关代码:

public class HomePresenter implements HomeContract.IPresenter {

private HomeContract.IView view;

private HomeModel homeModel;

private String nextPageUrl;

// 传入View并实例化Model
public HomePresenter(HomeContract.IView view) {
this.view = view;
homeModel = new HomeModel();
}

// 使用Model请求数据,并在得到请求结果的时候调用View的方法进行回调
@Override
public void requestFirstPage() {
Disposable disposable = homeModel.getFirstHomeData()
// …
.subscribe(itemLists -> { view.setFirstPage(itemLists); },
throwable -> { view.onError(throwable.toString()); });
}

// 使用Model请求数据,并在得到请求结果的时候调用View的方法进行回调
@Override
public void requestNextPage() {
Disposable disposable = homeModel.getMoreHomeData(nextPageUrl)
// …
.subscribe(itemLists -> { view.setFirstPage(itemLists); },
throwable -> { view.onError(throwable.toString()); });
}
}

从上面我们可以看出,在Presenter需要将View和Model建立联系。我们需要在初始化的时候传入View,并实例化一个Model。Presenter通过Model获取数据,并在拿到数据的时候,通过View的方法通知给View层。

然后,就是我们的View层的代码,同样,我对代码做了删减:

@Route(path = BaseConstants.EYEPETIZER_MENU)
public class HomeActivity extends CommonActivity implements HomeContract.IView {

// 实例化Presenter
private HomeContract.IPresenter presenter;
{
presenter = new HomePresenter(this);
}

@Override
protected int getLayoutResId() {
return R.layout.activity_eyepetizer_menu;
}

@Override
protected void doCreateView(Bundle savedInstanceState) {
// …
// 使用Presenter请求数据
presenter.requestFirstPage();
loading = true;
}

private void configList() {
// …
getBinding().rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
// 请求下一页的数据
presenter.requestNextPage();
}
}
});
}

// 当请求到结果的时候在页面上做处理,展示到页面上
@Override
public void setFirstPage(List<HomeBean.IssueList.ItemList> itemLists) {
loading = false;
homeAdapter.addData(itemLists);
}

// 当请求到结果的时候在页面上做处理,展示到页面上
@Override
public void setNextPage(List<HomeBean.IssueList.ItemList> itemLists) {
loading = false;
homeAdapter.addData(itemLists);
}

@Override
public void onError(String msg) {
ToastUtils.makeToast(msg);
}

// …
}

从上面的代码中我们可以看出实际在View中也要维护一个Presenter的实例。 当需要请求数据的时候会使用该实例的方法来请求数据,所以,在开发的时候,我们需要根据请求数据的情况,在Presenter中定义接口方法。

实际上,MVP的原理就是View通过Presenter获取数据,获取到数据之后再回调View的方法来展示数据。

2.3 MVC 和 MVP 的区别

  1. MVC 中是允许 Model 和 View 进行交互的,而MVP中,Model 与 View 之间的交互由Presenter完成;
  2. MVP 模式就是将 P 定义成一个接口,然后在每个触发的事件中调用接口的方法来处理,也就是将逻辑放进了 P 中,需要执行某些操作的时候调用 P 的方法就行了。

2.4 MVP的优缺点

优点:

  1. 降低耦合度,实现了 Model 和 View 真正的完全分离,可以修改 View 而不影响 Modle;
  2. 模块职责划分明显,层次清晰;
  3. 隐藏数据;
  4. Presenter 可以复用,一个 Presenter 可以用于多个 View,而不需要更改 Presenter 的逻辑;
  5. 利于测试驱动开发,以前的Android开发是难以进行单元测试的;
  6. View 可以进行组件化,在MVP当中,View 不依赖 Model。

缺点:

  1. Presenter 中除了应用逻辑以外,还有大量的 View->Model,Model->View 的手动同步逻辑,造成 Presenter 比较笨重,维护起来会比较困难;
  2. 由于对视图的渲染放在了 Presenter 中,所以视图和 Presenter 的交互会过于频繁;
  3. 如果 Presenter 过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密,一旦视图需要变更,那么Presenter也需要变更了。

3、MVVM (分手大师)

3.1 基础概念

MVVM 是 Model-View-ViewModel 的简写。它本质上就是 MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

  • 模型层 (Model):负责从各种数据源中获取数据;
  • 视图层 (View):在 Android 中对应于 Activity 和 Fragment,用于展示给用户和处理用户交互,会驱动 ViewModel 从 Model 中获取数据;
  • ViewModel 层:用于将 Model 和 View 进行关联,我们可以在 View 中通过 ViewModel 从 Model 中获取数据;当获取到了数据之后,会通过自动绑定,比如 DataBinding,来将结果自动刷新到界面上。

使用 Google 官方的 Android Architecture Components ,我们可以很容易地将 MVVM 应用到我们的应用中。下面,我们就使用它来展示一下 MVVM 的实际的应用。你可以在Github中获取到它的源代码。

3.2 示例程序

在该项目中,我们使用了:

  1. 果壳网的 API 作为数据源;
  2. 使用 Retrofit 进行网络数据访问;
  3. 使用 ViewMdeol 作为整体的架构设计。

该项目的包结构如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里的model.data下面的类是对应于网络的数据实体的,由JSON自动生成,这里我们不进行详细描述。这里的model.repository下面的两个类是用来从网络中获取数据信息的,我们也忽略它的定义。

上面就是我们的 Model 的定义,并没有太多的内容,基本与 MVP 一致。

下面的是 ViewModel 的代码,我们选择了其中的一个方法来进行说明。当我们定义 ViewModel 的时候,需要继承 ViewModel 类。

public class GuokrViewModel extends ViewModel {

public LiveData<Resource> getGuokrNews(int offset, int limit) {
MutableLiveData<Resource> result = new MutableLiveData<>();
GuokrRetrofit.getGuokrService().getNews(offset, limit)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onError(Throwable e) {
result.setValue(Resource.error(e.getMessage(), null));
}

@Override
public void onComplete() { }

@Override
public void onSubscribe(Disposable d) { }

@Override
public void onNext(GuokrNews guokrNews) {
result.setValue(Resource.success(guokrNews));
}
});
return result;
}
}

这里的 ViewModel 来自 android.arch.lifecycle.ViewModel,所以,为了使用它,我们还需要加入下面的依赖:

api “android.arch.lifecycle:runtime: a r c h V e r s i o n " a p i " a n d r o i d . a r c h . l i f e c y c l e : e x t e n s i o n s : archVersion" api "android.arch.lifecycle:extensions: archVersion"api"android.arch.lifecycle:extensions:archVersion”
annotationProcessor “android.arch.lifecycle:compiler:$archVersion”

在 ViewModel 的定义中,我们直接使用 Retrofit 来从网络中获取数据。然后当获取到数据的时候,我们使用 LiveData 的方法把数据封装成一个对象返回给 View 层。在 View 层,我们只需要调用该方法,并对返回的 LiveData 进行"监听"即可。这里,我们将错误信息和返回的数据信息进行了封装,并且封装了一个代表当前状态的枚举信息,你可以参考源代码来详细了解下这些内容。

上面我们定义完了 Model 和 ViewModel,下面我们看下 View 层的定义,以及在 View 层中该如何使用 ViewModel。

@Route(path = BaseConstants.GUOKR_NEWS_LIST)
public class NewsListFragment extends CommonFragment {

private GuokrViewModel guokrViewModel;

private int offset = 0;

private final int limit = 20;

private GuokrNewsAdapter adapter;

@Override
protected int getLayoutResId() {
return R.layout.fragment_news_list;
}

@Override
protected void doCreateView(Bundle savedInstanceState) {
// …

guokrViewModel = ViewModelProviders.of(this).get(GuokrViewModel.class);

fetchNews();
}

private void fetchNews() {
guokrViewModel.getGuokrNews(offset, limit).observe(this, guokrNewsResource -> {
if (guokrNewsResource == null) {
return;
}
switch (guokrNewsResource.status) {
case FAILED:
ToastUtils.makeToast(guokrNewsResource.message);
break;
case SUCCESS:
adapter.addData(guokrNewsResource.data.getResult());
adapter.notifyDataSetChanged();
break;
}
});
}
}

以上就是我们的 View 层的定义,这里我们先使用了

这里的view.fragment包下面的类对应于实际的页面,这里我们 ViewModelProviders 的方法来获取我们需要使用的 ViewModel,然后,我们直接使用该 ViewModel 的方法获取数据,并对返回的结果进行“监听”即可。

以上就是 MVVM 的基本使用,当然,这里我们并没有使用 DataBinding 直接与返回的列表信息进行绑定,它被更多的用在了整个 Fragment 的布局中。

3.3 MVVM 的优点和缺点

MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点:

  1. 低耦合:视图(View)可以独立于Model变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
  2. 可重用性:你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 view 重用这段视图逻辑。
  3. 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
  4. 可测试:界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。

4、组件化

4.1 基础概念

所谓的组件化,通俗理解就是将一个工程分成各个模块,各个模块之间相互解耦,可以独立开发并编译成一个独立的 APP 进行调试,然后又可以将各个模块组合起来整体构成一个完整的 APP。它的好处是当工程比较大的时候,便于各个开发者之间分工协作、同步开发;被分割出来的模块又可以在项目之间共享,从而达到复用的目的。组件化有诸多好处,尤其适用于比较大型的项目。

简单了解了组件化之后,让我们来看一下如何实现组件化开发。你可能之前听说过组件化开发,或者被其高大上的称谓吓到了,但它实际应用起来并不复杂,至少借助了现成的框架之后并不复杂。这里我们先梳理一下,在应用组件化的时候需要解决哪些问题:

  1. 如何分成各个模块? 我们可以根据业务来进行拆分,对于比较大的功能模块可以作为应用的一个模块来使用,但是也应该注意,划分出来的模块不要过多,否则可能会降低编译的速度并且增加维护的难度。
  2. 各个模块之间如何进行数据共享和数据通信? 我们可以把需要共享的数据划分成一个单独的模块来放置公共数据。各个模块之间的数据通信,我们可以使用阿里的 ARouter 进行页面的跳转,使用封装之后的 RxJava 作为 EventBus 进行全局的数据通信。
  3. 如何将各个模块打包成一个独立的 APP 进行调试? 首先这个要建立在2的基础上,然后,我们可以在各个模块的 gradle 文件里面配置需要加载的 AndroidManifest.xml 文件,并可以为每个应用配置一个独立的 Application 和启动类。
  4. 如何防止资源名冲突问题? 遵守命名规约就能规避资源名冲突问题。
  5. 如何解决 library 重复依赖以及 sdk 和依赖的第三方版本号控制问题? 可以将各个模块公用的依赖的版本配置到 settings.gradle 里面,并且可以建立一个公共的模块来配置所需要的各种依赖。

Talk is cheap,下面让我们动手实践来应用组件化进行开发。你可以在Github中获取到它的源代码。

4.2 组件化实践

包结构

首先,我们先来看整个应用的包的结构。如下图所示,该模块的划分是根据各个模块的功能来决定的。图的右侧白色的部分是各个模块的文件路径,我推荐使用这种方式,而不是将各个模块放置在 app 下面,因为这样看起来更加的清晰。为了达到这个目的,你只需要按照下面的方式在 settings.gralde 里面配置一下各个模块的路径即可。注意在实际应用的时候模块的路径的关系,不要搞错了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后,我们介绍一下这里的 commons 模块。它用来存放公共的资源和一些依赖,这里我们将两者放在了一个模块中以减少模块的数量。下面是它的 gradle 的部分配置。这里我们使用了 api 来引入各个依赖,以便在其他的模块中也能使用这些依赖。

dependencies {
api fileTree(include: [‘*.jar’], dir: ‘libs’)
// …
// router
api ‘com.alibaba:arouter-api:1.3.1’
annotationProcessor ‘com.alibaba:arouter-compiler:1.1.4’
// walle
api ‘com.meituan.android.walle:library:1.1.6’
// umeng
api ‘com.umeng.sdk:common:1.5.3’
api ‘com.umeng.sdk:analytics:7.5.3’
api files(‘libs/pldroid-player-1.5.0.jar’)
}

路由

接着,我们来看一下路由框架的配置。这里,我们使用阿里的 ARouter 来进行页面之间的跳转,你可以在Github上面了解该框架的配置和使用方式。这里我们只讲解一下在组件化开发的时候需要注意的地方。注意到 ARouter 是通过注解来进行页面配置的,并且它的注解是在编译的时候进行处理的。所以,我们需要引入arouter-compiler来使用它的编译时处理功能。需要注意的地方是,我们只要在公共的模块中加入arouter-api就可以使用ARouter的API了,但是需要在每个模块中引入arouter-compiler才能使用编译时注解。也就是说,我们需要在每个模块中都加入arouter-compiler依赖。

模块独立

最后

以前一直是自己在网上东平西凑的找,找到的东西也是零零散散,很多时候都是看着看着就没了,时间浪费了,问题却还没得到解决,很让人抓狂。

后面我就自己整理了一套资料,还别说,真香!

资料有条理,有系统,还很全面,我不方便直接放出来,大家可以先看看有没有用得到的地方吧。

系列教程图片

2020Android复习资料汇总.png

flutter

NDK

设计思想开源框架

微信小程序
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
-1715166564060)]

[外链图片转存中…(img-V2xIfYz3-1715166564060)]

[外链图片转存中…(img-tDtDvFDa-1715166564061)]

[外链图片转存中…(img-uKIjZ7zb-1715166564062)]

[外链图片转存中…(img-WjOhBFPu-1715166564063)]

[外链图片转存中…(img-t29gOG55-1715166564063)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值