Android ViewModel实现和原理

ViewModel实现和原理

  • 前言
  • [1. 使用](#1. 使用)
    • [1.1 gradle准备](#1.1 gradle准备)
    • [1.2 模拟场景](#1.2 模拟场景)
    • [1.3. LiveData和ViewModel](#1.3. LiveData和ViewModel)
    • [1.4 更新数据](#1.4 更新数据)
  • [2. 原理与源码解读](#2. 原理与源码解读)
    • [2.1 添加观察者](#2.1 添加观察者)
    • [2.2 setValue](#2.2 setValue)
    • [2.3 post](#2.3 post)
  • 参考资料

前言

ViewModel的主要基于观察者的设计模式,他主要分为两个部分:

  1. 提供者Provider:在我们这里就是数据的提供者,当实体的数据改变的时候,会自动通知所有观察者,观察者收到通知后就可以做对应的数据。
  2. 观察者Observer:观察者注册提供程序,当提供者每次发送通知的时候,观察者就会做对应的处理。

其最终实现出来的效果就是,在代码中,一旦我们注册的实体对象里面的数据改变之后,对应的UI就会自动的进行更新,这样数据更新的代码我们只需要在观察者里面写一套,不需要反复写多套了。

提供者和观察者是一对多的关系,也就是一个提供者可以被很多个观察者注册

1. 使用

1.1 gradle准备

在用上ViewModel之前,需要在项目的build.gradle中加上如下内容,开启DataBinding。

为了写ui方便我把ViewBinding也加上了,他不是必须的,但是我的demo代码里面会有ViewBinding相关内容。

xml 复制代码
android {
	buildFeatures {
		dataBinding = true
        viewBinding = true
    }
}

1.2 模拟场景

我们模拟一个简单的场景,页面里面就三个TextView,两个按钮,我们的目标就是用ViewModel来完成点了按钮之后他的Text就动态修改的功能。

实体是我随便设置的

java 复制代码
class Student(var name: String = "",
              var age: Int = 0,
              var id: String = "")

页面长这样,三个TextView和两个按钮

1.3. LiveData和ViewModel

任何一个ViewModel的动态更新都需要围绕LiveData和ViewModel这两个类进行

LiveData:将实体动态化,可以被具有生命周期的对象观察到,只有这个目标对象的生命周期处于活动中,才会收到通知。

ViewModel:通信类,LiveData通过ViewModel来下发通知。

所以我们的代码最后写成这样:

ViewModel作为容器类,里面的成员变量是一个Student的LiveData类(一般都用MutableLiveData,如果有特殊需求也可以自己写)。

如果实际开发的时候有多个这样需要通信的实体,都丢到自定义的ViewModel类里面

kotlin 复制代码
class StudentViewModel : ViewModel() {
    private var student = MutableLiveData<Student>()

    fun getStudent(): MutableLiveData<Student> {
        return student
    }
}

注册通知的方式也很简单,先通过ViewModelProvider生成一个ViewModel的实体,然后将对应的实体进行观察者的注册即可。

kotlin 复制代码
lass ViewModelActivity : AppCompatActivity() {

    private lateinit var binding: ActivityViewModelBinding
    private lateinit var viewModel: StudentViewModel

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityViewModelBinding.inflate(layoutInflater)
        setContentView(binding.root)

		// 创建ViewModel实体的固定写法
        viewModel = ViewModelProvider(this).get(StudentViewModel::class.java)
        // 注册Student对象,这样Student这个对象一旦改变,就会自动调用这里面的方法
        viewModel.getStudent().observe(this) {
            binding.vmName.text = "姓名:${it.name}"
            binding.vmAge.text = "年龄:${it.age}"
            binding.vmId.text = "id:${it.id}"
        }
    }
}

1.4 更新数据

更新数据有两种方式:

setValue:整个对象改变之后,他会自动的通知更新。

post:只改变目标对象里面的一两个成员变量时,通过Post进行更新。

kotlin 复制代码
class ViewModelActivity : AppCompatActivity() {

    private lateinit var binding: ActivityViewModelBinding
    private lateinit var viewModel: StudentViewModel

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
    	//上面有的代码就不贴了

        binding.vmBtn1.setOnClickListener {
            val student = Student(name = "张三", age = 16, id = "001")
            viewModel.setStudent(student)
        }

        binding.vmBtn2.setOnClickListener {
            viewModel.setName("李四")
        }
    }
}
kotlin 复制代码
class StudentViewModel : ViewModel() {
    private var student = MutableLiveData<Student>()

    //通过调用setValue进行更新,代码写成这样是因为kotlin语法省略
    //注意,setValue这个方法必须要在主线程进行
    fun setStudent(student: Student) {
        this.student.value = student
    }

	// 只更新局部成员变量,通过postValue进行更新
    fun setName(name: String) {
        this.student.value?.name = name
        student.postValue(student.value)
    }

    fun getStudent(): MutableLiveData<Student> {
        return student
    }
}

2. 原理与源码解读

这里先把ViewModel在代码层面上执行的原理先讲了,然后我们再通过源码看一下他具体是怎么实现的:

  1. 添加观察者就是在LiveData里面弄了一个HashMap,key是观察者的实体,value是观察者和生命周期对象的绑定类。
    等于是说我们实际上创建的这个观察者,即监听LiveData这个可以变动的实体,也监听了页面本身的生命周期。
  2. setValue就是当key更新了之后,就遍历这个HashMap的keySet,将生命周期状态为运行中的key,调用他们的回调。
  3. post就是通过handler来进行下方通知,所以post可以在子线程跑,其他的部分和setValue一样。

从他的这个原理我们也可以看到观察者模式这种设计模式的一般代码思路:

  1. 添加观察者就是找个集合,List,Set,HashMap等,把观察者对象装进去,观察者对象在注册的时候一般都会传入一个回调CallBack。
  2. 下发通知就是触发某个通知方法之后,遍历集合,然后挨个调用他们注册时传入的回调。

2.1 添加观察者

为了方便我们看源码,我先把当时我们调用observe的这行代码还原成Java的样子

java 复制代码
	viewModel.getStudent().observe((LifecycleOwner)this, (Observer)(new Observer() {
         public void onChanged(Object var1) {
            this.onChanged((Student)var1);
         }

         public final void onChanged(Student it) {
            TextView var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmName;
            Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmName");
            var10000.setText((CharSequence)("姓名:" + it.getName()));
            var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmAge;
            Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmAge");
            var10000.setText((CharSequence)("年龄:" + it.getAge()));
            var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmId;
            Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmId");
            var10000.setText((CharSequence)("id:" + it.getId()));
         }
      }));

我们实际上是new了一个Observer对象,然后将这个对象作为入参传了进来。这个东西看着其实和我们的onClickListener之类的很像,其实就是个回调的监听。

java 复制代码
public abstract class LiveData<T> {

	// 其实就是一个map,我们不用去关心他里面的具体代码原理,知道他是一个map,有和hashmap同款的功能即可
	private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
            new SafeIterableMap<>();

	public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
		// 主线程判断,不是主线程就抛出异常
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // 生命周期已经死亡的就无视
            return;
        }
		// 创建了另一个观察者类,这个观察者类是负责观察页面生命周期的
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        // key为生命周期实体,value为观察者和页面生命周期的观察者类
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
        	// 如果这个监听已经被注册过了,就会抛出异常
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        // 将observer本身对页面的生命周期做监听
        owner.getLifecycle().addObserver(wrapper);
    }
}

mObservers就是我们所说的那个HashMap,key是观察者,也就是我们的new的那个observer类,value则是一个LifecycleBoundObserver对象,他是负责监听页面的生命周期的。

我们就通过这种方式即监听了LiveData本身,又监听了页面的生命周期。

2.2 setValue

接下来我们看看他是怎么做到动态更新的。从setValue这个方法开始,为了方便看,这里省略一些和原理无关的逻辑代码。

java 复制代码
public abstract class LiveData<T> {
	@MainThread
    protected void setValue(T value) {
    	// setValue方法也必须在主线程执行
        assertMainThread("setValue");
        dispatchingValue(null);
    }

	void dispatchingValue(@Nullable ObserverWrapper initiator) {
        do {
            if (initiator != null) {
              //省略代码,我们入参是null 
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    // 重点看这行,这里就是遍历所有的监听执行
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        
    }

	private void considerNotify(ObserverWrapper observer) {
		// 页面生命周期判断,不符合就return了
		if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        // 数据的版本号判断,版本号不符合就return了
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        // 调用onChanged,onChanged就是我们的回调方法
        observer.mObserver.onChanged((T) mData);
	}
}

这样一看逻辑就很清晰了,每次我们调用setValue,他就会跑一个for循环,把所有的observer都做一个检测,符合要求的就调用最后的onChanged,不符合的就不调用。

2.3 post

我们最后在过一下post这条线的更新逻辑,这边本质上逻辑也是一样的

java 复制代码
public abstract class LiveData<T> {

	volatile Object mPendingData = NOT_SET;
	
	private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            // 本质上还是调用setValue,回到2.2了
            setValue((T) newValue);
        }
    };

	protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        // 无视掉同步锁的那些代码,本质就是跑了这一行,这个方法从名字上就知道是把一个东西post到主线程执行
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
}

代码一眼看到头,通过一个线程池将一个runnable推到主线程去处理,最后还是调用的setValue。

最后看一眼他这个线程池是怎么推到主线程的。

java 复制代码
public class ArchTaskExecutor extends TaskExecutor {

	// TaskExecutor是个抽象类,实现是DefaultTaskExecutor
	private TaskExecutor mDelegate;

	private ArchTaskExecutor() {
        mDefaultTaskExecutor = new DefaultTaskExecutor();
        mDelegate = mDefaultTaskExecutor; 
    }
	
	@Override
    public void postToMainThread(@NonNull Runnable runnable) {
        mDelegate.postToMainThread(runnable);
    }
}

public class DefaultTaskExecutor extends TaskExecutor {
	
	private volatile Handler mMainHandler;
	
	public void postToMainThread(@NonNull Runnable runnable) {
        if (mMainHandler == null) {
            synchronized (mLock) {
                if (mMainHandler == null) {
                    mMainHandler = createAsync(Looper.getMainLooper());
                }
            }
        }
        //noinspection ConstantConditions
        mMainHandler.post(runnable);
    }
}

参考资料

https://developer.android.google.cn/topic/libraries/architecture/viewmodel/viewmodel-factories?hl=zh-cn

相关推荐
SRC_BLUE_171 小时前
SQLI LABS | Less-39 GET-Stacked Query Injection-Intiger Based
android·网络安全·adb·less
无尽的大道4 小时前
Android打包流程图
android
镭封6 小时前
android studio 配置过程
android·ide·android studio
夜雨星辰4876 小时前
Android Studio 学习——整体框架和概念
android·学习·android studio
邹阿涛涛涛涛涛涛6 小时前
月之暗面招 Android 开发,大家快来投简历呀
android·人工智能·aigc
IAM四十二6 小时前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
奶茶喵喵叫7 小时前
Android开发中的隐藏控件技巧
android
Winston Wood8 小时前
Android中Activity启动的模式
android
众乐认证8 小时前
Android Auto 不再用于旧手机
android·google·智能手机·android auto
三杯温开水9 小时前
新的服务器Centos7.6 安卓基础的环境配置(新服务器可直接粘贴使用配置)
android·运维·服务器