最近在使用MVVM架构的时候涉及到一个在View层中何处初始化话ViewModel的问题。由于咱们的项目中都会有BaseAcitvity,对于MVVM架构的话,咱们能够经过泛型指定子类使用的具体的ViweModel,而后BaseActivity抽象出初始化ViewModel的方法,由子类来具体实现。主要代码以下:java
public abstract class BaseActivity<VM extends ViewModel> extends AppCompatActivity {
protected VM viewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(layoutId());
viewModel = createViewModel();
}
abstract VM createViewModel();
@LayoutRes
abstract int layoutId();
}
复制代码
public class ActivityHome extends BaseActivity<HomeViewModel> {
@Override
HomeViewModel createViewModel() {
return new ViewModelProvider(this).get(HomeViewModel.class);
}
@Override
int layoutId() {
return R.layout.activity_home;
}
}
复制代码
public class HomeViewModel extends AndroidViewModel {
public HomeViewModel(@NonNull Application application) {
super(application);
}
}
复制代码
因而我想到可不能够直接在在BaseActivity里初始化子类的ViewModel,而不用子类每次都要实现这个createViewmodel方法,可是要实现这个效果须要解决两个问题:数组
首先,第一个问题:如何在BaseActivity中知道子类指定的哪一个ViewModelbash
public class ActivityHome extends BaseActivity架构
咱们在子类中经过泛型指定了HomeViewModel并看成BaseActivity的泛型类的形参传递给BaseActivity,按道理来说在BaseActivity中是能够拿到本身的泛型信息的,实际上也是能够的: Class类中的getGenericSuperclass()方法能够拿到父类的类型信息,固然也就拿到了泛型信息,由于一个类拥有泛型,那么这个类的类型就是这些泛型决定的(不许确)。app
public abstract class BaseActivity<VM extends ViewModel> extends AppCompatActivity {
private final String TAG = getClass().getSimpleName();
protected VM viewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(layoutId());
viewModel = createViewModel();
Type genericSuperclass = getClass().getGenericSuperclass();
if (genericSuperclass != null) {
Log.i(TAG, genericSuperclass.toString());
// log输出为:ActivityHome: com.allfun.blogssourcecode.one.BaseActivity<com.allfun.blogssourcecode.one.HomeViewModel>
}
}
abstract VM createViewModel();
@LayoutRes
abstract int layoutId();
}
复制代码
其实就是当前类ActivityHome初始化的时候调用父类BaseActivity的onCreate方法触发getGenericSuperclass得到BaseActivity的泛型,从log里能够看出,咱们看到了ActivityHome在泛型里指定的ViewModel:HomeViewModel。ide
第二个问题:知道子类指定的ViewModel以后如何获得对应ViewModel的class类(由于须要使用new ViewModelProvider(this).get(HomeViewModel.class)进行ViewModel的实例化)。ui
经过问题一的getGenericSuperclass()方法拿到父类的泛型信息,返回值为Type,而后咱们经过强转将类型强转为泛型类型,再经过getActualTypeArguments()方法拿到泛型类型对应的泛型数组,由于咱们这里只有一个泛型VM因此取第一个就是咱们须要的HomeViewModel。this
可是咱们须要的是HomeViewModel的Class对象,而如今获得的是HomeViewModel Type,那么咱们找一下Class和Type的关系: 编码
public abstract class BaseActivity<VM extends ViewModel> extends AppCompatActivity {
private final String TAG = getClass().getSimpleName();
protected VM viewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(layoutId());
createViewModel();
}
private void createViewModel() {
Type genericSuperclass = getClass().getGenericSuperclass();
if (genericSuperclass != null) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Class homeViewModelClass = (Class) actualTypeArguments[0];
viewModel = ((VM) new ViewModelProvider(this).get(homeViewModelClass));
}
}
@LayoutRes
abstract int layoutId();
}
...
...
复制代码
目的是达到了,可是有个问题仍是不太明白: java泛型只存在编译期间,运行时就会将泛型擦除,也就是说 ((VM) new ViewModelProvider(this).get(homeViewModelClass))这里的VM会被替换为Object,那问题来了,这样的话是如何完成类型转换的,由于从结果看也顺利转换为HomeViewModel类型了。spa
其实这里VM确实会被替换为ViewModel(此处由于又extends ViewModel,应该会替换为ViewModel),之因此也能转转换成功,是由于这个Class自己就是HomeViewModel的Class类,这里强转只是为了让编译器(存疑)能够识别这个Class是HomeViewModel的Class,是为了继续下一步的编码,由于咱们不强转为VM类型,那么就没法使 VM viewModel = ((VM) new ViewModelProvider(this).get(homeViewModelClass)),由于左边须要一个VM实例,而右边不强转的话只是一个Object,尽管这个Object其实是VM的实例,我举个例子会比较好理解:
public class Example {
static class People {
}
static class Doctor extends People {
}
public static void main(String[] args) {
Doctor doctor = new Doctor();
test(doctor);
}
static void test(Object object) {
Class aClass = object.getClass();
Log.e("test",aClass.toString());
//log输出为: test: class com.allfun.blogssourcecode.one.Example$Doctor
}
}
复制代码
又上面这个例子能够知道,一个类实例自己是什么实例化的时候就定下来了,中间可能由于做为参数传递的时候丢失了具体的类型而转化为父类(头Doctor转化为Object)可是在内存里依然是Doctor对象,因此呢类型转化在这里只是一个标记而已,告诉程序这个对象如今看成这种类型来使用,并不会改变对象实际的属性(听说也会有改变本来对象结构的强转,可是在这里这种状况不是)。
因此呢,我认为((VM) new ViewModelProvider(this).get(homeViewModelClass))这里的强转只是为了让编码能进行下去,由于不加(VM)等式不成立会报错。
可是程序实际运行的时候方法体内VM会替换为ViewModel,那么会被强转为ViewModel为不是HomeViewModel不是吗?其实不会,由于Java泛型有这么一种规律:
位于声明一侧的,源码里写了什么到运行时就能看到什么;
位于使用一侧的,源码里写什么到运行时都没了。
protected VM viewModel;这里属于声明一侧,因此viewModel = ((VM) new ViewModelProvider(this).get(homeViewModelClass));运行的时候是赋值给VM类型的viewModel,换成例子中VM就是HomeViewModel,因此最后仍是拿到了VM泛型表明的类型实参的对象实例。
其实呢,createViewModel方法能够这样写
private void createViewModel() {
Type genericSuperclass = getClass().getGenericSuperclass();
if (genericSuperclass != null) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Class<VM> homeViewModelClass = (Class<VM>) actualTypeArguments[0];
viewModel = (new ViewModelProvider(this).get(homeViewModelClass));
}
}
复制代码