Android MVVM架构

Posted 绵绵思远道~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android MVVM架构相关的知识,希望对你有一定的参考价值。

1.MVC,MVP,MVVVM

1.1什么是MVVM

1.MVVM,是Model-View-ViewModel的简写,是M-V-VM三部分组成。它本质上就是MVC 的改进 版。MVVM 就是将其中的View 的状态和行为抽象化,其中ViewModel将视图 UI 和业务逻辑分 开,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。

2.MVVM采用双向数据绑定,view中数据变化将自动反映到viewmodel上,反之,model中数据变化 也将会自动展示在页面上。把Model和View关联起来的就是ViewModel。ViewModel负责把Model 的数据同步到View显示出来,还负责把View的修改同步回Model。

3.MVVM核心思想,是关注model的变化,让MVVM框架利用自己的机制自动更新DOM,也就是所 谓的数据-视图分离,数据不会影响视图。

如图(重点):

 

 1.2 android MVVM

MVVM分为Model,View,ViewModel 三个部分

Model:数据层,包含数据实体和对数据实体的操作,和MVP的model没有区别。

View: 界面层,对应于Activity,XML,负责数据显示以及用户交互。相比MVP的view,这里面的 view视图数据一般是在xml中使用DataBinding进来双向绑定数据的。    

ViewModel:关联层,作为中间桥梁 去通知model数据层处理数据业务,并将结果回调给 UI 层处 理 UI 逻辑。ViewModel中只有activity持有vm引用,vm是不持有view的引用的,所以vm的构造方 法中不能传入视图相关的对象。所以重点在于怎么通知view,可以通过观察者回调的方式。但是现 在一般是结合Jetpack来进行view的更新的。

2.MVVM项目框架

如图:

 3 MVVM核心构成

3.1 DataBinding (重点)(详细讲解)

数据和UI双向绑定

3.2 LifeCycle

界面生命周期感知

3.3 ViewModel

业务逻辑

3.4 LiveData

可观察数据项

3.5 MVVM项目框架搭建 通用性的快速开发框架 可应用各项目中

4 DataBinding

4.1 DataBinding的简介

1.Data binding 在2015年7月发布的android Studio v1.3.0 版本上引入,在2016年4月Android Studio v2.0.0 上正式支持。目前为止,Data Binding 已经支持双向绑定了,实在2016年的google I/O大会上发布的。现在已经很普及啦,在项目中也在慢慢使用。

 2.I/O大会上发布的。现在已经很普及啦,在项目中也在慢慢使用。 Databinding 是一个实现数据和UI绑定的框架,是一个实现 MVVM 模式的工具,有了 Data Binding,在Android中也可以很方便的实现MVVM开发模式。会java web开发的会更好的理解在 xml中绑定数据的模式,在web开发中也是使用@来实现数据的显示的。

3.Data Binding 是一个support库,最低支持到Android 2.1(API Level 7+)。使用 DataBing,Gradle的Android Plugin需要在1.5.0-alpha1以上。

4.Data Binding 之前我们不可避免地要编写大量的毫无营养的代码,如 findViewById()、 setText(),setVisibility(),setEnabled() 或 setOnClickListener() 等,通过 Data Binding , 我们可 以通过声明式布局以精简的代码来绑定应用程序逻辑和布局,这样就不用编写大量的毫无营养的代 码了。

缺点:

1. ViewModel与View一一对应;

2. 使用起来灵活性比较低;

3. Model属性发生变化时,ViewDatabinding采用异步更新数据,对于现实大量数据的ListView,会 有一定延迟,在实践测试中发现,Databing效率较低,对于负责的界面不太适用;

4. 自动生成大量代码和属性字段:ViewDataBinding 实现类 DataBinderMapper 等。

4.2使用DataBinding

具体步骤:

1.构建环境(Build Environment)

在build.gradle中添加如下代码:

 

dataBinding 
        enabled = true
    

2. 数据绑定布局文件

数据绑定的布局文件和我们以前经常写的布局文件稍有不同,并从布局的根标记开始,后面依次是数据 元素和视图根元素,即根布局是 layout,接下来是 data 节点,variable 节点,示例如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.hp.mvvmdemo.model.User" />
        <variable
            name="handler"
            type="com.hp.mvvmdemo.view.activity.MainActivity.Handler" />
    </data>

    <LinearLayout
        android:gravity="center"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/tv_show"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />

        <TextView
            android:layout_marginTop="30dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:text="@user.username" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:text="@user.password" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@()->handler.onClick()"/>

    </LinearLayout>


</layout>

3.在MainActivity.java中获取bingding对象

@Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
       

    

4.4databinding点击事件

xml:

 

 activity:

 5.单向绑定

5.1单个值的绑定:

1.集成DataDinding 添加 dataBinding.enabled true 和 dataBinding enabled = true

android 
....
dataBinding 
enabled = true

2.实现xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <variable
        name="title"
        type="java.lang.String" />
</data>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:textColor="#000000"
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@title"/>
</LinearLayout>
</layout>

5.2对象的绑定:

1.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <variable name="user" type="com.example.User"/>
</data>
<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@user.firstName"/>
    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@user.lastName"/>
</LinearLayout>
</layout>

2.实体类:

public class User 
    private final String firstName;
    private final String lastName;
    public User(String firstName, String lastName) 
        this.firstName = firstName;
        this.lastName = lastName;
    
    public String getFirstName() 
        return this.firstName;
    
    public String getLastName() 
        return this.lastName;
    

3.databinding:

@Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        MainActivityBinding binding = DataBindingUtil.setContentView(this,
                R.layout.main_activity);
        User user = new User("Test", "User");
        binding.setUser(user);
    

6.双向绑定

双向绑定是指其中任意一个变化后都会同步更新到另一个。双向绑定使用@=表达式来实现 4.5.1 目前已经支持双向绑定的列表

 7.从网页获取图片

整体架构:

 

导入要使用的依赖:

build.gradle        

implementation 'com.squareup.picasso:picasso:2.71828'
    //for rxjava
    implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
    //for rxandroid
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    //衔接 Retrofit & RxJava,此处一定要注意使用RxJava2的版本
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
    //添加Retrofit依赖
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    //添加Gson解析
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    //添加图片加载库依赖
    implementation 'com.github.bumptech.glide:glide:4.12.0'

xml层:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="imgurl"
            type="String" />
        <variable
            name="localurl"
            type="int" />
        <variable
            name="mainviewmodel"
            type="com.hp.day48stage01.viewmodel.MainViewModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".view.activity.MainActivity">

        <ImageView
            app:netImage="@imgurl"
            app:localImage="@localurl"
            android:id="@+id/imageView"
            android:layout_width="300dp"
            android:layout_height="300dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@drawable/ic_launcher_background" />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="64dp"
            android:text="点击见美女"
            android:onClick="@()->mainviewmodel.onChange()"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="@+id/imageView"
            app:layout_constraintHorizontal_bias="0.495"
            app:layout_constraintStart_toStartOf="@+id/imageView"
            app:layout_constraintTop_toBottomOf="@+id/imageView"
            app:layout_constraintVertical_bias="0.029" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

编写实体类:

ModelGril.java

public class ModelGirl 
    private boolean success;
    private String imgurl;
    private Info info;

    @Override
    public String toString() 
        return "MobileGirl" +
                "success=" + success +
                ", imgurl='" + imgurl + '\\'' +
                ", info=" + info +
                '';
    

    public boolean isSuccess() 
        return success;
    

    public void setSuccess(boolean success) 
        this.success = success;
    

    public String getImgurl() 
        return imgurl;
    

    public void setImgurl(String imgurl) 
        this.imgurl = imgurl;
    

    public Info getInfo() 
        return info;
    

    public void setInfo(Info info) 
        this.info = info;
    

    public static class Info
        private int width;
        private int height;
        private String type;

        public int getWidth() 
            return width;
        

        public void setWidth(int width) 
            this.width = width;
        

        public int getHeight() 
            return height;
        

        public void setHeight(int height) 
            this.height = height;
        

        public String getType() 
            return type;
        

        public void setType(String type) 
            this.type = type;
        
    

编写网络接口:

public interface GetImage_Interface 
    @GET("api/mobil.girl?type=json")
    Observable<ModelGirl> getPic();

适配器:

public class ImageBindingAdapter 



    @BindingAdapter(value = "netImage","localImage",requireAll = false)
    public static void setImage(ImageView imageView,String url,int path)

        if (url != null && !"".equals(imageView)) 
            /*Glide.with(imageView.getContext())
                    .load(url)
                    .override(300,300)
                    .centerCrop()
                    .into(imageView);*/
            Picasso.get().load(url).placeholder(R.mipmap.jiazai).into(imageView);
        else 
            imageView.setImageResource(path);
        
    

MainViewModel
public class MainViewModel 
    private String imgurl;

    private ModelGirl modelGirl;

    private ActivityMainBinding binding;

    private final String TAG = "MainViewModel";

    public ModelGirl getModelGirl() 
        return modelGirl;
    

    public void setModelGirl(ModelGirl modelGirl) 
        this.modelGirl = modelGirl;
    

    public MainViewModel() 

    

    public MainViewModel(ActivityMainBinding binding,String imgurl) 
        this.binding = binding;
        this.imgurl = imgurl;
        initGirl();
    

    private void initGirl() 

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.vvhan.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        GetImage_Interface request = retrofit.create(GetImage_Interface.class);

        Observable<ModelGirl> observable = request.getPic();

        observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<ModelGirl>() 
                    @Override
                    public void accept(ModelGirl modelGirl) throws Exception 
                        Log.i(TAG,"连接成功");
                        //Log.i(TAG, modelGirl.toString());
                        imgurl = modelGirl.getImgurl();
                        binding.setImgurl(imgurl);
                    
                , new Consumer<Throwable>() 
                    @Override
                    public void accept(Throwable throwable) throws Exception 
                        Log.i(TAG,"连接失败");
                    
                );
    
    public void onChange()
        initGirl();
    

MainActivity:
public class MainActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);

        binding.setMainviewmodel(new MainViewModel(binding,""));

    

Android 基于Jetpack的MVVM架构入门指南

Android 基于Jetpack的MVVM架构入门指南

目录

Android MVVM架构入门指南

架构组件简介

View Binding

简介

View Binding 的优点

用法

ViewModel

简介

ViewModel 的生命周期

用法

LiveData

简介

LiveData 的优势

LiveData的子类

用法

Data Binding

简介

用法

Lifecycles

简介

主要类

用法

实现了MVVM架构的Demo


架构组件简介

Android 架构组件是一系列的组件库,可以帮助我们设计稳健、可测试且易维护的应用,主要和常用的组件包括:View Binding 、Data Binding、Lifecycle、LiveData、ViewModel

如上Android架构图,有四部分主要组件,每个组件都有其责任

  • Activity 和Fragment代表的View层,不处理业务逻辑只显示视图、处理用户交互、观察数据并展示

  • ViewModel+LiveData层,ViewModel向数据仓库Repository中请求数据,然后通过LiveData发送到View层

  • Repository层,它不是一个Android组件,而是一个普通的类,它负责从所有可用的源来获取数据

  • 数据层,提供数据源的一层,主要是网络请求框架、Room数据库等

View Binding

简介

主要作用是为了替代 findViewById 在启动View Binding后,系统会为该模块下的每个xml生成一个绑定类,这个类的类名是以xml布局文件名去掉下划线后,单词首字母大写加上Binding命名的。

例如:activity_main.xml ---> ActivityMainBinding

View Binding 的优点

  • 空安全:View Binding会创建对视图的直接引用,因此不存在因视图 ID 无效而引发空指针异常的风险。(我们手动findeViewById()时容易传入无效的id)

  • 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。 val button:Button=findViewById<RecyclerView>(R.id.recyclerview)

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/view-binding

用法

View Binding 按模块启用,在app模块下的build.gradle添加如下配置

android {
        ...
        viewBinding {
            enabled = true
        }
    }
    
    或者使用buildFeatures方式但需要满足如下条件:
    1、Android Studio 版本 : 最低 4.1
    2、Gradle 版本 : 最低版本 6.6.1
    3、Gradle 插件版本配置 : 最低版本 4.1.0
    
    android {
      ...
       buildFeatures {
          viewBinding true
      }
}
​
    

如果想忽略某个布局文件,可将 tools:viewBindingIgnore="true" 属性添加到相应布局文件的根视图

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:viewBindingIgnore="true"
    tools:context=".MainActivity">
    
       <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
        
        ...
</androidx.constraintlayout.widget.ConstraintLayout>
    

1. Activity中使用View Binding

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Activity 使用

  2. 通过调用 getRoot() 方法获取对根视图的引用

  3. 调用 setContentView()方法将根视图传入

    private lateinit var binding: ActivityMainBinding
​
    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }
    binding.name.text ="Wilfried"
    binding.name.setOnClickListener {  }

2. Fragment中使用View Binding

Fragment中使用View Binding的步骤根Activity类似,主要区分在于是在哪个方法中进行绑定操作onCreateView、onViewCreated

onCreateView

    lateinit var binding: FragmentMainBinding
​
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentMainBinding.inflate(inflater, container, false)
        return binding.root
    }

onViewCreated

    lateinit var binding: FragmentMainBinding
​
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding = FragmentMainBinding.bind(view)
    }

3. ListAdapter中使用View Binding

​
class MyListAdapter : ListAdapter<Word, MyListAdapter.WordViewHolder>(WORDS_COMPARATOR) {
​
    class WordViewHolder(private val viewBinding: RecyclerviewItemBinding) :
        RecyclerView.ViewHolder(viewBinding.root) {
​
        fun bind(text: Word?) {
            viewBinding.textView.text = text?.word
        }
​
        companion object {
            fun create(parent: ViewGroup): WordViewHolder {
                val bind = RecyclerviewItemBinding
                    .bind(LayoutInflater.from(parent.context)
                            .inflate(R.layout.recyclerview_item, parent, false)
                    )
                return WordViewHolder(bind)
            }
        }
    }
​
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
        return WordViewHolder.create(parent)
    }
​
    override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
​
    companion object {
        private val WORDS_COMPARATOR = object : DiffUtil.ItemCallback<Word>() {
            override fun areItemsTheSame(oldItem: Word, newItem: Word): Boolean {
                return oldItem === newItem
            }
​
            override fun areContentsTheSame(oldItem: Word, newItem: Word): Boolean {
                return oldItem.word == newItem.word
            }
        }
    }
}

ViewModel

简介

ViewModel 主要是用来存储和管理与UI相关的数据的,将一个Activity或Fragment组件相关的数据逻辑提取出来,并能适配UI组件的生命周期,如当屏幕旋转Activity重建后,ViewModel中的数据依然有效 ViewModel一般都是会配合LiveData使用

ViewModel 的生命周期

ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle(Activity、Fragment、所有实现LifecycleOwner的生命周期对象)

我们在首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统有可能会在 Activity 的整个生命周期内多次调用 onCreate(),但是ViewModel只要一个,比如在旋转设备屏幕时重走生命周期,此时依然可以拿到ViewModel中最新的数据。

由上图可以看出,ViewModel 存在的时间范围是从首次请求 ViewModel 直到 Activity 完成并销毁。

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/viewmodel

用法

定义ViewModel

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }
​
    fun getUsers(): LiveData<List<User>> {
        return users
    }
​
    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
        ...
        users.value = usersInfoList 
    }
}

Activity 中使用ViewModel

1. 默认实现方式

class MyActivity : AppCompatActivity() {
​
    override fun onCreate(savedInstanceState: Bundle?) {
​
        val viewModel = ViewModelProviders
                .of(this).get(MainViewModel::class.java)
        
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}
​

2. 通过activity-ktx扩展库

class MyActivity : AppCompatActivity() {
​
    override fun onCreate(savedInstanceState: Bundle?) {
        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val model: MyViewModel by viewModels()
        
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}
​

3. 过activity-ktx扩展库,自定义 ViewModelProvider.Factory(用于ViewModel需要传参数的情况)

  • MainActivity

​
class MyActivity : AppCompatActivity() {
​
    override fun onCreate(savedInstanceState: Bundle?) {
​
    private val wordViewModel: WordViewModel by viewModels {
        WordViewModelFactory((application as WordsApplication).repository)
    }
        
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}
​
  • WordViewModel

class WordViewModel(private val repository: WordRepository) : ViewModel() {
​
    val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()
​
    fun insert(word: Word) = viewModelScope.launch {
        repository.insert(word)
    }
}
class WordViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(WordViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return WordViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
​

LiveData

简介

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者

LiveData 的优势

  1. 观察者会绑定到 Lifecycle 对象(所有实现了所有实现LifecycleOwner的生命周期对象),并在其关联的生命周期遭到销毁后进行自我清理。

  2. 如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

  3. 如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

LiveData的子类

MutableLiveData

实现接口LiveData,并提供两个方法 setValue(T) 和 postValue(T) 用于将数据分发出去

setValue(T) 只能在主线程中调用,postValue(T) 可以在任何线程中调用。

MediatorLiveData

继承自MutableLiveData,它可以作为中间人的角色监听其他LiveData,通过addSource进行注册LiveData,当数据更新时通过MediatorLiveData倒一手再进行处理。提供如下两个方法,

addSource(LiveData< S> source, Observer<? super S> onChanged)方法

removeSource(LiveData< S> toRemote)方法

案例:如果我们只需要liveData1发出的10个值,并将其合并到liveDataMerger中。收到10个值之后,我们就停止监听liveData1并将其删除。

 liveDataMerger.addSource(liveData1, new Observer() {
      private int count = 1;
​
      @Override public void onChanged(@Nullable Integer s) {
          count++;
          liveDataMerger.setValue(s);
          if (count > 10) {
              liveDataMerger.removeSource(liveData1);
          }
      }
 });

 

 

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/livedata

用法

  1. 创建 LiveData 的实例以存储某种类型的数据。这通常在 ViewModel 类中完成。

class MyViewModel : ViewModel() {
​
    // Create a LiveData with a String
    val livedata: MutableLiveData<String> =  MutableLiveData<String>()
    
    }
}
​
  1. 在界面控制器(如 Activity 或 Fragment)中创建 Observer 对象。

  2. 使用 observe() 方法将 Observer 对象附加到 LiveData 对象。

        myViewModel.livedata.observe(this, Observer {
​
        })
  1. 可以使用 observeForever(Observer) 方法在没有关联的 LifecycleOwner 对象的情况下注册一个观察者。在这种情况下,观察者会被视为始终处于活跃状态

        myViewModel.livedata.observeForever {
​
        }

5.更新LiveData:LiveData 是一个抽象类,MutableLiveData 类将公开 setValue(T) 和 postValue(T) 方法,修改存储在 LiveData 对象中的值,则必须使用这些方法。

 

Data Binding

简介

Data Binding 简单的解释就是,之前我们需要通过id获取到控件,然后通过控件设置数据,现在有了Data Binding之后 我们可以直接在布局文件中直接绑定数据,它的侧重点是在绑定数据

注意:在许多情况下, Data Binding可简化实现,提高性能,提供与数据绑定相同的好处。如果使用数据绑定的主要目的是取代 findViewById() 调用,请考虑改用 View Binding。

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/data-binding

用法

  1. 使用 Data Binding ,首先需要在 app moudle 下的 build.gradle 中添加:

android {
    ...
    dataBinding {
        enabled = true
    }
    ...
}
  1. 创建一个实体类,例:Info 类

    class Info(var name: String = "Wilfried",
               var job: String = "Android Engineer",
               var company: String = "noboAuto")

3.在这个Activity的xml(activity_my_info.xml)中的根布局下,通过Alt+Enter快捷键创建databinding的布局,同时导入 Info 类

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
​
    <data>
​
        <variable
            name="myInfo"
            type="com.noboauto.example.Info" />
    </data>
​
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.slideshow.SlideshowFragment">
​
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{myInfo.name}"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  1. 此时编译器会自动根据这个布局生成相应的绑定类,这里会生成一个 ActivityMyInfoBinding 的类,然后在对应的Activity获取Data Binding并绑定数据

class MyInfoActivity : AppCompatActivity() {
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
​
        val bindingBinding : ActivityMyInfoBinding = DataBindingUtil.setContentView(this, R.layout.activity_my_info)
        bindingBinding.myInfo = Info()//已实现默认参数
    }
}

Lifecycles

简介

生命周期感知型组件可执行操作来响应另一个组件(如 Activity 和 Fragment)的生命周期状态的变化

生命周期拥有者LifecycleOwner生命周期的观察者LifecycleObserver之间快速方便的建立一种联系。在生命周期拥有者的生命周期变化时,观察者会收到对应的通知。

简单的说就是用来监听Activity与Fragment的生命周期变化

主要类

lifecycleRegister:

lifecycle的唯一子类,在生命周期拥有者的生命周期发生变化时触发自身状态和相关观察者的订阅回调逻辑。

lifecycleOwner

该接口的实现类可以提供lifecycleRegister的实例,主要实现类就是AppCompatActivity和Fragment。 (android.app.Activity.Activity默认不实现lifecycleOwner接口,所以推荐使用AppCompatActivity)

lifecycleObserver

该接口的实现类表示为关注生命周期事件的观察者。

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/lifecycle

用法

生命周期拥有者 LifecycleOwner

  • AppCompatActivity的实现原理是在的父类ComponentActivity中实现了LifecycleOwner,并创建和提供了LifecycleRegistry

​
    private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
​
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }

在ComponentActivity的子类ComponentActivity中实现了事件的分发

​
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
        ...
        mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
​
    }

枚举中的方法跟Activity生命周期一致,注意ON_ANY只要生命周期变化就会调用此方法

        public enum Event {
        /**
         * Constant for onCreate event of the {@link LifecycleOwner}.
         */
        ON_CREATE,
        /**
         * Constant for onStart event of the {@link LifecycleOwner}.
         */
        ON_START,
        /**
         * Constant for onResume event of the {@link LifecycleOwner}.
         */
        ON_RESUME,
        /**
         * Constant for onPause event of the {@link LifecycleOwner}.
         */
        ON_PAUSE,
        /**
         * Constant for onStop event of the {@link LifecycleOwner}.
         */
        ON_STOP,
        /**
         * Constant for onDestroy event of the {@link LifecycleOwner}.
         */
        ON_DESTROY,
        /**
         * An {@link Event Event} constant that can be used to match all events.
         */
        ON_ANY
    }
​
​
  • 自定义LifecycleOwner

实现LifecycleOwner接口,重写getLifecycle方法返回Lifecycle的唯一子类LifecycleRegistry

markState和handleLifecycleEvent都可以将生命周期状态分发出去,但是markState已经是过时的方法,建议使用handleLifecycleEvent

class MyActivity : Activity(), LifecycleOwner {
​
    private lateinit var lifecycleRegistry: LifecycleRegistry
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
​
        lifecycleRegistry = LifecycleRegistry(this)
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
​
        lifecycle.addObserver(MyInfoObserver())
        lifecycle.addObserver(MyInfoObserver2())
    }
​
    override fun onStart() {
        super.onStart()
        lifecycleRegistry.markState(Lifecycle.State.STARTED)
    }
​
    override fun onResume() {
        super.onResume()
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
​
    }
​
    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

生命周期的观察者 LifecycleObserver

两种方式可以实现生命周期的观察

  1. 实现LifecycleObserver接口,通过OnLifecycleEvent注解来回调各个生命周期状态

class MyInfoObserver : LifecycleObserver {
​
    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onMyInfoCreate() {
        Log.i("wilfried", "onCreate")
    }
​
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onMyInfoStart() {
        Log.i("wilfried", "onStart")
    }
​
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onMyInfoResume() {
        Log.i("wilfried", "onResume")
    }
}
  1. 实现LifecycleEventObserver接口,通过回调方法onStateChanged来实现状态监听(本身也是实现了LifecycleObserver)

public interface LifecycleEventObserver extends LifecycleObserver {
​
    void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event);
}
​
class MyInfoObserver2 : LifecycleEventObserver {
    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        Log.i("wilfried", "LifecycleOwner==${source} Event==${event.name} ")
​
    }
}

最终打印结果如下:

2021-05-27 20:37:48.820 28878-28878/com.example.android.roomwordssample I/wilfried: onCreate
2021-05-27 20:37:48.821 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_CREATE 
2021-05-27 20:37:48.839 28878-28878/com.example.android.roomwordssample I/wilfried: onStart
2021-05-27 20:37:48.839 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_START 
2021-05-27 20:37:48.841 28878-28878/com.example.android.roomwordssample I/wilfried: onResume
2021-05-27 20:37:48.841 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_RESUME 

实现了MVVM架构的Demo

Demo下载地址(更新中)

以上是关于Android MVVM架构的主要内容,如果未能解决你的问题,请参考以下文章

在 MVVM 架构 Android 中启动服务的正确位置是啥

Android 应用程序架构 - MVVM 还是 MVC?

Android MVVM 架构应用实现

Android 基于Jetpack的MVVM架构入门指南

用 kotlin 学习 Android MVVM 架构组件

Android | Android应用架构之MVVM模式