MVVM,是Model-View-ViewModel的简写。Model:数据,View:UI相关,ViewModel:UI相关数据与控制。
JetPack提供的组件LiveData与ViewModel,十分契合该模式。
(1)LiveData作为数据驱动的核心,驱动与监听Model数据变化。
(2)ViewModel组件,作为页面数据管理的存储与控制中心。
(3)Activity或Fragment,也就是MVVM中的View。
MVVM的核心在于,通过数据驱动修改UI,实现上数据与UI解耦,方便切面测试,易于扩展,也让开发人员可独立开发数据或者UI相关。
以下将以一个获取天气预报信息作为例子。从搭建结构到示例各模块的简单实现。
新建工程结构如下:
(1)数据层model中包含本地数据、网络接口数据,以及管理这两个数据源的数据仓库Repository。
(2)UI层,放置Activity、Fragment等页面元素。UI层持有ViewModel的数据引用,监听ViewModel数据的变化。
(3)ViewModel层,作为model与view的中间连接层,持有对model层的引用。但ViewModel不会持有View的引用,避免内存泄漏。
定义一个PlaceViewModel,管理页面数据
public class PlaceViewModel extends ViewModel {
//此处定义的LiveData,只在该类内部使用。
//意义在于:通过触发该LiveData的数据改变,联动的驱动apiResponseLiveData从Repository获取数据
private MutableLiveData
//获取ViewModel
final PlaceViewModel placeViewModel = new ViewModelProvider(MainActivity.this,new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(PlaceViewModel.class);
//监听该ViewModel中apiResponseLiveData的数据变化
placeViewModel.apiResponseLiveData.observe(this, new Observer>() {
@Override
public void onChanged(ApiResponse placeResponseApiResponse) {
Log.e(TAG, "onChanged: ");
}
});
queryPlaces = findViewById(R.id.query_places);
queryPlaces.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//调用方法,驱动数据变化
placeViewModel.queryPlaces();
}
});
Repository作为数据仓库,在工程中以单例的形式存在即可。该仓库中,可包含多个数据源,如本地数据库、本地缓存、网络接口数据等,该仓库也适合作为测试的切面使用。
获取数据库、缓存等较为简单,参照前文的Room数据库使用即可。此处只介绍如何合理的获取网络接口数据(以Retrofit为例)。
public class Repository {
private static final Repository ourInstance = new Repository();
public static Repository getInstance() {
return ourInstance;
}
private Repository() {
}
public LiveData> queryPlaces() {
//获取网络数据
return ServiceManager.getInstance().create(PlaceService.class).queryPlaces();
}
}
ServiceManager作为网络接口的管理类,以Retrofit为基础,操作接口。此处有别于通常的Retrofit的地方在于:采用了LiveDataCallAdapterFactory,作为接口数据转换的工厂,该类的作用在于把接口数据全部转为LiveData格式,当ViewModel调用接口方法时,直接能得到LiveData格式的数据。
public class ServiceManager {
private Retrofit retrofit;
private PlaceService placeService;
private String baseUrl = "https://api.caiyunapp.com/";
private static final ServiceManager ourInstance = new ServiceManager();
public static ServiceManager getInstance() {
return ourInstance;
}
private ServiceManager() {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
//此处为LiveData格式转换的特殊处理
.addCallAdapterFactory(new LiveDataCallAdapterFactory())
.build();
}
public T create(Class serviceClass) {
return retrofit.create(serviceClass);
}
}
该类的定义,参照android系统本身的DefaultCallAdapterFactory即可。只需重写父类中的get方法。
public class LiveDataCallAdapterFactory extends CallAdapter.Factory {
@Override
public CallAdapter, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
//返回类型必定需要是LiveData
if (getRawType(returnType) != LiveData.class) {
return null;
} else if (!(returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException("type must be parameterized as Call or Call extends Foo>");
} else {
//首个参数
final Type responseType = getParameterUpperBound(0, (ParameterizedType)returnType);
//获取该参数的类
Class> rawResponseType = getRawType(responseType);
//rawResponseType 判断是否符合要求
if(rawResponseType != ApiResponse.class){
throw new IllegalArgumentException("type must be ApiResponse");
}
return new LiveDataCallAdapter<>(responseType);
}
}
}
//R为传入的的类对象
public class LiveDataCallAdapter implements CallAdapter> {
private Type responseType;
public LiveDataCallAdapter(Type responseType) {
this.responseType = responseType;
}
@Override
public Type responseType() {
return responseType;
}
@Override
public LiveData adapt(final Call call) {
//返回一个LiveData格式数据
return new LiveData() {
AtomicBoolean started = new AtomicBoolean(false);
@Override
protected void onActive() {
super.onActive();
//避免多次调用
if (started.compareAndSet(false, true)) {
//方法入队,发起请求
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
ApiResponse responseData = new ApiResponse<>();
if (response.isSuccessful()) {
responseData = (ApiResponse) response.body();
} else {
responseData.setData(null);
responseData.setCode(response.code());
}
responseData.success = true;
responseData.code = response.code();
postValue(responseData);
}
@Override
public void onFailure(Call call, Throwable t) {
ApiResponse responseData = new ApiResponse<>();
responseData.code = -1;
responseData.data = null;
responseData.success = false;
responseData.message = Log.getStackTraceString(t);
postValue(responseData);
}
});
}
}
};
}
}
最后,再看下retrofit的接口定义:
public interface PlaceService {
@GET("v2.5/"+TOKEN+"/121.6544,25.1552/weather.json")
LiveData> queryPlaces();
}
至此,已经完成了一个基于JetPack中的 LiveData与ViewModel的MVVM框架。该框架根据LiveData与ViewModel的特性,十分简单的实现了数据驱动,UI响应数据驱动发生变化。组件本身的生命周期自我控制的特性,也减少了我们工程中可能发生的问题。
JetPack系列在android上的作用,十分类似Vue,React等在前端的作用。
官方提供组件,实现数据响应式框架。
目前,android-11的beta版本也对JetPack系列做了一系列的升级与更新。后续该系列将会越来趋于完善。
JetPack预计后续将成为android上的主流框架,android也将对这套组件框架做更多的升级与完善。