Android MVVM架构

背景

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

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 。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容