记录一次ViewModel初始化位置的优化

最近在使用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的关系: 编码

Type继承关系树
由Type的继承关系能够看出,它只有一个实现子类Class,也就是说,全部类型的Type最终都是由一个个Class表示的,那么咱们就直接将获得的type强转为Class对象,至此就能够经过ViewModelProvider(this).get(HomeViewModel.class)在BaseActivity中经过泛型直接实例化出子类的ViewModel对象了。 修改一下代码,以下:

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));
        }
    }
复制代码
相关文章
相关标签/搜索