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模式。

相关推荐
三少爷的鞋12 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)16 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码18 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程18 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程21 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓1 天前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer