Android下MVP和MVVM模式的实践

转载注明出处:https://blog.csdn.net/skysukai

1、前言

MVP和MVVM诞生已经好些年头了,记得刚毕业才参加工作的时候,第一次见到了有上万行的Activity,这种巨无霸的Activity维护起来简直就是噩梦。这时候,就需要进行代码重构了。MVP和MVVM两种框架,将逻辑层、显示层、数据层分层处理,逻辑清楚。

2、MVC

在讲MVP、MVVM之前,要先说明MVC才能讲清楚这些架构。MVC架构诞生已有40余年,其典型定义如下:

3、MVP

3.1 MVP模式

有关MVP的介绍网上有很多,大家可以自行百度。这里贴一个图,能比较清楚地说明MVP模式内各个模块间的关系:

可以看到Presenter层处于MVP模式的中心地位,View层负责UI显示,Model层负责数据处理。

3.2 一个简单MVP的实践

首先给出首页界面的视觉效果图:

可以看到,这是一个标准的互联网应用。首页会请求数据加载到页面显示,比较适合使用MVP模式。

3.2.1 类定义

MVP编程是面向接口编程,相对来说,接口会比较多。Presenter层、View层、Model层分别定义各自的接口,给出首页界面的类定义:

java 复制代码
public interface IPreviewPresenter {
    void destory();
}
java 复制代码
public interface IPreviewView {
    void onLoadStart();

    void onLoadEnd();

    void onLoadFail(CaseListResult caseList);

    void onLoadMoreSuccess(CaseListResult caseList);

    void onRefreshStarted();

    void onRefreshSuccess(CaseListResult caseList);
}
java 复制代码
public interface IPreviewModel {
    void getData(int pageNum, RequestListener<CaseListResult> listener);
}

在以上各个接口中,需要预先考虑需要的业务,放在各自的接口中。

3.2.2 实现

在MVP模式中,通常View层是Activity/Fragment用以显示UI;Presenter层会同时持有View层、Model层的引用:

java 复制代码
public class PreviewPresenter implements IPreviewPresenter {

    private IPreviewView mView;
    private IPreviewModel mModel;

    public PreviewPresenter(IPreviewView view) {
        mView = view;
        mModel = new PreviewModel();
        mParam = new CaseListParam();
    }

    public void loadPreviewList(int pageNum) {
        mView.onLoadStart();
        mModel.getData(pageNum, new RequestListener<CaseListResult>() {
            @Override
            public void onSuccess(CaseListResult result) {
                mView.onLoadEnd();
                mView.onLoadMoreSuccess(result);
            }

            @Override
            public void onFailure(CaseListResult result) {
                mView.onLoadEnd();
                mView.onLoadFail(result);
            }
        });
    }

    @Override
    public void destory() {
		......
    }
  ......
}
java 复制代码
public class PreviewFragment extends UltrasoundBaseFragment
        implements IPreviewView {
	private PreviewPresenter mPresenter;
    private PreviewListAdapter mAdapter;

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
		......
        initAdapter();
    }

    private void initAdapter() {
        mRefreshLayout.setRefreshing(false);
        mAdapter = new PreviewListAdapter(R.layout.item_preview, null);

        mPreviewList.setAdapter(mAdapter);
		......
    }

    @Override
    public void onResume() {
        super.onResume();
        reloadData();
    }

    private void reloadData() {
        ......
        mPageNum = 1;
        mPresenter.loadPreviewList(mPageNum);
    }

    @Override
    public void onRefreshStarted() {
		......
    }

    @Override
    public void onRefreshSuccess(CaseListResult caseList) {
        ......
    }

    @Override
    public void onLoadStart() {
        ......
    }

    @Override
    public void onLoadEnd() {
        ......
    }

    @Override
    public void onLoadMoreSuccess(CaseListResult caseList) {
        ......
        if (count == 0) {
            View emptyView = LayoutInflater.from(getContext())
                    .inflate(R.layout.empty_view, null);
            mAdapter.setEmptyView(emptyView);

            return;
        }

        if (caseList.result.pageNum * caseList.result.pageSize
                >= caseList.result.count) {
            //数据全部加载完毕
            mAdapter.loadMoreEnd();
        } else {
            mAdapter.loadMoreComplete();
        }
    }

    @Override
    public void onLoadFail(CaseListResult caseList) {
        if (caseList.result == null) {
            View errorView = LayoutInflater.from(getContext())
                    .inflate(R.layout.error_view, null);
            mAdapter.setEmptyView(errorView);

            return;
        } else {
            mAdapter.loadMoreFail();
        }
    }

}
java 复制代码
public class PreviewModel implements IPreviewModel {

    @Override
    public void getData(int pageNum, RequestListener<CaseListResult> listener) {
        RequestHelper.getPreviewList(pageNum, listener);
    }
}

以上,一个MVP模式的首界面代码就实现了。

3.3 略微复杂的MVP模式

还是首先给出设计稿:

要达到的目标是在插入U盘时,扫描U盘下的视频文件然后列表展示。当然这只是视频列表页面,还有图片列表、音乐列表界面。随着业务复杂程度的增加,代码行数也成线性倍数增加。

3.3.1 UML类图

这个项目的代码复杂度高了很多,给出UML类图,代码就不展示了。

刚才已经提到,代码有三套相互独立的业务。那当然可以抽象出统一的部分:BaseFragment<V, P extends BasePresenter>、BasePresenter。BaseFragment用于存放三套业务都涉及的诸如初始化操作等:createPresenter()、initViews()等;BasePresenter用于持有view层等。处理完公共部分就是各自业务了。

在MVP模式中,处于核心地位的依然是presenter:

VideoListPresenter<V extends IVideoListView>这个presenter没有直接持有view,由统一抽象的BasePresenter通过弱引用持有了view层,这样就间接持有了view层;而model层则是直接被presenter层持有。

3.4 总结

在MVP模式中,presenter层持有view层和model层的引用,presenter层始终处于核心地位。如果model层要需要和presenter层通信,可以由回调接口来实现。

4、MVVM

4.1 MVVM框架

和MVP模式一样,先给出MVVM的框架图:

把MVVM称为框架是因为谷歌公司做了许多适配的工作,开发者只需要在既定的框架下实现功能即可。从框架图我们可以看到,MVVM和MVP的最大区别就是View层和ViewModel层之间变成了一个双向的箭头,不是两个单向的箭头。在MVP模式中,Presenter层会同时持有View层和Model的引用;而在MVVM框架里面,ViewModel不需要持有View层的引用,发生变化之后,View层会自动更新界面。

4.2 早期的MVVM示例

还是先给出实现效果。在说MVVM之前,就不得不提一提databinding。databinding是一种工具,是MVVM的具体实现。这里默认读者已经了解databinding。使用databinding,需要在xml布局文件里定义好需要绑定的数据及控件:

xml 复制代码
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="viewmodel"
            type="com.myapp.data.ViewModel" />
    </data>
    <ConstraintLayout... /> <!-- UI layout's root element -->
</layout>

定义好布局绑定之后,还需要在UI界面里定义定义适配器,通过编译之后才能确定view和viewmodel的绑定关系:

java 复制代码
@BindingAdapter("app:goneUnless")
public static void goneUnless(View view, Boolean visible) {
    view.visibility = visible ? View.VISIBLE : View.GONE;
}

4.3 使用Jetpack里的ViewModel

随着时间的推移,databinding已经逐渐被弃用,谷歌推出了ViewModel来取代databinding。有关ViewModel的描述可以参考官网。还是首先给出设计稿:

4.3.1 UML类图

4.3.2 准备工作

建立View层和ViewModel层的对应关系:

java 复制代码
public static CloudMusicHomeRecommendViewModel getInstance(ViewModelStoreOwner owner) {
        return new ViewModelProvider(owner).get(CloudMusicHomeRecommendViewModel.class);
    }

4.3.3 实现细节

以每日推荐页签时序图进行说明。进入页面后,首先请求数据:

java 复制代码
mRecommendCloudMusicViewModel.getDailyRecommendLiveData().observe(getViewLifecycleOwner(), dailyRecommend -> {
            ImageUtil.show(dailyRecommend.get(0).getCoverImgUrl(), mBinding.recommendMusicDailyMusicImv, R.drawable.recommend_music_daily_recommend_music,
                    R.drawable.recommend_music_daily_recommend_music, 746, 280, 1);
        });

mRecommendCloudMusicViewModel.getDailyRecommendList();

请求到数据后,通过SetValue来通知View层更新:

java 复制代码
public void getRecommendPlayList() {
        repository.getRecommendPlaylist(recommendList -> {
            if (recommendList != null) {
                recommendPlaylistLiveData.setValue(recommendList.getData().getList());
            }
        });
    }

View层通过设置监听来刷新UI:

java 复制代码
mRecommendCloudMusicViewModel.getRecommendPlaylistLiveData().observe(getViewLifecycleOwner(), this::updateRecommendPlaylistUI);

如此,就实现了通过jetpack里的ViewModel来实现了MVVM模式。

相关推荐
吾即是光1 小时前
[SWPUCTF 2021 新生赛]error
android
大耳猫1 小时前
Android 基于Camera2 API进行摄像机图像预览
android·kotlin·相机·camera
MYBOYER1 小时前
Kotlin DSL Gradle 指南
android·开发语言·kotlin
Mr_Xuhhh2 小时前
程序地址空间
android·java·开发语言·数据库
呆呆小雅3 小时前
C# 结构体
android·java·c#
ᥬ 小月亮5 小时前
Layui表格的分页下拉框新增“全部”选项
android·javascript·layui
sunly_15 小时前
Flutter:启动屏逻辑处理02:启动页
android·javascript·flutter
Sgq丶16 小时前
Android Studio 配置 proto
android·ide·android studio
_小马快跑_19 小时前
ConstraintLayout 中的ImageFilterView探索:处理图片圆角、亮度、饱和度、图片重叠等
android
IT-sec20 小时前
jquery-picture-cut 任意文件上传(CVE-2018-9208)
android·前端·javascript·安全·web安全·网络安全·jquery