Jetpack-ViewModel

夕陽下的奔跑發表於2019-09-11

作用儲存和管理UI相關的資料,並且與宣告週期關聯。ViewModel保證資料在配置改變時能夠存活,如螢幕旋轉。

引入的原因

  1. 如果系統銷燬或者重新建立UI元件,那麼儲存的與UI相關的瞬時資料就會丟失。舉例來說,你的應用可能在Activity中儲存了一個使用者列表。當Activity因為配置改變重建時,新的Activity需要重新獲取使用者列表。針對簡單的資料,Activity可以用onSaveInstanceState()方法儲存並在onCreate()重建這些資料,但是這個方法只是適用於少量的可以被序列化和反序列化的資料,而不適用於大量的資料例如使用者或者點陣圖的列表。
  2. 另一個問題是UI元件會經常需要非同步呼叫,它們需要花費一些時間。UI元件需要管理這些非同步呼叫,並且確保系統能夠在元件被銷燬時能夠回收這些呼叫,以避免記憶體洩漏。這些管理需要大量的維護工作,同時這些物件在配置改變時會重新建立,這是一種資源浪費,因為這些物件已經建立了,但是又必須重新建立。
  3. 像Activity和Fragment這樣的UI元件的首頁目的是展示UI資料,響應使用者行為,以及處理作業系統的行為,如許可權申請。UI元件也需要負責從資料庫或者網路載入資料,這會使類膨脹。將過多的責任分配給UI元件會導致一個類需要獨立處理很多工作,而不是將這些工作委託給其他類。把過多的工作分配給UI元件也會導致測試變得困難。

使用:

定義ViewModel

public class UserViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }

    static class User {
        String name;
        int age;
    }
}
複製程式碼

在Activity中獲取一個例項

UserViewModel model = null;
model = new ViewModelProvider(this).get(UserViewModel.class);
model.getUsers().observe(this, new Observer<List<UserViewModel.User>>() {
	@Override
    public void onChanged(List<UserViewModel.User> users) {
    }
});
複製程式碼

在多個Fragment中共享ViewModel

//通過activity的ViewModelStore獲取ViewModel
new ViewModelProvider(getActivity()).get(UserViewModel.class);
複製程式碼

類圖:

Jetpack-ViewModel

關鍵類:

ViewModel:資料管理類,以及處理Activity和Fragment對資料的互動操作

public abstract class ViewModel {
    //為ViewModel儲存一些資料
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    //onDestroy時標記資料已被清除
    private volatile boolean mCleared = false;
}
複製程式碼

ViewModelStore:儲存ViewModel

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}
複製程式碼

ViewModelProvider:獲取ViewModel

public class ViewModelProvider {
    //ViewModel的建立工廠
    private final Factory mFactory;
    //儲存ViewModel
    private final ViewModelStore mViewModelStore;
    
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
    //獲取對應的ViewModel物件
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        //如果ViewModelStore中不存在,則使用Factory建立
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
}
複製程式碼

Factory:建立ViewlMode例項

public interface Factory {
    @NonNull  //建立ViewModel例項
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
複製程式碼

實現Factory介面的有KeyedFactory,NewInstanceFactory,AndroidViewModelFactory等例項建立類

Activity的重建時的資料恢復過程:

資料儲存過程

  1. 在ComponentActivity的onSaveInstanceState()中儲存:

    Jetpack-ViewModel

  2. SavedStateRegistryController中呼叫SavedStateRegistry進行儲存

    Jetpack-ViewModel

  3. SavedStateRegistry執行儲存邏輯

    Jetpack-ViewModel
    ①AbstractSavedStateViewModelFactory建立的物件最終會被新增到mComponents中

    ②在資料恢復時,mRestoredState會從取出儲存的components。當呼叫AbstractSavedStateViewModelFactory的create方法時,會從mRestoredState中獲取對應的bundle,並remove

    如何去使用這些資料的邏輯有點複雜,可以看AbstractSavedViewModelFactory的create方法:

Jetpack-ViewModel

資料恢復過程

  1. ComponentActivity的onCreate執行恢復:
    Jetpack-ViewModel
  2. SavedStateRegistryController中呼叫SavedStateRegistry的performRestore進行恢復:
    Jetpack-ViewModel
  3. SavedStateRegistry執行恢復邏輯
    Jetpack-ViewModel
    取出儲存時key為SAVED_COMPONENTS_KEY對應的bundle