記錄一次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));
        }
    }
複製代碼
相關文章
相關標籤/搜索