天天看點

Android Jetpack架構元件(三)之ViewModel一、ViewModel簡介二、生命周期三、基本使用四、 在 Fragment 之間共享資料五、源碼分析六、ViewModel是如何實作狀态保留的

我的部落格即将同步至 OSCHINA 社群,這是我的 OSCHINA ID:xiangzhihong,邀請大家一同入駐:https://www.oschina.net/sharing-plan/apply

一、ViewModel簡介

在早期的Android開發中,由于應用相對較小,頁面相對簡單,我們會将資料請求、頁面UI處理和資料加載全部放在Activity或Fragment中進行,但是随着項目的疊代,這種開發方式顯得越來越臃腫,并且也不易于項目的維護和擴充。

此時,借鑒後端的後端程式的開發思路,我們對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的生命周期的對應關系示意圖。

Android Jetpack架構元件(三)之ViewModel一、ViewModel簡介二、生命周期三、基本使用四、 在 Fragment 之間共享資料五、源碼分析六、ViewModel是如何實作狀态保留的

在這裡插入圖檔描述

從上圖可以看出,ViewModel會伴随着Activity/Fragment的整個生命周期,直到ViewModel綁定的Activity/Fragment執行onDestroy()方法之後才會被銷毀。

三、基本使用

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

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.
            });
        }
    }
               

複制

五、源碼分析

5.1 ViewModel源碼

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

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。

5.2 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,下面我們來分别介紹一下它們。

5.3 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();
        }
    }           

複制

5.4 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方法之間。