Android Jetpack架構組件(三)之ViewModel

ViewModel簡介

在早期的Android開發中,因爲應用相對較小,頁面相對簡單,咱們會將數據請求、頁面UI處理和數據加載所有放在Activity或Fragment中進行,可是隨着項目的迭代,這種開發方式顯得愈來愈臃腫,而且也不易於項目的維護和擴展。android

此時,借鑑後端的後端程序的開發思路,咱們對Android項目進行了分層,典型的有MVC,MVP和MVVM等項目分層,而後每層負責本身的事情便可。以如今流行的MVVM模式爲例。數據庫

  • Model層:數據層,主要負責數據實體和對數據實體的操做。
  • View層:視圖層,對應Android的Activity、Fragment和View等,負責數據的顯示以及與用戶的交互。
  • ViewModel層:關聯層,用於將Model和View進行綁定,當Model發生更改時,即時通知View進行刷新,固然,也能夠反向通知。

在JetPack架構中,ViewModel組件是一個能夠感知生命週期的形式來存儲和管理視圖相關的數據的組件,所以它適合如下場景。後端

  • 適合須要保存大量數據的場景。例如,對於須要保存小量數據的場景,咱們可使用Activity/ Fragment的onSaveInstanceState方法保存數據,而後在onCreate方法中利用onRestoreInstanceState進行還原。可是,onSaveInstanceState只適合用來存儲數據量少且序列化或者反序列化不復雜的數據,若是被序列化的對象複雜的話,序列化會消耗大量的內存,進而形成丟幀和視覺卡頓等問題。而ViewModel不只支持數據量大的狀況,還不須要序列化、反序列化操做。
  • 在Android中,Activity/Fragment主要用於顯示視圖數據,若是它們也負責數據庫或者網絡加載數據等操做,那麼勢必形成代碼臃腫,而將邏輯代碼放到ViewModel以後,能夠更有效的將視圖數據相關邏輯和視圖控制器分離開來。

除此以外,ViewModel的好處還有不少,可是最終的目的就是爲了讓代碼可維護性更高,下降代碼的冗餘程度。網絡

生命週期

咱們知道,Android的Activity/Fragment是有生命週期的,咱們能夠在不一樣的生命週期函數中執行不一樣的操做來達到不一樣的目的。因爲ViewModel是保存在內存中的,因此ViewModel的生命週期並不會隨Activity/Fragment的生命週期發生變化 。架構

下圖是官方給出的ViewModel與Activity的生命週期的對應關係示意圖。
在這裏插入圖片描述
從上圖能夠看出,ViewModel會伴隨着Activity/Fragment的整個生命週期,直到ViewModel綁定的Activity/Fragment執行onDestroy()方法以後纔會被銷燬。app

基本使用

1,添加gradle以來,以下所示。ide

dependencies {
   implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
}

2,建立一個繼承自ViewModel類的MyViewModel類,建立ViewModel類千萬不能持有Context的引用,不然會引發內存泄漏,若是須要使用Context能夠繼承AndroidViewModel。函數

public class MyViewModel extends ViewModel {
    private MutableLiveData<String> user;
    public LiveData<String> getUsers() {
        if (user == null) {
            user = new MutableLiveData<String>();
            loadUsers();
        }
        return user;
    }

    private void loadUsers() {
        user.setValue("Android應用開發實戰");
    }
}

3, 爲了不內存泄漏,咱們能夠在onCleared()方法中進行資源釋放操做。而後,咱們在Activity中就可使用MyViewModel,以下所示。源碼分析

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                Log.d(TAG, "LiveData監聽數據返回:"+s);
            }
        });
    }
}

在 Fragment 之間共享數據

public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    public class MasterFragment extends Fragment {
        private SharedViewModel model;

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            model.getSelected().observe(getViewLifecycleOwner(), { item ->
               // Update the UI.
            });
        }
    }

源碼分析

ViewModel源碼

ViewModel類是一個抽象接口,其部分源碼以下。gradle

public abstract class ViewModel {
    
    @Nullable
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;

    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }

    @MainThread
    final void clear() {
        mCleared = true;
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }

   
    @SuppressWarnings("unchecked")
    <T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            closeWithRuntimeException(result);
        }
        return result;
    }

    
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    <T> T getTag(String key) {
        if (mBagOfTags == null) {
            return null;
        }
        synchronized (mBagOfTags) {
            return (T) mBagOfTags.get(key);
        }
    }

    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

能夠發現,ViewModel抽象類的主要做用就是使用HashMap存儲數據。ViewModel 有一個子類AndroidViewModel,它的源碼以下。

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

與繼承ViewModel不一樣,AndroidViewModel須要提供一個 Application 的 Context。

ViewModelProvider

在前面的示例代碼中,咱們在Activity中使用ViewModelProviders.of方法來獲取ViewModel實例,以下所示。

MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);

打開ViewModelProviders類的源碼,能夠發現ViewModelProviders一共有四個構造方法,都是用來建立ViewModelProvider對象,只不過參數不一樣而已。

public static ViewModelProvider of(@NonNull Fragment fragment) {
        return of(fragment, null);
    }
    
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }
    
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
        Application application = checkApplication(checkActivity(fragment));
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(fragment.getViewModelStore(), factory);
    }
    
 public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }

在構建ViewModelProvider的時候須要用到ViewModelStore和Factory,下面咱們來分別介紹一下它們。

ViewModelStore

ViewModelStore主要做用是存儲ViewModel的容器,當咱們打開ViewModelStore的源碼時會發現ViewModelStore是經過HashMap來存儲ViewModel的數據的。而且,ViewModelStore還提供了一個clear方法,用來清空Map集合裏面的ViewModel,咱們能夠在Activity/Fragment的onDestroy方法執行clear方法執行ViewModel數據的清除。

protected void onDestroy() {
        super.onDestroy();
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }
    }

Factory

當咱們使用ViewModelProvider獲取ViewModel實例時,ViewModelProvider一共提供了4個構造函數,另外一個比較重要的構造函數是

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory{
        mFactory = factory;
        mViewModelStore = store;
    }

ViewModelProvider的第二個參數是factory,它的子類有NewInstanceFactory和AndroidViewModelFactory兩個,咱們可使用ViewModelProvider.AndroidViewModelFactory.getInstance獲取單例的Factory對象,NewInstanceFactory源碼以下。

public static class NewInstanceFactory implements Factory {

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }

而AndroidViewModelFactory的源代碼以下。

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

        private static AndroidViewModelFactory sInstance;

       
        @NonNull
        public static AndroidViewModelFactory getInstance(@NonNull Application application) {
            if (sInstance == null) {
                sInstance = new AndroidViewModelFactory(application);
            }
            return sInstance;
        }

        private Application mApplication;

       
        public AndroidViewModelFactory(@NonNull Application application) {
            mApplication = application;
        }

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }
    }

AndroidViewModelFactory實例化構造方法裏面有個參數class,能夠引用Context,實際上是Appplication實例。在上面的代碼中,若是是有application參數,則經過newInstance(application)實例化,不然調用父類的create方法而後經過newInstance()實例化。若是經過newInstance(application)實例化,就能夠在ViewModel裏面拿到Context,因爲Application是APP全局的生命週期最長,因此就不存在內存泄露問題。

ViewModel是如何實現狀態保留的

前面說過,ViewModel是不會隨着Activity/Fragment的銷燬而銷燬的,由於ViewModel是將數據使用ViewModelStore 保存在HashMap 中,因此只要ViewModelStore不被銷燬,則ViewModel的數據就不會被銷燬。

衆所周知,Android在橫豎屏切換時會觸發onSaveInstanceState,而後在還原時則會觸發onRestoreInstanceState。除此以外,Android的Activity類還提供了onRetainNonConfigurationInstance和getLastNonConfigurationInstance兩個方法,當設備狀態發生改變時,上面的兩個方法就會被系統調用。

其中,onRetainNonConfigurationInstance 方法用於處理配置發生改變時數據的保存,而getLastNonConfigurationInstance則用於恢復建立Activity時獲取上次保存的數據。首先,咱們來看一下onRetainNonConfigurationInstance方法,以下所示。

public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        ... 
        
        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

能夠發現,ViewModel會將數據存儲在 NonConfigurationInstances 對象中,而NonConfigurationInstances是定義在Activity裏面的一個類,以下所示。

static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }

再來看一下getLastCustomNonConfigurationInstance方法,
getLastNonConfigurationInstance方法返回的數據就是NonConfigurationInstances.activity屬性,以下所示。

@Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

如今,咱們再看一下ComponentActivity 的 getViewModelStore方法,以下所示。

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mViewModelStore == null) {
    
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

能夠發現,在getViewModelStore方法中咱們首先會獲取NonConfigurationInstances對象,不爲空則從其身上拿到ViewModelStore,也就是以前保存的ViewModelStore,而後當Activity被再次建立的時候恢復數據。

須要說明的是,onRetainNonConfigurationInstance方法會在onSaveInstanceState方法以後被調用,即調用順序一樣在onStop方法和 onDestroy方法之間。

相關文章
相關標籤/搜索