MVC、MVP和MVVM是常见的三种架构设计模式,当前MVP和MVVM的使用相对比较普遍,固然MVC也并无过期之说。而所谓的组件化就是指将应用根据业务需求划分红各个模块来进行开发,每一个模块又能够编译成独立的APP进行开发。理论上讲,组件化和前面三种架构设计不是一个层次的。它们之间的关系是,组件化的各个组件可使用前面三种架构设计。咱们只有了解了这些架构设计的特色以后,才能在进行开发的时候选择适合本身项目的架构模式,这也是本文的目的。java
MVC (Model-View-Controller, 模型-视图-控制器),标准的MVC是这个样子的:android
Activity并不是标准的Controller,它一方面用来控制了布局,另外一方面还要在Activity中写业务代码,形成了Activity既像View又像Controller。git
在Android开发中,就是指直接使用Activity并在其中写业务逻辑的开发方式。显然,一方面Activity自己就是一个视图,另外一方面又要负责处理业务逻辑,所以逻辑会比较混乱。github
这种开发方式不太适合Android开发。设计模式
MVP (Model-View-Presenter) 是MVC的演化版本,几个主要部分以下:api
因此,对于MVP的架构设计,咱们有如下几点须要说明:bash
为了说明MVP设计模式,咱们给出一个示例程序。你能够在Github中获取到它的源代码。网络
在该示例中,咱们使用了:架构
下面是该模块的基本的包结构:app
这里核心的代码是MVP部分。
这里咱们首先定义了MVP模式中的最顶层的View和Presenter,在这里分别是BaseView
和BasePresenter
,它们在该项目中是两个空的接口,在一些项目中,咱们能够根据本身的需求在这两个接口中添加本身须要的方法。
而后,咱们定义了HomeContract
。它是一个抽象的接口,至关于一层协议,用来规定指定的功能的View和Presenter分别应该具备哪些方法。一般,对于不一样的功能,咱们须要分别实现一个MVP,每一个MVP都会又一个对应的Contract。笔者认为它的好处在于,将指定的View和Presenter的接口定义在一个接口中,更加集中。它们各自须要实现的方法也一目了然地展示在了咱们面前。
这里根据咱们的业务场景,该接口的定义以下:
public interface HomeContract {
interface IView extends BaseView {
void setFirstPage(List<HomeBean.IssueList.ItemList> itemLists);
void setNextPage(List<HomeBean.IssueList.ItemList> itemLists);
void onError(String msg);
}
interface IPresenter extends BasePresenter {
void requestFirstPage();
void requestNextPage();
}
}
复制代码
HomeContract
用来规定View和Presenter应该具备的操做,在这里它用来指定主页的View和Presenter的方法。从上面咱们也能够看出,这里的IView
和IPresenter
分别实现了BaseView
和BasePresenter
。
上面,咱们定义了V和P的规范,MVP中还有一项Model,它用来从网络中获取数据。这里咱们省去网络相关的具体的代码,你只须要知道APIRetrofit.getEyepetizerService()
是用来获取Retrofit对应的Service,而getMoreHomeData()
和getFirstHomeData()
是用来从指定的接口中获取数据就行。下面是HomeModel
的定义:
public class HomeModel {
public Observable<HomeBean> getFirstHomeData() {
return APIRetrofit.getEyepetizerService().getFirstHomeData(System.currentTimeMillis());
}
public Observable<HomeBean> getMoreHomeData(String url) {
return APIRetrofit.getEyepetizerService().getMoreHomeData(url);
}
}
复制代码
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<ActivityEyepetizerMenuBinding> 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的方法来展现数据。
优势:
缺点:
MVVM 是 Model-View-ViewModel 的简写。它本质上就是 MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化,让咱们将视图 UI 和业务逻辑分开。
使用 Google 官方的 Android Architecture Components ,咱们能够很容易地将 MVVM 应用到咱们的应用中。下面,咱们就使用它来展现一下 MVVM 的实际的应用。你能够在Github中获取到它的源代码。
在该项目中,咱们使用了:
该项目的包结构以下图所示:
这里的model.data
下面的类是对应于网络的数据实体的,由JSON自动生成,这里咱们不进行详细描述。这里的model.repository
下面的两个类是用来从网络中获取数据信息的,咱们也忽略它的定义。
上面就是咱们的 Model 的定义,并无太多的内容,基本与 MVP 一致。
下面的是 ViewModel 的代码,咱们选择了其中的一个方法来进行说明。当咱们定义 ViewModel 的时候,须要继承 ViewModel 类。
public class GuokrViewModel extends ViewModel {
public LiveData<Resource<GuokrNews>> getGuokrNews(int offset, int limit) {
MutableLiveData<Resource<GuokrNews>> result = new MutableLiveData<>();
GuokrRetrofit.getGuokrService().getNews(offset, limit)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<GuokrNews>() {
@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:$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<FragmentNewsListBinding> {
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 的布局中。
MVVM模式和MVC模式同样,主要目的是分离视图(View)和模型(Model),有几大优势:
所谓的组件化,通俗理解就是将一个工程分红各个模块,各个模块之间相互解耦,能够独立开发并编译成一个独立的 APP 进行调试,而后又能够将各个模块组合起来总体构成一个完整的 APP。它的好处是当工程比较大的时候,便于各个开发者之间分工协做、同步开发;被分割出来的模块又能够在项目之间共享,从而达到复用的目的。组件化有诸多好处,尤为适用于比较大型的项目。
简单了解了组件化以后,让咱们来看一下如何实现组件化开发。你可能以前据说过组件化开发,或者被其高大上的称谓吓到了,但它实际应用起来并不复杂,至少借助了现成的框架以后并不复杂。这里咱们先梳理一下,在应用组件化的时候须要解决哪些问题:
Talk is cheap,下面让咱们动手实践来应用组件化进行开发。你能够在Github中获取到它的源代码。
首先,咱们先来看整个应用的包的结构。以下图所示,该模块的划分是根据各个模块的功能来决定的。图的右侧白色的部分是各个模块的文件路径,我推荐使用这种方式,而不是将各个模块放置在 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
依赖。
为了可以将各个模块编译成一个独立的 APP,咱们须要在 Gradle 里面作一些配置。
首先,咱们须要在gradle.properties
定义一些布尔类型的变量用来判断各个模块是做为一个 library 仍是 application 进行编译。这里个人配置以下面的代码所示。也就是,我为每一个模块都定义了这么一个布尔类型的变量,固然,你也能够只定义一个变量,而后在各个模块中使用同一个变量来进行判断。
isGuokrModuleApp=false
isLiveModuleApp=false
isLayoutModuleApp=false
isLibraryModuleApp=false
isEyepetizerModuleApp=false
复制代码
而后,咱们来看一下各个模块中的 gradle 该如何配置,这里咱们以开眼视频的功能模块做为例子来进行讲解。首先,一个模块做为 library 仍是 application 是根据引用的 plugin 来决定的,因此,咱们要根据以前定义的布尔变量来决定使用的 plugin:
if (isEyepetizerModuleApp.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
复制代码
假如咱们要将某个模块做为一个独立的 APP,那么启动类你确定须要配置。这就意味着你须要两个 AndroidManifest.xml 文件,一个用于 library 状态,一个用于 application 状态。因此,咱们能够在 main 目录下面再定义一个 AndroidManifest.xml,而后,咱们在该配置文件中不仅指定启动类,还使用咱们定义的 Application。指定 Application 有时候是必须的,好比你须要在各个模块里面初始化 ARouter 等等。这部分代码就不给出了,能够参考源码,这里咱们给出一下在 Gradle 里面指定 AndroidManifest.xml 的方式。
以下所示,咱们能够根据以前定义的布尔值来决定使用哪个配置文件:
sourceSets {
main {
jniLibs.srcDirs = ['libs']
if (isEyepetizerModuleApp.toBoolean()) {
manifest.srcFile "src/main/debug/AndroidManifest.xml"
} else {
manifest.srcFile "src/main/AndroidManifest.xml"
}
}
}
复制代码
此外,还须要注意的是,若是咱们但愿在每一个模块中都能应用 DataBinding 和 Java 8 的一些特性,那么你须要在每一个模块里面都加入下面的配置:
// use data binding
dataBinding {
enabled = true
}
// use java 8 language
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
复制代码
对于编译时注解之类的配置,咱们也须要在每一个模块里面都进行声明。
完成了以上的配置,咱们只要根据须要编译的类型,修改以前定义的布尔值,来决定是将该模块编译成 APP 仍是做为类库来使用便可。
以上就是组件化在 Android 开发当中的应用。
MVC、MVP和MVVM各有各自的特色,能够根据应用开发的须要选择适合本身的架构模式。组件化的目的就在于保持各个模块之间的独立从而便于分工协做。它们之间的关系就是,你能够在组件化的各个模块中应用前面三种架构模式的一种或者几种。