最近在使用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的繼承關係能夠看出,它只有一個實現子類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));
}
}
複製代碼