Jetpack MVVM

Android开发中的架构

Android中的开发架构是用来描述 视图层逻辑层数据层三者之间的关系的。

  • 视图层:用户界面,即界面的展示和交互事件的响应。
  • 逻辑层:为了实现系统功能而进行的必要逻辑。
  • 数据层:数据的获取和存储,含本地数据和服务器数据。

一般我们的架构之路是从 MVC -> MVP -> MVVM 演变的。

MVC

MVC,Model-View-Controller,职责分类如下:

  • Model,模型层,即数据模型,用于获取和存储数据。
  • View,视图层,在Android中就是xml布局。
  • Controller,控制层,负责业务逻辑。

View接收到用户操作事件,通知到Controller进行对应的逻辑处理,然后通知Model去获取/更新数据,Model再把新的数据传递给View更新界面,这就是一个完整MVC的数据流向。

但在Android中,因为xml布局作为视图层能力很弱,很多操作View的代码是写在Activity/Fragment中的,而业务逻辑同样也是写在Activity/Fragment中。

所以,Android中MVC的问题点如下:

  1. Activity/Fragment的责任不明确,同时负责View和Controller,这样就导致Activity/Fragment的代码量很大,不满足单一职责原则。
  2. Model耦合View,View的修改会导致Controller和Model都进行改动,不满足最少知识原则。
MVP

MVP,Model-View-Presenter,职责分类如下:

  • Model,模型层,即数据模型,用于获取和存储数据。
  • View,视图层,即Activity/Fragment.
  • Presenter,控制层,负责业务逻辑。

MVP解决了MVC的问题:

1.Activity/Fragment的责任明确,逻辑不再写在Activity/Fragment中,而是在Presenter中;

2.Model不再持有View。

View层接收到用户操作事件后,调用Presenter进行逻辑处理,然后通知Model获取或更新数据,Model把数据给到Presenter,Presenter再通知View更新界面。

MVP的实现思路:

  • UI逻辑抽象成IView接口,由具体的Activity实现类来完成,且调用Presenter进行逻辑处理。
  • 业务逻辑抽象成IPresenter接口,由具体的Presenter实现类来完成。逻辑操作完成后调用IView接口方法刷新UI。

MVP本质是面向接口编程,实现了依赖倒置原则。MVP解决了View层责任不明确的问题,但并没有解决代码耦合的问题,View和Presenter之间相互持有。

所以,MVP的问题点如下:

  1. 会引入大量的IView、IPresenter接口,增加实现的复杂度。
  2. View和Presenter相互持有,形成耦合。
MVVM

MVVM,Model-View-ViewModel,职责分类如下:

  • Model,模型层,即数据模型,用于获取和存储数据。
  • View,视图,即Activity/Fragment。
  • ViewModel,视图模型,负责业务逻辑。

注意,MVVM这里的ViewModel就是一个名称,可以理解为MVP中的Presenter。不等同于前面文章中的ViewModel组件 ,Jetpack ViewModel组件是对MVVM的ViewModel的具体实施方案。

MVVM的本质是数据驱动,把解耦做的更彻底,ViewModel不持有View 。

View产生事件,使用ViewModel进行逻辑处理后,通知Model更新数据,Model把更新的数据给ViewModel,ViewModel自动通知View更新界面而不是主动调用View的方法

到这里你会发现,所谓的架构模式本质上理解很简单。比如MVP,甚至你都可以忽略这个名字,理解成在更高的层面上面向接口编程,实现了依赖倒置原则,就是这么简单。

MVVM的实现 ------ Jetpack MVVM

Jetpack MVVM是MVVM架构在Android开发中的一种具体实现方式,是Google官方提供并推荐的。

Jetpack MVVM不仅通过数据驱动实现了View与ViewModel的解耦,还兼顾了Android页面开发中其他不可预期的错误,例如Lifecycle能妥善处理页面生命周期,避免View的空指针问题,ViewModel使得配置更改导致Activity重建时无需重新向后台请求数据,节省了开销。

首先,请看下图,该图显示了所有模块应如何彼此交互:

各模块对应MVVM架构:

  • View层:Activity/Fragment
  • ViewModel层:Jetpack ViewModel + Jetpack LivaData
  • Model层:Repository仓库,包含 本地持久性数据 和 服务端数据

View层 包含了我们平时写的Activity/Fragment/布局文件等与界面相关的东西。

ViewModel层 用于持有和UI相关的LiveData数据,以保证这些数据在屏幕旋转时不会丢失,并且还要提供接口给View层调用以及和仓库层进行通信。

Model层 要做的主要工作是判断调用方请求的数据应该从本地数据源中获取还是从网络数据源中获取,并将获取到的数据返回给调用方。本地数据源可以使用数据库、SharedPreferences等持久化技术来实现,而网络数据源则通常使用Retrofit访问服务器提供的Webservice接口来实现。

另外,图中所有的箭头都是单向的,例如View层指向了ViewModel层,表示View层会持有ViewModel层的引用,但是反过来ViewModel层却不能持有View层的引用。除此之外,引用也不能跨层持有,比如View层不能持有仓库层的引用,谨记每一层的组件都只能与它相邻层的组件进行交互。

这种设计打造了一致且愉快的用户体验。无论用户上次使用应用是在几分钟前还是几天之前,现在回到应用时都会立即看到应用在本地保留的数据。如果此数据已过期,则应用的Repository将开始在后台更新数据。

示例

这里以一个获取天气信息并展示的业务来说明如何实现Jetpack MVVM。

新建一个天气信息类,里面有2个字段,一个字段用来表示什么样的天气,另一个表示温度:

java 复制代码
public class WeatherInfo {
    //晴天/雨天/多云...
    private String text;
    //温度
    private String temp;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getTemp() {
        return temp;
    }

    public void setTemp(String temp) {
        this.temp = temp;
    }
}

为了实现数据驱动,需要使用LiveData来包装天气信息,其中loadingLiveData是用来控制进度条的显示的。根据上面的架构图,还需要使用ViewModel来存储LiveData,这样在屏幕旋转时LiveData的数据不会丢失:

java 复制代码
public class WeatherInfoViewModel extends ViewModel {
    //天气信息
    private MutableLiveData<WeatherInfo> weatherInfoLiveData;
    //进条度的显示
    private MutableLiveData<Boolean> loadingLiveData;

    public LiveData<WeatherInfo> getWeatherInfoLiveData() {
        return weatherInfoLiveData;
    }

    public LiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }
}

LiveData是一种可观察的数据存储器,应用组件(如 Activity、Fragment 和 Service)可以使用此存储器监控对象的更改,而无需在它们之间创建明确且严格的依赖路径。LiveData组件还遵循应用组件的生命周期状态,并包括清理逻辑以防止对象泄漏和过多的内存消耗。

注意到WeatherInfoViewModel类暴露的getWeatherInfoLiveData()方法返回的是LiveData类型,即不可变的,而不是MutableLiveData,好处是避免数据在外部被更改。

我们在WeatherActivity中观察LiveData的更新:

java 复制代码
public class WeatherActivity extends AppCompatActivity{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        initData();
    }

    private void initData(){
        ViewModelProvider viewModelProvider = new ViewModelProvider(this);
        WeatherInfoViewModel weatherInfoViewModel = viewModelProvider.get(WeatherInfoViewModel.class);

        weatherInfoViewModel.getWeatherInfoLiveData().observe(this, new Observer<WeatherInfo>() {
            @Override
            public void onChanged(WeatherInfo weatherInfo) {
                //更新天气信息
                mTvWeather.setText(weatherInfo.getText());
                mTvTemp.setText(weatherInfo.getTemp());
            }
        });

        weatherInfoViewModel.getLoadingLiveData().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean aBoolean) {
                //显示或隐藏加载进度条
                mProgressBar.setVisibility(aBoolean? View.VISIBLE:View.GONE);
            }
        });
        
        ...

    }

}

每次更新天气信息,相应的onChanged()方法都会回调,而不需要ViewModel主动调用View层方法更新UI,这就是数据驱动 ------ 数据的更改驱动View自动更新。

因为LiveData具有生命周期感知能力,这意味着,只有Activity处于活跃状态,onChanged()才会回调。当Activity的onDestroy()执行时,LiveData会自动移除观察者。

鉴于WeatherInfoViewModel对象比对应的View对象存活的时间更长,WeatherInfoViewModel中不得包含对View对象的直接引用,包括Context。

我们已经使用ViewModel将LiveData与Activity关联起来了,那么如何获取天气信息呢?

一个简单的想法是直接在ViewModel中使用OkHttp从服务器获取天气信息,获取成功后将数据设置给LiveData,但是这样会使ViewModel承担了太多的责任,违背了单一职责原则。

在上面的架构图中,ViewModel将数据获取过程委派给一个新的模块------Repository。这里我们也照着架构图新建一个类WeatherRepository,用来从服务器获取天气信息:

java 复制代码
public class WeatherRepository {

    private static WeatherRepository weatherRepository;

    public static WeatherRepository getWeatherRepository() {
        if(weatherRepository == null){
            weatherRepository = new WeatherRepository();
        }
        return weatherRepository;
    }

    public void getWeatherInfoFromServer(Callback callback){

        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(15L, TimeUnit.SECONDS)
                .readTimeout(15L, TimeUnit.SECONDS)
                .writeTimeout(15L, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true);

        HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger(){
            @Override
            public void log(String message) {
                Log.d("TAG", "url info:" + message);
            }
        });
        logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(logInterceptor);

        OkHttpClient okHttpClient = builder.build();
        Request request = new Request.Builder()
                .url("http://xxxx")
                .get()
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new okhttp3.Callback(){

            @Override
            public void onFailure(Call call, IOException e) {
                Log.d("TAG", "onFailure: ");
                callback.onFailed(e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    String data = response.body().string();
                    Gson gson = new Gson();
                    WeatherInfo weatherInfo;
                    try {
                        weatherInfo = gson.fromJson(data, WeatherInfo.class);
                        callback.onSuccess(weatherInfo);
                    } catch (Exception e){
                        Log.i("TAG", "Exception e:" + e.getMessage());
                    }
                }
            }

        });
    }
}

getWeatherInfoFromServer()方法传入一个接口回调Callback:

java 复制代码
public interface Callback<T> {
    //成功的时候回调
    public void onSuccess(T t);
    //失败的时候回调
    public void onFailed(String msg);
}

在WeatherInfoViewModel中调用getWeatherInfoFromServer()方法,并在接口回调中修改LiveData中的值:

java 复制代码
public class WeatherInfoViewModel extends ViewModel {
    //天气信息
    private MutableLiveData<WeatherInfo> weatherInfoLiveData;
    //进条度的显示
    private MutableLiveData<Boolean> loadingLiveData;


    public LiveData<WeatherInfo> getWeatherInfoLiveData() {
        return weatherInfoLiveData;
    }

    public MutableLiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }

    public void getWeatherInfo(){
        //获取天气信息前显示进度条
        loadingLiveData.setValue(true);
        
        WeatherRepository.getWeatherRepository().getWeatherInfoFromServer(new Callback<WeatherInfo>() {
            @Override
            public void onSuccess(WeatherInfo weatherInfo) {
                //获取成功后,让进度条消失
                loadingLiveData.setValue(false);
                weatherInfoLiveData.setValue(weatherInfo);
            }

            @Override
            public void onFailed(String msg) {
                loadingLiveData.setValue(false);
                weatherInfoLiveData.setValue(null);
            }
        });
    }
}

这样就把获取天气信息委派给了WeatherRepository,当我们还想从本地获取天气信息直接在WeatherRepository中添加相应的方法即可:

java 复制代码
public class WeatherRepository {
    
    public void getWeatherInfoFromLocal(){
        //从本地获取天气信息
    }

    public void saveWeatherInfoToLocal(){
        //将天气信息存入本地
    }
    
    public void getWeatherInfoFromServer(){
        //从服务器获取天气信息
    }
}

最后,还要在Activity中,调用weatherInfoViewModel.getWeatherInfo()获取天气信息。这样,通过WeatherRepository获取到天气信息后,回调给ViewModel更新其中的LiveData的值,通过数据驱动,相应的View可以拿到天气信息更新UI。

以上就是通过Jetpack MVVM实现的MVVM架构的实现方案。

相关推荐
消失的旧时光-19438 小时前
Kotlinx.serialization 使用讲解
android·数据结构·android jetpack
Tans514 小时前
Androidx Fragment 源码阅读笔记(下)
android jetpack·源码阅读
Lei活在当下2 天前
【业务场景架构实战】2. 对聚合支付 SDK 的封装
架构·android jetpack
Tans54 天前
Androidx Fragment 源码阅读笔记(上)
android jetpack·源码阅读
alexhilton5 天前
runBlocking实践:哪里该使用,哪里不该用
android·kotlin·android jetpack
Tans58 天前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
ljt27249606619 天前
Compose笔记(四十九)--SwipeToDismiss
android·笔记·android jetpack
4z3311 天前
Jetpack Compose重组优化:机制剖析与性能提升策略
性能优化·android jetpack
alexhilton11 天前
Android ViewModel数据加载:基于Flow架构的最佳实践
android·kotlin·android jetpack
水牛15 天前
一行代码完成startActivityForResult
android·android jetpack