文章目录
- 组件(MVVM)
- ViewModel
- LiveData
- DataBinding
- 小案例(篮球积分器)
- ViewModelSavedState
- SharedPreferences
- 简单使用
- 自定义文件名
- 类结构
- 定义Model
- AndroidViewModel和小案例
组件(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()调用不了,数据依然会丢失。