把EditText交给ViewModel管理
Android小萌新今天在做项目的时候遇到一个小问题,来记录一下~
在做一个登录界面的时候,想使用DataBinding+ViewModel+LiveData
但是怎样让ViewModel拿到EditText控件的实例呢?一开始想到把DataBinding对象从Activity传入ViewModel,后来发现不可行,因为DataBinding在初始化的时候需要传入owner参数,而这个owner参数传的是Activity本身,也就是说DataBinding持有了Activity的引用,这时候如果把DataBinding传给ViewModel不就成了ViewModel持有Activity的引用了吗?内存泄漏!不行!
解决办法是通过DataBinding双向绑定(View可以操作数据,数据变化时通知View),让EditText的内容直接对应到ViewModel中的LiveData上,这样的话在输入框输入的同时LiveData也在随时变化。
一些收获的经验:
1. @={}和@{}
我发现EditText的text属性要使用@={...}
而不是像TextView直接使用@{...}
来和Livedata绑定,多出来的这个"="我个人认为是TextView和LiveData绑定仅仅只是get数据,而EditText和数据绑定需要get和实时set数据,所以"="可以理解为赋值
<EditText
...
android:text="@={viewModel.inputAccount}"
... />
<EditText
...
android:text="@={viewModel.inputVerify}"
... />
<Button
...
android:onClick="@{(v)->viewModel.onLogin()}"
... />
2. 为什么在账号EditText输入一个数,getInputAccount()会被调用两次呢?
public class TemporaryLoginViewModel extends ViewModel {
private static final String TAG = "TemporaryLoginViewModel";
MutableLiveData<String> mInputAccount;
MutableLiveData<String> mInputVerify;
public MutableLiveData<String> getInputAccount() {
// TODO:为什么EditText输入一个数,getInputAccount()会调用两次?
Log.d(TAG, "getInputAccount: Entrance");
//双检锁
if (mInputAccount == null)
synchronized (TemporaryLoginViewModel.class) {
if (mInputAccount == null)
mInputAccount = new MutableLiveData<>();
}
//只是TextView展示的话可以返回不可变的LiveData,这里因为是EditText所以只能返回可变的MutableLivedata
return mInputAccount;
}
public MutableLiveData<String> getInputVerify() {
...
}
public void onLogin() {
Log.d(TAG, "onLogin: 账号:" + mInputAccount.getValue() + " 验证码:" + mInputVerify.getValue());
}
}
这就要进入源码去看一眼了,在getInputAccount()
上选择findUsages
发现有两处地方调用了它
第一处在一个回调方法的onChange()
中,我们打个断点查看虚拟机栈的栈帧,在第一次执行到断点的时候,虚拟机栈是这样的:
onChange()
内部是这样的:
也就是说你在输入框里打字使得EditText数据改变的时候,首先回调到onChange()
中,在这个onChange()
中通过getInputAccount()
得到LiveData再给它set一个字符串值
第二处是在executeBindings()
中,这个方法是什么时候执行呢?我们让程序继续执行,在下一次执行到断点的时候,虚拟机栈是这样的:
可以看到在第二次执行到断点的时候,程序从executeBindings()
方法中企图调用getInputAccount()
继续向下追踪,就可以看到这样的一个描述
意思是当View所绑定的数据发生变更的时候,执行此方法
总结
走到这里就很清晰了,整个流程是首先在输入框中输入,当监听到输入后先回调onChange()
,在onChange()中通过getInputAccount()得到LiveData,然后修改了LiveData的值;LiveData一但修改,就会重新执行executeBindings()
,所以又会调用一次getInputAccount()
到现在就明白了为什么ViewModel中的getInputAccount()
会被执行两次啦~
3. getInputAccount()
只能返回MutableLiveData
第三个问题也很好理解,为了安全嘛,我一开始试图让getInputAccount()
返回一个不可修改的LiveData,然后报错了!
从第二个问题的分析不难看出,人家内部还要给get到的LiveData执行setValue()
呢,所以返回的LiveData一定是可变的MutableLiveData啦~
4. 程序启动时会额外执行一次getInputAccount()
当我查看Activity中的setLifecycleOwner(this)方法时发现它设置了一个LifecycleObserver
进入这个Observer
它观察到Activity处于onStart状态的时候会调用executePendingBindings()
进入executePendingBindings()
瞅瞅
又要去调用executeBindingsInternal(),这不就是我们上面在虚拟机栈中看到的调用步骤吗?也就是说在Activity在onStart状态时会执行一次getInputAccount()
作者:星星早点睡
链接:https://juejin.cn/post/7066330400594853924
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。