LiveData
1.LiveData的作用
LiveData是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 activity、fragment 或 service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
《第一行代码安卓》第三版中提到
LiveData主要用来是和ViewModel结合起来用,因为ViewModel在单线程中不会出错,但是一旦ViewModel内部开启线程去执行,得到的数据还是原来的数据
2.LiveData的特点
如果观察者的生命周期处于STARTED或者RESUMED,则LiveData认为观察者处于活跃状态,LiveData会把更新通知给活跃观察者,
非活跃观察者不会收到通知
3.LiveData与ViewModel(无参数)的结合
public class TextViewModel extends ViewModel {
public MutableLiveData<Integer> livedata = new MutableLiveData<Integer>();
int a =0;
public void init(){
livedata.setValue(a);
Log.d("1234",String.valueOf(livedata.getValue()));
}
public void plusOne(){
a++;
livedata.setValue(a);
Log.d("1234",String.valueOf(livedata.getValue()));
}
public void clear(){
a = 0;
livedata.setValue(a);
}
}
这个作用就是init()获得此时的a的值的大小
plusOne()让a的值+1,然后显示a的值的大小
clear()让a的值清零
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextViewModel viewModel = new ViewModelProvider(this).get(TextViewModel.class);
TextView textView = findViewById(R.id.textview);
Button button_0 = findViewById(R.id.button_0);
Button button_1 = findViewById(R.id.button_1);
Button button_2 = findViewById(R.id.button_2);
button_0.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
viewModel.init();
}
});
button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
viewModel.plusOne();
}
});
button_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
viewModel.clear();
}
});
viewModel.livedata.observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));
}
});
}
}
先看TextViewModel的那块
public MutableLiveData<Integer> livedata = new MutableLiveData<Integer>();
这里我们将livedata修改为MutableLiveData对象,并把它的泛型指定为Integer,表示为它包含整型数据,
为什么要用MutableLiveData呢,因为MutableLiveData为一种可变的LiveData
这是MutableLiveData的源码,可以看出来MutableLiveData的主要功能是实现了LiveData的postValue和setValue的功能
getValue() | 获取LiveData中的数据 |
---|---|
setValue() | 在主线程中给LiveData设置数据 |
postValue() | 在非主线程中给LiveData设置数据 |
这样大部分代码都能看懂了,这下我们再来看看MainActivity里面的代码
viewModel.livedata.observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));
}
});
任何一个LiveData对象都可以调用它的**observe()**方法来观察数据的变化
observe()方法接受两个参数:
第一个参数为一个LifecycleOwner对象,activity本身就有一个lifecycle对象,因此直接传入this。第二个是Observe()接口,当livedata中的数据发生变化便会回调到这里
4.LiveData与ViewModel(有参数)的结合
其实可以直接在刚才LiveData与ViewModel **(无参数)**的版本下修改
但我还是重写了一个
public class ViewModel extends androidx.lifecycle.ViewModel {
public MutableLiveData<Integer>mIntegerMutableLiveData;
int a = 0;
public ViewModel(MutableLiveData<Integer> integerMutableLiveData) {
mIntegerMutableLiveData = integerMutableLiveData;
}
public void init(){
mIntegerMutableLiveData.setValue(a);
}
public void addOne(){
a++;
mIntegerMutableLiveData.setValue(a);
}
public void clear(){
a = 0;
mIntegerMutableLiveData.setValue(a);
}
}
其实就是比无参版的多了一个
public ViewModel(MutableLiveData<Integer> integerMutableLiveData) {
mIntegerMutableLiveData = integerMutableLiveData;
}
也就是有参构造
ViewModelFactory中和向ViewModel传递参数的差不多
public class ViewModelFactory implements ViewModelProvider.Factory {
public MutableLiveData<Integer>mIntegerMutableLiveData;
public ViewModelFactory(MutableLiveData<Integer> integerMutableLiveData) {
mIntegerMutableLiveData = integerMutableLiveData;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new com.example.livedataviewmodelwithparams.ViewModel(mIntegerMutableLiveData);
}
}
MainActivity中的修改
public class MainActivity extends AppCompatActivity {
SharedPreferences mPreferences;
ViewModel mViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
int a = mPreferences.getInt("sava_data",0);
MutableLiveData<Integer> savedCounter = new MutableLiveData<>(a);
mViewModel = new ViewModelProvider(this,new ViewModelFactory(savedCounter)).get(ViewModel.class);
Button button = findViewById(R.id.button_0);
Button button1 = findViewById(R.id.button_1);
Button button2 = findViewById(R.id.button_2);
TextView textView = findViewById(R.id.textview);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mViewModel.init();
}
});
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mViewModel.addOne();
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mViewModel.clear();
}
});
mViewModel.mIntegerMutableLiveData.observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));
}
});
}
@Override
protected void onPause() {
super.onPause();
SharedPreferences.Editor editor = mPreferences.edit();
editor.putInt("sava_data",mViewModel.mIntegerMutableLiveData.getValue());
editor.apply();
}
}
真的代码差不多
我感觉唯一要注意的就是这儿
int a = mPreferences.getInt("sava_data",0);
MutableLiveData<Integer> savedCounter = new MutableLiveData<>(a);
mViewModel = new ViewModelProvider(this,new ViewModelFactory(savedCounter)).get(ViewModel.class);
为什么说唯一要注意的就是这儿呢,因为哥们当时候傻逼了,
我当时候是这么写的
MutableLiveData<Integer>savaedcounter = mPreferences.getInt("sava_data",0);
然后这块一直爆红,原因也很明显
mPreferences.getInt("sava_data",0);
这个是int类型,而MutableLiveDatasavaedcounter是一个**MutableLiveData**类型,卡了好久
最后发现,你不是要**MutableLiveData**这个类型嘛,我给你转化成这个不就好了,
所以就先
int a = mPreferences.getInt("sava_data",0);
MutableLiveData<Integer> savedCounter = new MutableLiveData<>(a);
就成功转化
其他就没啥了。
5.map()和switchMap()
5.0 map()和switchMap()的作用
比如一个user类,类里面包含了一个名字和年龄,可是我只想要user里面的年龄,这时候怎么办呢,这时就得用
**map()或者switchMap()**了。
但我写着写着发现它的作用就是:
把一个类型变成另一个类型
5.1map()
如果希望在将 LiveData 对象分派给观察者之前对存储在其中的值进行更改,或者需要根据另一个实例的值返回不同的 LiveData 实例,可以使用LiveData中提供的Transformations类。
比如我想把一个 MutableLiveData类型转化为String类型
5.11没带ViewModel版
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Integer类型的liveData1
MutableLiveData<Integer> liveData1 = new MutableLiveData<>();
TextView textView = findViewById(R.id.textview);
//转换成String类型的liveDataMap
LiveData<String> liveDataMap = Transformations.map(liveData1, new Function<Integer, String>() {
@Override
public String apply(Integer input) {
String s = input + "好好听课";
Log.i("TAG", "apply: " + s);
return s;
}
});
liveDataMap.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
Log.i("TAG", "onChanged1: "+s);
textView.setText(s);
}
});
liveData1.setValue(100);
}
}
最后的结果就是
其实主要有用的就是这几段代码
MutableLiveData<Integer> liveData1 = new MutableLiveData<>();
LiveData<String> liveDataMap = Transformations.map(liveData1, new Function<Integer, String>() {
@Override
public String apply(Integer input) {
String s = input + "好好听课";
Log.i("TAG", "apply: " + s);
return s;
}
});
第一行,确定了它的类型是**MutableLiveData**类型
第二行
LiveData<String> liveDataMap = Transformations.map(liveData1, new Function<Integer, String>() {
@Override
public String apply(Integer input) {
String s = input + "好好听课";
Log.i("TAG", "apply: " + s);
return s;
}
});
最后再让liveDataMap进行observe()
在textview上显示
5.12带ViewModel版
其实那个不带ViewModel版写和不写没啥区别,LiveData不和ViewModel进行绑定就没啥意义
我写的还是带参数的那个版本
ViewModel
public class viewmodel extends ViewModel {
public MutableLiveData<Integer>mIntegerMutableLiveData = new MutableLiveData<Integer>();
public viewmodel(int value) {
mIntegerMutableLiveData.postValue(value);
Log.d("TAG","好"+String.valueOf( mIntegerMutableLiveData.getValue()));
}
public LiveData<String> liveDataMap = Transformations.map(mIntegerMutableLiveData, new Function<Integer, String>() {
@Override
public String apply(Integer input) {
Log.d("TAG","hahaha"+(input));
String s = input+"hahaha";
Log.d("TAG", "apply: " + s);
return s;
}
});
}
ViewModelFactory
public class viewmodelfactory implements ViewModelProvider.Factory {
private int value;
public viewmodelfactory(int value) {
this.value = value;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new viewmodel(value);
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Integer类型的liveData1
TextView textView = findViewById(R.id.textview);
//转换成String类型的liveDataMap
viewmodel viewModel = new ViewModelProvider(this,new viewmodelfactory(1)).get(viewmodel.class);
viewModel.liveDataMap.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
textView.setText(s);
}
});
}
}
最后的效果
5.2switchMap()
如果想要根据某个值 切换观察不同LiveData数据,则可以使用Transformations.switchMap()方法
viewmodel
public class viewmodel extends ViewModel {
public MutableLiveData<String>mMutableLiveData = new MutableLiveData<>();
public MutableLiveData<String>mMutableLiveData1 = new MutableLiveData<>();
public MutableLiveData<Integer>mIntegerMutableLiveData = new MutableLiveData<>();
public LiveData<String> mlivedata = Transformations.switchMap(mIntegerMutableLiveData, new Function<Integer, LiveData<String>>() {
@Override
public LiveData<String> apply(Integer input) {
if(input==123) {
return mMutableLiveData;
}
else{
return mMutableLiveData1;
}
}
});
public viewmodel(int a,String b,String c) {
mIntegerMutableLiveData.setValue(a);
mMutableLiveData.setValue(b);
mMutableLiveData1.setValue(c);
}
}
viewmodelfactory
public class viewmodelfactory implements ViewModelProvider.Factory {
public int a;
public String b;
public String c;
public viewmodelfactory(int a,String b,String c){
this.a = a;
this.b = b;
this.c = c;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new viewmodel(a,b,c);
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewmodel viewModel = new ViewModelProvider(this,new viewmodelfactory(1,"hao","good")).get(viewmodel.class);
TextView textView = findViewById(R.id.text_1);
viewModel.mlivedata.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
textView.setText(s);
}
});
}
}
最后效果
Room
1.Room的定义
首先使用的数据库是一个面向关系的数据库,将面向对象的语言和面向关系的数据库结合在一起就是ORM,即对象关系映射
而Room就是有这样的一个ORM框架
Room 是一个数据库对象映射库,可以轻松访问 Android 应用程序上的数据库。
Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
- 针对 SQL 查询的编译时验证。
- 可最大限度减少重复和容易出错的样板代码的方便注解。
- 简化了数据库迁移路径。
2.Room的三个组件
-
(Database)[数据库类],用于保存数据库并作为应用持久性数据底层连接的主要访问点。
-
(Entity)[数据实体],用于表示应用的数据库中的表。
-
(DAO)[数据访问对象 (DAO)],提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。
3.添加依赖
implementation 'androidx.room:room-common:2.3.0'
implementation 'androidx.room:room-runtime:2.4.3'
annotationProcessor "androidx.room:room-compiler:2.4.3"
4.Room数据库的使用
4.1Entity的用法
您可以将每个 Room 实体定义为带有 [@Entity
] 注解的类。Room 实体包含数据库中相应表中的每一列的字段,包括构成[主键]的一个或多个列。
以下代码是一个简单实体的示例,定义了一个 User
表,其中包含 ID 列、名字列
@Entity
public class User {
@PrimaryKey
public int uid;
@ColumnInfo
public String name;
}
注意:要保留某个字段,Room 必须拥有该字段的访问权限。您可以通过将某个字段设为公开或为其提供 getter 和 setter 方法,确保 Room 能够访问该字段。
默认情况下,Room 将类名称用作数据库表名称。如果您希望表具有不同的名称,请设置 @Entity
注解的 tableName属性。同样,Room 默认使用字段名称作为数据库中的列名称。如果您希望列具有不同的名称,请将 [@ColumnInfo
] 注解添加到该字段并设置 [name
] 属性。
@Entity(tableName = "users")
public class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
}
原本没有写**(tableName = “users”)之前,这个数据库的名字是User**,用来之后,数据库的名字变成了users
4.11定义主键
用
@PrimaryKey
来定义主键
主键 | 外键 |
---|---|
主键是能确定一条记录的唯一标识 | 外键用于与另一张表的关联。是能确定另一张表记录的字段,用于保持数据的一致性 |
刚才的那段代码,主键就是id
4.12定义复合主键
如果您需要通过多个列的组合对实体实例进行唯一标识,则可以通过列出 @Entity
的 [primaryKeys
]属性中的以下列定义一个复合主键:
@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
public String firstName;
public String lastName;
}
4.13忽略字段
默认情况下,Room 会为实体中定义的每个字段创建一个列。 如果某个实体中有您不想保留的字段,则可以使用 **[@Ignore
]**为这些字段添加注解,
@Entity
public class User {
@PrimaryKey
public int uid;
@ColumnInfo
public String name;
@Ignore
public String lastName;
}
这样你的数据库中就不会出现lastName这个数据了
如果实体继承了父实体的字段,则使用 属性的 [ignoredColumns
]属性通常会更容易:@Entity
@Entity(ignoredColumns = "lastName")
public class newUser extends User{
@PrimaryKey
public int id;
@ColumnInfo
public String name1;
}
我用newUser继承User这个类
在**@Entity()里面写ignoredColumns = “”**就会把原本那个类的一个东西忽略了
4.2DAO访问数据
当您使用 Room 持久性库存储应用的数据时,您可以通过定义数据访问对象 (DAO) 与存储的数据进行交互。每个 DAO 都包含一些方法,这些方法提供对应用数据库的抽象访问权限。在编译时,Room 会自动为您定义的 DAO 生成实现。
通过使用 DAO(而不是查询构建器或直接查询)来访问应用的数据库,您可以**[使关注点保持分离]**
我的理解就是,不要把所有功能都写到MainActivity和同一个Fragment,这样可以避免许多与组件生命周期相关的问题,并提高这些类的可测试性
,这是一项关键的架构原则。DAO 还可让您在**[测试应用]**时更轻松地模拟数据库访问。
4.21DAO的一些基本用法
4.211插入操作
@Dao
public interface UserDao {
@Insert
public void insertUsers(User... users);
@Insert
public void insertBothUsers(User user1, User user2);
@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}
Insert这个注释表示的是插入,方法中的对象的类必须是在Entity中被注释过
,调用**@Insert就会执行插入的操作,方法中无需再写其他的东西**
如果 @Insert
方法接收单个参数,则会返回 long
值,这是插入项的新 rowId
。如果参数是数组或集合,则该方法应改为返回由 long
值组成的数组或集合,并且每个值都作为其中一个插入项的 rowId
。
4.212更新操作
借助 @Update
注释,您可以定义更新数据库表中特定行的方法。与 @Insert
方法类似,@Update
方法接受数据实体实例作为参数。 以下代码展示了一个 @Update
方法示例,该方法尝试更新数据库中的一个或多个 User
对象:
@Dao
public interface UserDao{
@Update
public void updataUsers(User... users);
}
Room 使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,Room 不会进行任何更改。
@Update
方法可以选择性地返回 int
值,该值指示成功更新的行数。
4.213删除操作
借助 @Delete
注释,您可以定义从数据库表中删除特定行的方法。与 @Insert
方法类似,@Delete
方法接受数据实体实例作为参数。 以下代码展示了一个 @Delete
方法示例,尝试从数据库中删除一个或多个 User
对象:
@Dao
public interface UserDao{
@Delete
public void deleteUsers(User... users);
}
它的操作方法和更新的操作步骤差不多
4.214查询操作
使用 **[@Query
]**注解,您可以编写 SQL 语句并将其作为 DAO 方法公开。使用这些查询方法从应用的数据库查询数据,或者需要执行更复杂的插入、更新和删除操作。
Room 会在编译时验证 SQL 查询。这意味着,如果查询出现问题,则会出现编译错误,而不是运行时失败。
4.2141简单的查询
以下代码定义了一个方法,该方法使用简单的 SELECT
查询返回数据库中的所有 User
对象:
@Dao
public interface UserDao{
@Query("SELECT * FROM user")
public User[] loadAllUsers();
}
4.2142返回表格列的子集
在大多数情况下,您只需要返回要查询的表中的列的子集。例如,您的界面可能仅显示用户的名字和姓氏,而不是该用户的每一条详细信息。为节省资源并简化查询的执行,您应只查询所需的字段。
借助 Room,您可以从任何查询返回简单对象,前提是您可以将一组结果列映射到返回的对象。例如,您可以定义以下对象来保存用户的名字和姓氏:
public class NameTuple {
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
@NonNull
public String lastName;
}
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
4.2143将参数传递给查询
大多数情况下,您的 DAO 方法需要接受参数,以便它们可以执行过滤操作。Room 支持在查询中将方法参数用作绑定参数。
返回特定年龄以上的所有用户的方法
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
还可以在查询中传递多个参数或多次引用同一参数,如以下代码所示:
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);
@Query("SELECT * FROM user WHERE first_name LIKE :search " +
"OR last_name LIKE :search")
public List<User> findUserWithName(String search);
某些 DAO 方法可能要求您传入数量不定的参数,参数的数量要到运行时才知道。Room 知道参数何时表示集合,并根据提供的参数数量在运行时自动将其展开。
例如,以下代码定义了一个方法,该方法返回了部分地区的所有用户的相关信息:
@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);
4.3Database
创建数据库
AppDatabase 定义数据库配置,并作为应用对持久性数据的主要访问点。数据库类必须满足以下条件:
1.该类必须带有 @Database 注解,该注解包含列出所有与数据库关联的数据实体的 entities 数组。
2.该类必须是一个抽象类,用于扩展 RoomDatabase。
3.对于与数据库关联的每个 DAO 类,数据库类必须定义一个具有零参数的抽象方法,并返回 DAO 类的实例。
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
version = 1表示只有1张User表
4.4创建数据库
在MainActivity中
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
第一个参数为整个应用程序的顶层上下文,第二个参数为数据库类,第三个参数为数据库的名称,最后调用build方法完成创建。
有了具体的数据库后我们就可以使用DAO来对其进行具体的操作了,还记得我们在
数据库类中有一个返回相应的DAO的方法,我们可以用其来获取DAO:
UserDao userDao = db.userDao();
"SELECT * FROM user WHERE first_name LIKE :search " +
"OR last_name LIKE :search")
public List<User> findUserWithName(String search);
某些 DAO 方法可能要求您传入数量不定的参数,参数的数量要到运行时才知道。Room 知道参数何时表示集合,并根据提供的参数数量在运行时自动将其展开。
例如,以下代码定义了一个方法,该方法返回了部分地区的所有用户的相关信息:
@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);