Jetpackシリーズ-ViewModel入門からソースコードへ

12874 ワード

この文章は最も簡単な方法でViewModelの使用方法とその動作原理を教えることを目的としています.

ViewModelの紹介


ViewModelはGoogleが発売したJetPackフレームワークコンポーネントの1つで、その主な機能はデータを包装することであり、包装後のデータはホスト(Activity)が再構築する際にデータが再構築されない.一般的に言えば、Androidシステムでは、Activityが構成変更後にActivityが再起動(画面回転など)する可能性がありますが、この時点で変更したActivityのデータ(このActivityでネットワークが要求したデータを含む)は破棄され、再初期化され、これらの変更は失われます.たとえば、処理を行わずにEditTextにコンテンツを入力し、画面を回転するとActivityが破棄されて再起動され、以前に入力したコンテンツも失われます.これはもちろん私たちが望んでいるものではありません.
  • 従来の方法では、Activity破棄前にonSaveInstancesState()のBundleにデータを格納し、Activity再起動時にonCreate()のBundleからデータを復元することができますが、この方法は、リストデータやビットマップなどの潜在的な大量のデータには適用されません.
  • JetpackコンポーネントのViewModelは、Activityの再構築時にデータが破棄されないように保護するのに役立ちます.同じActivityの下にあるすべてのFragment間のデータ共有にも使用できます.データとUIの分離を実現します.公式には、ViewModelとLiveDataを併用することを推奨していますが、ViewModelの役割と使用をより明確に理解するために、この記事では、ViewModelを単独で取り出して使用します.LiveDataの使用については、次の記事で詳しく説明します.

  • 最後に、このActivityとViewModelのライフサイクルの比較を見てみましょう(画像はAndroid開発者の公式サイトより抜粋):
    図から分かるように、ViewModelはデータをカプセル化しているため、データのライフサイクルはActivityよりも長く、Activityのライフサイクルに従ってデータが変化しないことを意味し、ある程度実現されている.
    UI(Activity)とデータ(ViewModel)の分離.
    話が多すぎて、本題に入りました.

    ViewModelの使用


    1.基礎使用

  • 新しいプロジェクトを作成し、MainActivityのレイアウトファイルにTextViewにId:data_を追加tv.
  • ModelViewパッケージのデータクラスを作成する:
  • public class MyBean extends ViewModel {
      private  String dataBean;
    
      String getDataBean() {
        if (dataBean == null) {
          dataBean = “ ”;  //  
        }
        return dataBean;
      }
    
      void changeValue() {
        dataBean = dataBean + " +1 " ;
      }
    }
    
  • MainActivity:
  • public class MainActivity extends AppCompatActivity {
    
      private static final String TAG = “MainActivity”;
      private MyBean mBean;
    
      @Override
      protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        final TextView valueTv = findViewById(R.id.data_tv);
    
        // ViewModel :  ViewModelProviders   ViewModel(myBean)
        mBean = ViewModelProviders.of(this).get(MyBean.class);
        mBean.getDataBean();
    
        valueTv.setText(mBean.getDataBean());
    
        valueTv.setOnClickListener(*new*OnClickListener() {
          @Override
          public void onClick(View v) {
            mBean.changeValue();
            Log.d(TAG, “onClick:   Value  : “ + mBean.getDataBean());
          }
        });
    
        Log.d(TAG, “onCreate: “);
      }
    
      @Override
      protected void onDestroy() {
        Log.d(TAG, “onDestroy: “);
        super.onDestroy();
      }
    }
    

    OJBK、コードはもう書き終わりました~
    実は、最近の更新コードは簡単です.Activityの初期化時に私たちのView Model(MyBean)を初期化して値を割り当て、View Modelの値をTextViewに表示し、TextViewにクリックイベントを追加しました.クリックするとchangeValue()をトリガーしてView Modelの値を変更します.
    その後、アプリを実行し、TextViewを3回クリックし、画面を回転し、TextViewを再度クリックし、Logを表示します.
    D/MainActivity: onCreate: 
    D/MainActivity: onClick:   Value  :   +1 
    D/MainActivity: onClick:   Value  :   +1  +1 
    D/MainActivity: onClick:   Value  :   +1  +1  +1 
    D/MainActivity: onDestroy: 
    D/MainActivity: onCreate: 
    D/MainActivity: onClick:   Value  :   +1  +1  +1  +1 
    

    ログによると、私たちのViewModelのデータは確かに画面が回転した後に生き残ったという.TextViewの値をクリックイベントによって変更したい場合は、TextViewを使用します.setTextメソッドですが、LiveDataはよりエレガントで簡便に実現されています.

    2.Fragment間でデータを共有する


    1つのActivityの2つ以上のFragmentが互いに通信する必要があるのはよくありますが、この場合は通常面倒です.1つのActivityに2つのFragmentが同時に表示され、1つのFragmentがデータのリストを表示し、もう1つのFragmentが選択したデータの内容を表示することを想定します.この場合、2つのFragmentはいくつかのインタフェースを定義する必要があり、Activityはこの2つのFragmentをバインドしなければならないため、簡単ではありません.また、3番目のFragmentがある場合、この2つのFragmentは、別のFragmentの様々なシーンを処理する必要があります.
    しかし、ViewModelを採用すれば、簡単になります.
    mBean = ViewModelProviders.of(getActivity()).get(MyBean.class);
    

    両方のFragmentは、この方法でViewModelを取得します.このように、Fragmentがそれぞれ同じViewModelインスタンスを受け取る場合、そのインスタンスの役割ドメインはこのActivityの役割ドメインである.複数のFragmentがあってもActivityとすべてのFragmentとデータを共有できます.
    メリット:
  • Activityは、何もする必要はなく、通信に関する情報を知る必要もありません.
  • ModelViewのほか、Fragmentはお互いを理解する必要はありません.一方のFragmentが破棄された場合、もう一方はいつものように動作します.
  • 各Fragmentには独自のライフサイクルがあり、別のFragmentライフサイクルの影響を受けません.あるFragmentが別のFragmentに置き換えられた場合、UIは問題なく動作し続けます.

  • 3.その他の使用


    単純なViewModelの使用では、開発効率を向上させることはできません.以下に、いくつかの一般的な拡張子を示します.
  • Live Data:ViewModelオブジェクトとLive Data(ライフサイクルオブザーバ)を組み合わせて使用するのもAndroidの公式推奨です.ViewModelにLiveDataを追加すると、Activityでデータの変化を感知し、UIを更新できます.本当にUIとデータの分離を実現しますが、データが変化すると、UIは依然として変化することができます.また、LiveDataにはより多くの親切なデザインがありますので、次の記事でお話しします.
  • AndroidviewModel:ViewModel型がContextを必要とする場合.この需要はよくある.この場合、Android View Modelクラスを使用または拡張することができます.これは簡単です.Android View Modelのコードを検索してみてください.ここでは説明しません.
  • static拡張:ViewModelがActivity間のデータ共有を実現するには、ViewModelクラス内で特定のカプセル化されたデータクラスにstatic宣言を追加するだけです.たとえば、
  • などです.
    public class MyBean extends ViewModel {
      static private  String dataBean;
    ...
    }
    

    これは、グローバルにdataBeanインスタンスが1つしかないことを意味しますが、ログインユーザー情報を共有する必要がある場合に便利です.ここまで来て、あなたはViewModelの使用に対してすでに自分の理解があることを信じます
    おめでとうございます.ViewModelのエッセンス部分はもう体験しました.もしあなたがViewModelの動作原理に興味があれば、引き続き読んでください.私はあなたを連れてソースコードから一歩一歩ViewModelの動作原理を完全に理解します.興味がなければ、文章の末尾に直接引っ張ってまとめを見ることができます~

    ViewModelの動作原理:


    ViewModelの動作原理の核心の問題は、ViewModelがどこに格納されているかということです.どのようにアクセスしますか?ViewMoedlはActivityが破棄されたときに自分が破棄されないことをどのように実現しますか?

    1. ViewModelProviders.java


    ソースコードを一歩一歩見て、私たちは使用したこの一言から始めます.
    mBean = ViewModelProviders.of(this).get(MyBean.class);
    

    クリックしてViewModelProvidersに入ります.JAva、これはメソッドのリストです.
    2つのcheckメソッドが空の判断であり、ofメソッドを見ることができます.
    @NonNull
    @MainThread
    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(ViewModelStores.of(activity), factory);
    }
    

    これは難しくありません.View Providersの主な職責は次のとおりです.
  • ApplicationでAndroidViewModelFactoryを取得します(このAndroidViewModelFactoryは単一のモードです).
  • は、ViewModelProviderを初期化して返します.ViewModelStoreとAndroidViewModelFactoryに転送する必要があります.ActivityはViewModelStroe(AppCompatActivityはFragmentActivityを継承するが、FragmentActivityはViewModelStoreOwnerを実現し、ViewModelStroeを持つ)を保有するため、ここではViewModelStoresを使用する.of(activity). これはof()後で見ましょう.

  • 2. ViewModelProvider.java


    そして私たちはViewModelProviderに入りましたjava:
    public class ViewModelProvider {
    
        private static final String DEFAULT_KEY = "android.arch.lifecycle.ViewModelProvider.DefaultKey";
    
        private final Factory mFactory;
        private final ViewModelStore mViewModelStore;
    
        public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
            mFactory = factory;
            this.mViewModelStore = store;
        }
    
        @NonNull
        @MainThread
        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.
                }
            }
    
            viewModel = mFactory.create(modelClass);
            mViewModelStore.put(key, viewModel);
            //noinspection unchecked
            return(T) viewModel;
        }
    
     
        public static class NewInstanceFactory implements Factory {
    
            @SuppressWarnings("ClassNewInstance")
            @NonNull
            @Override
            public  T create(@NonNull Class  modelClass) {
                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);
                }
            }
        }
    
    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;
            }
    ···
    }
    
    

    このクラスは主にデータを取得するために使用され、主に2つの方法があります.
  • 構築方法:ViewModelStroe,Factoryに転送されます.Factory:AndroidViewModelFactory、静的内部クラス、NewInstanceFactoryを継承し、主にModelView.Classインスタンス化
  • getメソッド:ViewModelStoreにViewModelを指定するインスタンスがあるかどうかを確認し、すでにある場合はViewModelStoreから取り、ない場合はFactoryでインスタンス化します.

  • ここで、最初の質問の答えを理解しました.本当のデータはViewModelStroeに格納され、ViewModelProviderを通じてアクセスされます.

    3. ViewModelStores.java


    次に、ViewModelStoresのofメソッドを見てみましょう.ここでは、Activityの再構築時にViewModelが破壊されない手がかりを見つけることができます.
    @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if(activity instanceof ViewModelStoreOwner) {
            return((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }
    

    最後の文で、転送activityはholderFragmentからViewModelStoreを取り出します.

    4. HolderFragment.java


    私たちはこのHolderFragmentを注文します.java :
    public HolderFragment() {
        setRetainInstance(true);
    }
    
    

    Activityが構成変更のために再構築を破棄した場合、fragmentはonDestroy()を実行せずに保持されるという意味のこの方法を見つけました.これはFragmentの使い方で、私たちは知っていればいいのですが、Fragmentに入って真相を探りたいなら、ここに推薦リンクがあります.

    5. HolderFragmentManager


    そして私たちはHolderFragmentにいます.JAvaはHolderFragmentManagerという静的内部クラスを発見しました:
    static class HolderFragmentManager{
        private Map mNotCommittedActivityHolders = new HashMap<>();
        private Map mNotCommittedFragmentHolders = new HashMap<>();
    ...
    

    ここで、HolderFragmentManagerは、ActivityとholderFragmentのマッピングを完了するために、キー値ペアでActivityとholderFragmentを格納していることがわかります.2つ目の質問の答えは、ViewModelStroeがholderFragmentと連絡を取り、holderFragmentがActivityの再構築時にViewModelStroeが破棄されないことを保証していることも理解できるだろう.

    まとめ:


    一図は千言に勝つ.
    ViewModelの実現原理:1.本物のデータはViewModelStroeに格納され、ViewModelProviderによってアクセスされます.ViewModelStroeはholderFragmentと連絡を取り、holderFragmentはActivityの再構築時にViewModelStroeが破棄されないことを保証した.
    -----------------------------------------------------------分割線の終了

    END


    私はレイガです.もし私の文章が好きなら、あなたの称賛を残してください.質問やアドバイスがあれば、コメントエリアに私のGithubを残してください.注目してください.
    Jetpackコラム:Lifecycle:Jetpackシリーズ-Lifecycle入門からソースコードViewModel:Jetpackシリーズ-ViewModel入門からソースコードLive Data:Jetpackシリーズ-Live Data入門からソースコードPalette:Jetpackシリーズ-Palette入門ガイド
    --------------------------------------- The End