文章目录
组件(MVVM)
当我们不适用ViewModel时,是直接对view进行操作,并且数据是直接存储在controller中
ViewModel
ViewModel 将视图的数据和逻辑从具有生命周期特性的实体(如 Activity 和 Fragment)中剥离开来。直到关联的 Activity 或 Fragment 完全销毁时,ViewModel 才会随之消失,也就是说,即使在旋转屏幕导致 Fragment 被重新创建等事件中,视图数据依旧会被保留。ViewModels 不仅消除了常见的生命周期问题,而且可以帮助构建更为模块化、更方便测试的用户界面。
ViewModel
的优点也很明显,为Activity 、Fragment存储数据,直到完全销毁。尤其是屏幕旋转的场景,常用的方法都是通过onSaveInstanceState()
保存数据,再在onCreate()
中恢复,真的是很麻烦。
其次因为ViewModel
存储了数据,所以ViewModel
可以在当前Activity
的Fragment
中实现数据共享。
-
ViewModel
public class MyViewModel extends ViewModel { private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } }
-
MainActivity
public class MainActivity extends AppCompatActivity { private MyViewModel myViewModel; private TextView textView; private Button button1,button2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.myViewModel = new ViewModelProvider(this).get(MyViewModel.class); this.textView = findViewById(R.id.textView); this.button1 = findViewById(R.id.button1); this.button2 = findViewById(R.id.button2); this.textView.setText(String.valueOf(this.myViewModel.getNumber())); this.button1.setOnClickListener(v -> { this.myViewModel.setNumber(this.myViewModel.getNumber()+1); this.textView.setText(String.valueOf(this.myViewModel.getNumber())); }); this.button2.setOnClickListener(v -> { this.myViewModel.setNumber(this.myViewModel.getNumber()+2); this.textView.setText(String.valueOf(this.myViewModel.getNumber())); }); } }
注意:这里 **this.myViewModel = new ViewModelProvider(this).get(MyViewModel.class);**可能会报错,这是因为依赖版本不对的原因
解决方法:在build.grade中添加一条依赖
dependencies { implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' }
-
效果
此时就算旋转ViewText中的数值也不会改变了。
LiveData
LiveData 是一个可以感知 Activity 、Fragment生命周期的数据容器。当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。同时,LiveData 持有界面代码 Lifecycle 的引用,这意味着它会在界面代码(LifecycleOwner)的生命周期处于 started 或 resumed 时作出相应更新,而在 LifecycleOwner 被销毁时停止更新。
主要作用在两点:
数据存储器类
。也就是一个用来存储数据的类。可观察
。这个数据存储类是可以观察的,也就是比一般的数据存储类多了这么一个功能,对于数据的变动能进行响应。
主要思想就是用到了观察者模式
思想,让观察者和被观察者解耦,同时还能感知到数据的变化(其实就是通过setValue()进行感知),所以一般被用到ViewModel中,ViewModel
负责触发数据的更新,更新会通知到LiveData
,然后LiveData再通知活跃状态的观察者
-
ViewModelLiveData
public class ViewModelLiveData extends ViewModel { private MutableLiveData<Integer> likeNumber; public MutableLiveData<Integer> getLikeNumber() { if(likeNumber == null) { likeNumber = new MutableLiveData<>(); likeNumber.setValue(0); } return likeNumber; } public void addLikeNumber(Integer number){ likeNumber.setValue(likeNumber.getValue() + number); } }
-
MainActivity
public class MainActivity extends AppCompatActivity { TextView textView; ImageButton imageButton1; ImageButton imageButton2; ViewModelLiveData viewModelLiveData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); imageButton1 = findViewById(R.id.imageButton1); imageButton2 = findViewById(R.id.imageButton2); viewModelLiveData = new ViewModelProvider(this).get(ViewModelLiveData.class); //相当于一个观察者,当数据发生改变时,会自动的对textView进行一次刷新 //其他地方就不会出现对textView的修改了,进一步增加了代码的独立性77 viewModelLiveData.getLikeNumber().observe(this,integer -> { textView.setText(String.valueOf(integer)); }); //+1 imageButton1.setOnClickListener(view->{ viewModelLiveData.addLikeNumber(1); }); //-1 imageButton2.setOnClickListener(view->{ viewModelLiveData.addLikeNumber(-1); }); } }
-
效果
-
DataBinding
什么是 DataBinding 呢,简单说来就是帮我们实现 view 和 data 绑定的工具,把数据映射到 view 的 xml中,可以在 xml 布局文件中实现 view 的赋值,方法调用。使用 DataBinding 后,我们不同再写 findViewById,不用再获取控件对象,不用再设置监听,可以节省我们 activity 中的很多获取控件,赋值,添加监听所需要的代码。
DataBinding 是个好东西,15年 google IO 大会就开始推了,最直接的变化就是催生了 android 中 MVVM 的出现,MVVM = MVP + DataBinding 。
DataBinding可以帮我们减少很多没必要的代码,大大提高我们的开发效率。比如大量减少使用findViewById()、setText(),setVisibility(),setEnabled()等代码的几率。
DataBinding主要解决了两个问题:
- 需要多次使用findViewById等无营养的代码,损害了应用性能且令人厌烦
- 更新UI数据需切换至UI线程,将数据分解映射到各个view比较麻烦
-
修改配置
android { compileSdkVersion 30 buildToolsVersion "30.0.2" defaultConfig { dataBinding { enabled true } } }
上面写法已经过时,写下面这种写法
android { buildFeatures { dataBinding = true } }
-
转换为databing形式的layout
然后会自动帮我们创建一个ActivityMainBinding
-
创建ViewModel
public class MyViewModel extends ViewModel { private MutableLiveData<Integer> likeNumber; public MutableLiveData<Integer> getLikeNumber() { if(likeNumber == null) { likeNumber = new MutableLiveData<>(); likeNumber.setValue(0); } return likeNumber; } public void add(){ likeNumber.setValue(likeNumber.getValue() + 1); } }
-
MainActivity(进行一个绑定)
public class MainActivity extends AppCompatActivity { MyViewModel myViewModel; ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); binding = DataBindingUtil.setContentView(this,R.layout.activity_main); myViewModel = new ViewModelProvider(this).get(MyViewModel.class); binding.setData(myViewModel); binding.setLifecycleOwner(this); } }
-
在view中设置好变量,并进行属性和函数调用,我们调用属性其实本质上调用的是get()方法。
<data> <variable name="data" type="com.example.databinding.MyViewModel" /> </data> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(data.likeNumber)}" android:textSize="36sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.251" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="384dp" android:text="@string/button" android:onClick="@{()->data.add()}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
小案例(篮球积分器)
-
创建一个项目,并修改配置
plugins { id 'com.android.application' } android { compileSdkVersion 30 buildToolsVersion "30.0.2" defaultConfig { applicationId "com.example.score" minSdkVersion 30 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { dataBinding = true } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' }
-
创建视图
-
创建ViewModel
public class MyViewModel extends ViewModel { private MutableLiveData<Integer> aTeamScore; private MutableLiveData<Integer> bTeamScore; /** /* 存储回退数值 */ private Integer aBack,bBack; public MutableLiveData<Integer> getATeamScore() { if(aTeamScore == null){ aTeamScore = new MutableLiveData<>(); aTeamScore.setValue(0); } return aTeamScore; } public MutableLiveData<Integer> getBTeamScore() { if(bTeamScore == null){ bTeamScore = new MutableLiveData<>(); bTeamScore.setValue(0); } return bTeamScore; } public void aTeamAdd(int p){ aBack = aTeamScore.getValue(); bBack = bTeamScore.getValue(); aTeamScore.setValue(aTeamScore.getValue()+p); } public void bTeamAdd(int p){ aBack = aTeamScore.getValue(); bBack = bTeamScore.getValue(); bTeamScore.setValue(bTeamScore.getValue()+p); } /** * @Description 重置 * @date 2020/11/12 11:10 * @return */ public void reset(){ aTeamScore.setValue(0); bTeamScore.setValue(0); } /** * @Description 撤销操作 * @date 2020/11/12 11:09 * @return */ public void undo(){ aTeamScore.setValue(aBack); bTeamScore.setValue(bBack); } }
-
使用DataBinding,并调用ViewModel中的属性和方法
-
MainActivity
public class MainActivity extends AppCompatActivity { MyViewModel myViewModel; ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); binding = DataBindingUtil.setContentView(this,R.layout.activity_main); myViewModel = new ViewModelProvider(this).get(MyViewModel.class); binding.setData(myViewModel); binding.setLifecycleOwner(this); } }
-
之后创建一个横版的,并修改样式
-
演示
ViewModelSavedState
在上面的例子中,我们的数据存在ViewModel中,理论上是不会被清除的,相比于onSaveInstanceState确实方便了不少,但如果我们的进程被后台系统杀死,数据就会丢失。
我们这里来检验一下(因为之前的本地化多语言,我将虚拟机切换成立了中文)。
-
打开开发者模式:设置->关于模拟设备->版本号(连续点击进入开发者模式)
-
设置不保留活动:**设置->系统->开发者选项->下滑到 ‘应用’ 栏->打开 ‘不保留活动’ 选项 或者 选择后台进程限制的不允许后台进程 **
-
进行测试
解决办法
- onSaveInstanceState(太旧了)
- viewModelSavedState
onSaveInstanceState方法
public class MainActivity extends AppCompatActivity {
MyViewModel myViewModel;
ActivityMainBinding binding;
private static final String KEY_A_NUMBER = "A_number";
private static final String KEY_B_NUMBER = "B_number";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
if(savedInstanceState!=null){
myViewModel.getATeamScore().setValue((Integer) savedInstanceState.get(KEY_A_NUMBER));
myViewModel.getBTeamScore().setValue((Integer) savedInstanceState.get(KEY_B_NUMBER));
}
binding.setData(myViewModel);
binding.setLifecycleOwner(this);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_A_NUMBER, myViewModel.getATeamScore().getValue());
outState.putInt(KEY_B_NUMBER, myViewModel.getBTeamScore().getValue());
}
}
viewModelSavedState方法
-
添加依赖
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
-
修改ViewModel
public class MyViewModel extends ViewModel { //private MutableLiveData<Integer> aTeamScore; //private MutableLiveData<Integer> bTeamScore; private Integer aBack,bBack; private SavedStateHandle handle; private static final String KEY_A_NUMBER = "A_number"; private static final String KEY_B_NUMBER = "B_number"; public MyViewModel(SavedStateHandle handle) { this.handle = handle; } public MutableLiveData<Integer> getATeamScore() { if(!handle.contains(KEY_A_NUMBER)){ handle.set(KEY_A_NUMBER,0); } return handle.getLiveData(KEY_A_NUMBER); } public MutableLiveData<Integer> getBTeamScore() { if(!handle.contains(KEY_B_NUMBER)){ handle.set(KEY_B_NUMBER,0); } return handle.getLiveData(KEY_B_NUMBER); } public void aTeamAdd(int p){ aBack = (Integer) handle.getLiveData(KEY_A_NUMBER).getValue(); bBack = (Integer) handle.getLiveData(KEY_B_NUMBER).getValue(); handle.set(KEY_A_NUMBER,aBack+p); } public void bTeamAdd(int p){ aBack = (Integer) handle.getLiveData(KEY_A_NUMBER).getValue(); bBack = (Integer) handle.getLiveData(KEY_B_NUMBER).getValue(); handle.set(KEY_B_NUMBER,bBack+p); } /** * @Description 重置 * @date 2020/11/12 11:10 * @return */ public void reset(){ handle.set(KEY_A_NUMBER,0); handle.set(KEY_B_NUMBER,0); } /** * @Description 撤销操作 * @date 2020/11/12 11:09 * @return */ public void undo(){ handle.set(KEY_A_NUMBER,aBack); handle.set(KEY_B_NUMBER,bBack); } }
-
修改MainActivity
public class MainActivity extends AppCompatActivity { MyViewModel myViewModel; ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); binding = DataBindingUtil.setContentView(this,R.layout.activity_main); myViewModel = new ViewModelProvider(this,new SavedStateViewModelFactory(getApplication(),this)).get(MyViewModel.class); binding.setData(myViewModel); binding.setLifecycleOwner(this); } }
SharedPreferences
通过上面的ViewModelSavedState,我们进入后台后数据也不会丢失了,但是当我们点击返回时,数据还会丢失。
解决办法:SharedPreferences,永久保存数据
简单使用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences shp = getPreferences(Context.MODE_PRIVATE);
//存储数据
SharedPreferences.Editor edit = shp.edit();
edit.putInt("NUMBER", 100);
edit.commit();
//读取
int x = shp.getInt("NUMBER", 0);
Log.d("myLog", "onCreate:"+x);
}
}
运行代码,控制台日志打印如下
然后找到右下角的
点击,会看到一个目录,进入到data/data/,你会看到很多的包名,然后找到你当前MainActivity所在的包名,单击右键,进行一次刷新
其中的数据如下
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="NUMBER" value="100" />
</map>
自定义文件名
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//SharedPreferences shp = getPreferences(Context.MODE_PRIVATE);
SharedPreferences shp = getSharedPreferences("My_DATA", Context.MODE_PRIVATE);
//存储数据
SharedPreferences.Editor edit = shp.edit();
edit.putInt("NUMBER", 600);
edit.commit();
//读取
int x = shp.getInt("NUMBER", 0);
Log.d("myLog", "onCreate:"+x);
}
}
运行
类结构
我们当前的类继承自ContextWrapper类,getSharedPreferences方法是在ContextWrapper类中定义的。
定义Model
public class MyData {
private int data;
private Context context;
private Resources resources;
private SharedPreferences shp;
/**
* @Description 初始化基本信息,必须传入context对象
* @date 2020/11/14 14:56
* @param context
* @return
*/
public MyData(Context context) {
this.context = context;
this.resources = context.getResources();
shp = context.getSharedPreferences(resources.getString(R.string.MY_DATA),Context.MODE_PRIVATE);
}
/**
* @Description 存储数据
* @date 2020/11/14 14:55
* @return
*/
public void save(){
SharedPreferences.Editor edit = shp.edit();
edit.putInt(resources.getString(R.string.MY_KEY), data);
edit.commit();
}
/**
* @Description 读取数据
* @date 2020/11/14 14:55
* @return
*/
public int load(){
data = shp.getInt(resources.getString(R.string.MY_KEY), resources.getInteger(R.integer.defValue));
return data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
其中使用到的常量放在了资源中,自己创建一个int资源
-----string.xml-----
<resources>
<string name="app_name">SharedPreferences</string>
<string name="MY_DATA">my_data</string>
<string name="MY_KEY">my_key</string>
</resources>
-----int.xml-----
<resources>
<integer name="defValue">0</integer>
</resources>
在MainActivity中可以定义一个MyData,并传入context
MyData myData = new MyData(this);
这样子传可以,但不好,因为可能会引起内存的泄露
。因为我们的Activity在翻转,切换等情况会进行重新创建,而我们的myData中又有一个引用指向Activity,就会导致当前Activity对象并不会被垃圾回收器回收。
应该传递Application,只要应用还存在,就不会被重新创建。
MyData myData = new MyData(getApplication());
MainActivity代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyData myData = new MyData(getApplication());
myData.setData(800);
myData.save();
Log.d("myLog", "onCreate:"+myData.load());
}
之后自己可以查看结果
AndroidViewModel和小案例
首先,修改配置build.gradle,导入依赖
buildFeatures {
dataBinding = true
}
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
做个界面
编写viewModel
package com.example.viewmodelshp;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.SavedStateHandle;
/**
* @author codekiller
* @date 2020/11/14 15:19
* @Description ViewModel
*/
public class MyViewModel extends AndroidViewModel {
private SavedStateHandle handle;
private String key = getApplication().getString(R.string.data_key);
private String shpName = getApplication().getString(R.string.shp_name);
public MyViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) {
super(application);
this.handle = savedStateHandle;
if (!this.handle.contains(key)) {
load();
}
}
public LiveData<Integer> getNumber() {
return handle.getLiveData(key);
}
/**
* @return
* @Description 加载数据
* @date 2020/11/14 15:41
*/
public void load() {
SharedPreferences shp = getApplication().getSharedPreferences(shpName, Context.MODE_PRIVATE);
handle.set(key, shp.getInt(key, 0));
}
/**
* @return
* @Description 保存数据
* @date 2020/11/14 15:41
*/
public void save() {
SharedPreferences shp = getApplication().getSharedPreferences(shpName, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = shp.edit();
edit.putInt(key, getNumber().getValue());
edit.commit();
}
/**
* @param x
* @return
* @Description 进行+和-运算
* @date 2020/11/14 15:39
*/
public void add(int x) {
handle.set(key, getNumber().getValue() + x);
//save(); 耗时间
}
}
常量资源
<resources>
<string name="app_name">ViewModelSHP</string>
<string name="button_plus">+</string>
<string name="button_minus">-</string>
<string name="textview">HelloWorld</string>
<string name="data_key">DATA_KEY</string>
<string name="shp_name">shp_name</string>
</resources>
修改activity_main.xml,先转化为data binding layout,再修改代码(这里给出了修改的部分)
<data>
<variable
name="data"
type="com.example.viewmodelshp.MyViewModel" />
</data>
<TextView
android:text="@{String.valueOf(data.getNumber())}"/>
<Button
android:onClick="@{()->data.add(1)}"/>
<Button
android:onClick="@{()->data.add(-1)}"/>
修改MainActivity
public class MainActivity extends AppCompatActivity {
MyViewModel myViewModel;
ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
myViewModel = new ViewModelProvider(this, new SavedStateViewModelFactory(getApplication(), this)).get(MyViewModel.class);
binding.setData(myViewModel);
binding.setLifecycleOwner(this);
}
@Override
protected void onPause() {
super.onPause();
//保存数据
myViewModel.save();
}
}
之后,不论是进程杀死,还是退出应用,关机,数据都不会丢失。但是如果你手机没电了突然关机,onPause()调用不了,数据依然会丢失。