ViewModelの使用と原理解析

31231 ワード

本文はandroidxに基づいている.Lifecycle:lifecycle-extensions:2.0.0のソースコードを分析する
ViewModelは、ActivityおよびFragmentのデータを管理するためのライフサイクル意識でユーザインタフェースに関するデータを格納管理することを目的とする.FragmentとFragmentとの間の通信などを扱うこともできる.
ActivityまたはFragmentが関連付けられたViewModelを作成すると、ActivityまたはFragmentがアクティブである限り、Activity画面が回転するときに再構築されたとしても、ViewModelは破棄する.データの一時保存にも使えます
ViewModelは主にActivity/fragmentに必要なデータを取得または保持するためのものであり、開発者はActivity/fragmentでViewModelにおけるデータの変更を観察することができる(ここではLiveDataと併せて食べる必要がある).
ps:View ModelはUIのデータを管理するためのもので、View、Activity、Fragmentの参照(メモリの漏洩に注意)を持たせないでください.
本稿では、ViewModelを浅く深く学ぶ

一、ViewModelの使用


1.View Modelの導入

// AndroidX , support 
implementation 'androidx.appcompat:appcompat:1.0.2'

def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"

2.簡単に使う

  • Userデータクラス
  • を定義する
    class User implements Serializable {
    
        public int age;
        public String name;
    
        public User(int age, String name) {
            this.age = age;
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
  • そして私たちの今日の主役ViewModel
  • を引き出します
    public class UserModel extends ViewModel {
    
        public final MutableLiveData mUserLiveData = new MutableLiveData<>();
    
        public UserModel() {
            // 
            mUserLiveData.postValue(new User(1, "name1"));
        }
        
        //   
        public void doSomething() {
            User user = mUserLiveData.getValue();
            if (user != null) {
                user.age = 15;
                user.name = "name15";
                mUserLiveData.setValue(user);
            }
        }
    
    }
    
  • このときActivityではViewModelが使用できる.実は1つのコードが簡単にインスタンス化され、ViewModelを使用することができます.
  • // androidx 
    import androidx.fragment.app.FragmentActivity;
    import androidx.lifecycle.Observer;
    import androidx.lifecycle.ViewModelProviders;
    
    public class MainActivity extends FragmentActivity {
    
        private TextView mContentTv;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mContentTv = findViewById(R.id.tv_content);
    
            // ViewModel 
            final UserModel userModel = ViewModelProviders.of(this).get(UserModel.class);
    
            // TextView ViewModel , 
            userModel.mUserLiveData.observe(this, new Observer() {
                @Override
                public void onChanged(User user) {
                    mContentTv.setText(user.toString());
                }
            });
    
            findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //    User    TextView 
                    userModel.doSomething();
                }
            });
        }
    }
    

    この時、ボタン(userの中のageが15になった)をクリックして、携帯電話の画面を回転することができます(この時はActivityが再作成された、つまりonCreate()メソッドが再呼び出されましたが、ViewModelは実際に再作成されていませんか、それともその前のViewModelですか)が、回転するとTextViewに表示されているageが15なのか、、、、、これがViewModelの魔性です.これはView Modelのライフサイクルと言わざるを得ません.Activityが破棄された後でしか自動的に破棄されません(だからView ModelにActivityリファレンスを持たせないでください.メモリが漏れます).Googleの公式画像を引用し、ViewModelのライフサイクルを徹底的に示します.

    3.ViewModel妙用1:ActivityとFragmentの「通信」


    ViewModelがあれば、ActivityとFragmentはActivityに依存しているため、Activityをインスタンス化するときにActivityをViewModelProvidersに転送し、Activityが作成したViewModelにアクセスしやすくなります.ActivityでuserModelのデータを修正すると、そのFragmentは更新後のデータを取得することができる.
    public class MyFragment extends Fragment {
         public void onStart() {
            // ViewModel , Activity 
             UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
         }
     }
    

    4.ViewModel妙用2:FragmentとFragmentの「通信」


    例を見てみましょう(Google公式例)
    public class SharedViewModel extends ViewModel {
        private final MutableLiveData selected = new MutableLiveData();
    
        public void select(Item item) {
            selected.setValue(item);
        }
    
        public LiveData getSelected() {
            return selected;
        }
    }
    
    
    public class MasterFragment extends Fragment {
        private SharedViewModel model;
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }
    
    public class DetailFragment extends Fragment {
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            model.getSelected().observe(this, { item ->
               // Update the UI.
            });
        }
    }
    
  • まずViewModelを定義し、そこにデータ
  • を配置します.
  • それからMasterFragmentとDetailFragmentでこのViewModelを手に入れることができて、このViewModelを手に入れると中のデータを手に入れることができて、間接的にViewModelを通じて通信することに相当します.so easy....

  • 二、ViewModelソースコード解析


    また私たちがよく知っているソースの解析の一環に着きました
    次のコードからstart.
    final UserModel userModel = ViewModelProviders.of(this).get(UserModel.class);
    
    ViewModelProviders.of(this)に従って新しい世界の扉を開けます

    1. ViewModelProviders.of(this)メソッド

    /**
     *  ViewModelProvider, Activity alive Activity ViewModels.
     */
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }
    
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        // application , 
        Application application = checkApplication(activity);
        if (factory == null) {
            // ViewModelProvider.AndroidViewModelFactory
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }
    
    

    ViewModelProvidersのof()関数は、実は私たちがViewModelProviderを構築するのに便利です.ViewModelProviderは、名前を見ると何をしているのかがわかります.ViewModelを提供しています.
    FactoryはViewModelProviderの内部インタフェースであり、その実装クラスはViewModelインスタンスを構築するために使用される.その中には1つの方法しかありません.ViewModelを作成することです.
    /**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * 

    * * @param modelClass a {@code Class} whose instance is requested * @param The type parameter for the ViewModel. * @return a newly created ViewModel */

    @NonNull T create(@NonNull Class modelClass); }

    Factoryには2つの実装クラスがあります.1つはNewInstanceFactory、1つはAndroid View ModelFactoryです.
  • NewInstanceFactoryソース
  • public static class NewInstanceFactory implements Factory {
    
            @SuppressWarnings("ClassNewInstance")
            @NonNull
            @Override
            public  T create(@NonNull Class 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);
                }
            }
        }
    

    NewInstanceFactoryは、その構造方法にパラメータのないclassをインスタンス化するために使用され、View ModelにはContextがない、newInstance()によってインスタンス化される.
  • AndroidViewModelFactoryソース
  • public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
    
        private static AndroidViewModelFactory sInstance;
    
        /**
         * Retrieve a singleton instance of AndroidViewModelFactory.
         *
         * @param application an application to pass in {@link AndroidViewModel}
         * @return A valid {@link AndroidViewModelFactory}
         */
        @NonNull
        public static AndroidViewModelFactory getInstance(@NonNull Application application) {
            if (sInstance == null) {
                sInstance = new AndroidViewModelFactory(application);
            }
            return sInstance;
        }
    
        private Application mApplication;
    
        /**
         * Creates a {@code AndroidViewModelFactory}
         *
         * @param application an application to pass in {@link AndroidViewModel}
         */
        public AndroidViewModelFactory(@NonNull Application application) {
            mApplication = application;
        }
    
        @NonNull
        @Override
        public  T create(@NonNull Class 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をインスタンス化するために使用され、ViewModelにはContextがある可能性がある.
  • newInstance(アプリケーション)によってインスタンス化する.アプリケーションパラメータがある場合は、このようにして
  • をインスタンス化する.
  • アプリケーションパラメータを持たなければ、newInstance()メソッドを使用してインスタンスを構築する.

  • AndroidViewModelFactoryは、構造方法でViewModelにApplicationを持ち込むことで、ViewModelでContextを入手することができます.Applicationはアプリのグローバルなので、メモリの漏洩の問題はありません.一部のViewModelではContextリファレンスが必要ですが、メモリの漏洩が心配されています.
    次は引き続きof(this)メソッドは分析を続けましょう.最後のnew ViewModelProvider(activity.getViewModelStore(), factory);の最初のパラメータはactivityのgetView ModelStore()メソッドを呼び出します(このメソッドはView ModelStoreを返します.このクラスはView Modelを格納します.以下に説明します).ここのactivityはandroidxです.fragment.app.FragmentActivity、このgetViewModelStore()メソッドを見てみましょう
    /**
     *  Activity ViewModelStore
     */
    @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;
    }
    
    // ,Activity viewModelStore
    // , FragmentActivity NonConfigurationInstances( Activity NonConfigurationInstances, , , )
    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }
    
    

    Androidの縦横画面切り替え時にonSaveInstanceState()がトリガーされ、復元時にonRestoreInstanceState()が呼び出されますが、AndroidのActivityクラスにはonReainNonConfigurationInstance()とgetLastNonConfigurationInstance()の2つのメソッドがあります.
    この2つの見知らぬ方法を具体的に見てみましょう
    /**
      fragment 。 ! , onRetainCustomNonConfigurationInstance()
      FragmentActivity 
     */
    @Override
    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;
    }
    
    
    // Activity , mLastNonConfigurationInstances.activity nci
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
    
    

    getLastNonConfigurationInstance()の呼び出しタイミングを見てみましょう.
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ......
        super.onCreate(savedInstanceState);
    
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        ......
    }
    

    まさか、Activityは横長切り替え時にviewModelStoreをこっそり保存し、NonConfigurationInstancesインスタンスに入れたが、横長切り替え時に保存して戻ってきた.

    2. viewModelProvider.get(UserModel.class)


    次に、ViewModelコードを構築する後半に来ます.それはViewModelProviderのget()メソッドです.実装を見てみましょう.実は簡単です.
    public  T get(@NonNull Class modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    public  T get(@NonNull String key, @NonNull Class modelClass) {
        //    
        ViewModel viewModel = mViewModelStore.get(key);
    
        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        
        //    mFactory 
        viewModel = mFactory.create(modelClass);
        // 
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }
    
    

    基本的な考え方は一つのkeyを利用してViewModelをキャッシュし、キャッシュがあればキャッシュし、なければ再構築することである.構築時に使用するfactoryは、上のof()メソッドのfactoryである.

    3. ViewModelStore


    上のいくつかの場所ではViewModelStoreが使われていますが、実は一般的なViewModelを保存するクラスです.
    public class ViewModelStore {
    
        private final HashMap 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);
        }
    
        /**
         *  Clears internal storage and notifies ViewModels that they are no longer used.
         */
        public final void clear() {
            for (ViewModel vm : mMap.values()) {
                vm.onCleared();
            }
            mMap.clear();
        }
    }
    
    

    ViewModelStoreにはHashMap専用のストレージがありますが、普通でしょう.
    いつ呼び出されたclear()を見てみましょう.

    4. ViewModel.onCleared()リソース回収


    ViewModelがライフサイクルで感知されている以上、いつViewModelを整理すればいいのでしょうか.
    FragmentActivityのonDestroy()メソッドに来て、ここで整理されていることを発見しました.
    /**
     * Destroy all fragments.
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
    
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }
    
        mFragments.dispatchDestroy();
    }
    

    5.ビューモデルを見る


    多くの友达が聞くかもしれませんが、ViewModelはいったい何ですか?
    public abstract class ViewModel {
        /**
         *  ViewModel , 
         */
        @SuppressWarnings("WeakerAccess")
        protected void onCleared() {
        }
    }
    

    実はとても简単で、1つの抽象的な类だけあって、中は1つの空の方法???私は拭いて、長い間やって、もとはView Modelは主役ではありません....

    6. AndroidViewModel


    View ModelにはAndroid View Modelというサブクラスがあります.Applicationのプロパティが入っています.それだけで、ViewModelでContextを使いやすいようにしています.
    public class AndroidViewModel extends ViewModel {
        @SuppressLint("StaticFieldLeak")
        private Application mApplication;
    
        public AndroidViewModel(@NonNull Application application) {
            mApplication = application;
        }
    
        /**
         * Return the application.
         */
        @SuppressWarnings("TypeParameterUnusedInFormals")
        @NonNull
        public  T getApplication() {
            //noinspection unchecked
            return (T) mApplication;
        }
    }
    

    三、まとめ


    ViewModelのソースコードは実は多くなく、理解しやすいです.主に公式FragmentActivityが技術実装を提供しています.onRetainNonConfigurationInstance()は状態を保存し、getLastNonConfigurationInstance()は回復します.
    Activityにはこんな2つのものがあったのか、以前はonSaveInstanceState()とonRestoreInstanceState()を知っていただけで、ポーズを上げました.