Android MVVM架构

作者: jimdear | 来源:发表于2022-12-29 17:11 被阅读0次

    背景

    温故而知新,可以为师矣。

    MVVM的概念及理解

    要说MVVM架构,该架构的好处,个人觉得就是抽离出了一个叫VM 的概念,结合DataBinding,可以把代码做一个比较优雅的处理。另外,对于lifecycle,也可统一处理,尽可能地避免了无谓的内存泄漏,是个不错的架构设计思想。

    Databinding

    开启databindg 很简单,在build 文件中这样设置即可:

     dataBinding{
            enabled true
        }
    
    

    开启之后,就可以用到dataBinding的相应的内容了,然后,在你需要用到的xml 文件中,这样设置,例如:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:tools="http://schemas.android.com/tools"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
       <data>
    <import type="com.jimdear.wxcode.bean.TestBean"></import>
    <variable
       name="test"
       type="TestBean" />
       </data>
    <androidx.constraintlayout.widget.ConstraintLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       tools:context=".RxDemoActivity">
    
       <TextView
           android:id="@+id/tv_content"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{test.name}"
           android:textSize="17sp"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintStart_toStartOf="parent" />
    
       <Button
           android:id="@+id/btn_sumbit"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="sumbit the data change"
           app:layout_constraintBottom_toBottomOf="parent"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintTop_toBottomOf="@+id/tv_content" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    

    此处,在定义玩layout 后, 可以在data 标签里 import 相应的类,然后在xml 中,采用@{}的方式进行调用,例如,这里举例用的android:text="@{test.name}"。此处在xml中就可以映射使用test 这个类里面的属性。
    定义好这些之后,在activity 或fragment中,就可以使用。具体,以代码为例:

        ActivityRxDemoBinding activityRxDemoBinding = DataBindingUtil.setContentView(this, R.layout.activity_rx_demo);
           activityRxDemoBinding.tvContent.setText("Nice Test");
           TestBean testBean = new TestBean();
           testBean.setName("good is My Name3");
           activityRxDemoBinding.setTest(testBean);
       
    

    此处 DataBindingUtil.setContentView(this, R.layout.activity_rx_demo);得到一个binding 对象,这个对象,就包含了我们在xml中定义的组件,我们可以通过这个对象,找到相应的组件,到这一步,就是所谓的单向绑定,类似buttonknife的功能。

    双向绑定,ViewModule

    此处,我的理解是这样的,VM层和View层 有双向绑定关系,当View层发生变动,会带动VM层做相应的逻辑处理后,VM层,再把数据刷新传递给View层,完成一个双向绑定,数据刷新的一个操作。那么这个操作要怎么完成呢?

    首先,我们观测源码,看看google 是否已经定义好,所以,直接打开ViewModule 这个类:

    /*
    * Copyright (C) 2017 The Android Open Source Project
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    *      http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
    
    package androidx.lifecycle;
    
    import androidx.annotation.MainThread;
    import androidx.annotation.Nullable;
    
    import java.io.Closeable;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
    * ViewModel is a class that is responsible for preparing and managing the data for
    * an {@link android.app.Activity Activity} or a {@link androidx.fragment.app.Fragment Fragment}.
    * It also handles the communication of the Activity / Fragment with the rest of the application
    * (e.g. calling the business logic classes).
    * <p>
    * A ViewModel is always created in association with a scope (an fragment or an activity) and will
    * be retained as long as the scope is alive. E.g. if it is an Activity, until it is
    * finished.
    * <p>
    * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
    * configuration change (e.g. rotation). The new instance of the owner will just re-connected to the
    * existing ViewModel.
    * <p>
    * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
    * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
    * ViewModel. ViewModels usually expose this information via {@link LiveData} or Android Data
    * Binding. You can also use any observability construct from you favorite framework.
    * <p>
    * ViewModel's only responsibility is to manage the data for the UI. It <b>should never</b> access
    * your view hierarchy or hold a reference back to the Activity or the Fragment.
    * <p>
    * Typical usage from an Activity standpoint would be:
    * <pre>
    * public class UserActivity extends Activity {
    *
    *     {@literal @}Override
    *     protected void onCreate(Bundle savedInstanceState) {
    *         super.onCreate(savedInstanceState);
    *         setContentView(R.layout.user_activity_layout);
    *         final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
    *         viewModel.userLiveData.observer(this, new Observer<User>() {
    *            {@literal @}Override
    *             public void onChanged(@Nullable User data) {
    *                 // update ui.
    *             }
    *         });
    *         findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
    *             {@literal @}Override
    *             public void onClick(View v) {
    *                  viewModel.doAction();
    *             }
    *         });
    *     }
    * }
    * </pre>
    *
    * ViewModel would be:
    * <pre>
    * public class UserModel extends ViewModel {
    *     private final MutableLiveData&lt;User&gt; userLiveData = new MutableLiveData&lt;&gt;();
    *
    *     public LiveData&lt;User&gt; getUser() {
    *         return userLiveData;
    *     }
    *
    *     public UserModel() {
    *         // trigger user load.
    *     }
    *
    *     void doAction() {
    *         // depending on the action, do necessary business logic calls and update the
    *         // userLiveData.
    *     }
    * }
    * </pre>
    *
    * <p>
    * ViewModels can also be used as a communication layer between different Fragments of an Activity.
    * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
    * communication between Fragments in a de-coupled fashion such that they never need to talk to
    * the other Fragment directly.
    * <pre>
    * public class MyFragment extends Fragment {
    *     public void onStart() {
    *         UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
    *     }
    * }
    * </pre>
    * </>
    */
    public abstract class ViewModel {
       // Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
       @Nullable
       private final Map<String, Object> mBagOfTags = new HashMap<>();
       private volatile boolean mCleared = false;
    
       /**
        * This method will be called when this ViewModel is no longer used and will be destroyed.
        * <p>
        * It is useful when ViewModel observes some data and you need to clear this subscription to
        * prevent a leak of this ViewModel.
        */
       @SuppressWarnings("WeakerAccess")
       protected void onCleared() {
       }
    
       @MainThread
       final void clear() {
           mCleared = true;
           // Since clear() is final, this method is still called on mock objects
           // and in those cases, mBagOfTags is null. It'll always be empty though
           // because setTagIfAbsent and getTag are not final so we can skip
           // clearing it
           if (mBagOfTags != null) {
               synchronized (mBagOfTags) {
                   for (Object value : mBagOfTags.values()) {
                       // see comment for the similar call in setTagIfAbsent
                       closeWithRuntimeException(value);
                   }
               }
           }
           onCleared();
       }
    
       /**
        * Sets a tag associated with this viewmodel and a key.
        * If the given {@code newValue} is {@link Closeable},
        * it will be closed once {@link #clear()}.
        * <p>
        * If a value was already set for the given key, this calls do nothing and
        * returns currently associated value, the given {@code newValue} would be ignored
        * <p>
        * If the ViewModel was already cleared then close() would be called on the returned object if
        * it implements {@link Closeable}. The same object may receive multiple close calls, so method
        * should be idempotent.
        */
       <T> T setTagIfAbsent(String key, T newValue) {
           T previous;
           synchronized (mBagOfTags) {
               //noinspection unchecked
               previous = (T) mBagOfTags.get(key);
               if (previous == null) {
                   mBagOfTags.put(key, newValue);
               }
           }
           T result = previous == null ? newValue : previous;
           if (mCleared) {
               // It is possible that we'll call close() multiple times on the same object, but
               // Closeable interface requires close method to be idempotent:
               // "if the stream is already closed then invoking this method has no effect." (c)
               closeWithRuntimeException(result);
           }
           return result;
       }
    
       /**
        * Returns the tag associated with this viewmodel and the specified key.
        */
       @SuppressWarnings("TypeParameterUnusedInFormals")
       <T> T getTag(String key) {
           //noinspection unchecked
           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);
               }
           }
       }
    }
    
       
    

    Emu,确实,都已经把例子都给我们举出来了,按着这模版来写,就是一个典型的MVVM架构,那我们来看看这里的VM层吧,此处的 viewModel.userLiveData.observer ,就是一个观察者,在实时观测 由VM层处理后返回的的一个对象,而在View层中 点击事件中,viewModel.doAction(); 触发了相应的逻辑变动,从而刷新userLiveData,给该对象重新赋值,此时data就变了,变了之后,观察者相应了相应变化,开始刷新相应的数据。 这一步,就完成了相应的双向操作。

    采用ObservableField 的方式来进行双向绑定

    当我们的bean数据发生改变的时候,我们可以通过ObservableFileld 来刷新我们在之前定义在TextView中的name 属性,做到,点击之后,刷新bean 对象,相应的TextView 的text内容发生改变,而不是通过代码去书写。贴下相应代码:

    public class ObserDataBean {
      public ObservableField<String> testName = new ObservableField<>();
    
      public ObserDataBean(String testName) {
        this.testName.set(testName);
      }
    
      public ObservableField<String> getTestName() {
         return testName;
      }
    
      public void setTestName(ObservableField<String> testName) {
         this.testName = testName;
      }
    }
    
    

    然后,我们在View层,定义好相应的databinding:

            ActivityRxDemoBinding activityRxDemoBinding = DataBindingUtil.setContentView(this, R.layout.activity_rx_demo);
           activityRxDemoBinding.tvContent.setText("Nice Test");
           ObserDataBean obserDataBean = new ObserDataBean("Name One");
           activityRxDemoBinding.setTest(obserDataBean);
    
    

    然后在点击的时候,进行设值

       activityRxDemoBinding.btnSumbit.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
               obserDataBean.testName.set("Name Two");
               }
           });
    
    

    效果: 当点击的时候,该值改变了,顺带把textview 进行了一次赋值。十分智能。其原理也是采用了观察者模式,对值进行了动态的一个监听。

    原理分析:

    基本上,到这,就知道MVVM是个什么东东了。下一个章节,我们再深入研究下ViewModule、ViewProvider 和livedata 。

    相关文章

      网友评论

        本文标题:Android MVVM架构

        本文链接:https://www.haomeiwen.com/subject/csdzqdtx.html