ViewModel 是 Android 架构组件的一部分,用于帮助开发者管理 UI 数据的持久性和生命周期感知。ViewModel 的主要目的是将 UI 数据与界面控制逻辑分离,以便更好地管理数据的生命周期,确保数据在配置更改(如屏幕旋转)或 Activity 生命周期变化时不会丢失。
以下是 ViewModel 的主要特点和用途:
在 Android 应用程序中,通常会创建自定义的 ViewModel 类,该类扩展自 androidx.lifecycle.ViewModel
并包含需要在界面之间共享和保持的数据。然后,通过使用 ViewModelProvider
获取 ViewModel 实例,并在 UI 控制器(如 Activity 或 Fragment)中使用它。
ViewModel 是一项强大的工具,有助于改进 Android 应用程序的可维护性、性能和用户体验。它在处理 UI 数据时提供了一种更有效的方式。
ViewModel最常用的场景:
viewModel的生命周期:
一:活动内使用的变量放在ViewModel里面:
①创建一个java类,继承ViewModel类:
在里面定义变量,这些变量都是在主活动中需要使用的变量。
②在对应活动里面使用变量:
只能使用ViewModelProvider来获取ViewModel,因为直接在Create方法里面创建的话,会导致在屏幕旋转的时候造成数据丢失。
package com.example.demo.jetpack;
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
int count;
public MainViewModel() {
}
public MainViewModel(int count) {
this.count = count;
}
}
package com.example.demo.jetpack;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.demo.R;
public class ViewModelActivity extends AppCompatActivity {
private MainViewModel viewModel;
private TextView tv;
private Button btn;
private Button btn_clear;
private SharedPreferences.Editor edit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model);
SharedPreferences config = getSharedPreferences("config", Context.MODE_PRIVATE);
edit = config.edit();
int count = config.getInt("count", 0);
try {
tv = findViewById(R.id.tv_ViewModel);
btn = findViewById(R.id.btn_ViewModel);
viewModel = new ViewModelProvider(this).get(MainViewModel.class);
btn_clear = findViewById(R.id.btn_clear_ViewModel);
btn_clear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clearCount();
}
});
refresh();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.count++;
refresh();
edit.putInt("count", count);
edit.commit();
}
});
} catch (Exception e) {
Log.d("ViewModelActivity_test", e.toString());
}
}
private void clearCount() {
viewModel.count = 0;
tv.setText("0");
edit.putInt("count", 0);
edit.commit();
}
private void refresh() {
tv.setText(String.valueOf(viewModel.count));
}
}
注意:
二:向ViewModel传递参数:
直接赋值
向ViewModel传递参数是通过ViewModelProvider.Factory类实现
重写Create方法之后,
由于更新之后,只能向Create,会有一个CreationExtras变量,只能向ViewModel传递这个参数,还没想到,怎么ViewModel传递参数。
在继承ViewModelProvider.Factory的类中,实现Create方法:
创建一个构造器,将你需要的东西传进去,将参数传递给ViewModel的构造函数,这就是上面ViewModel里面的构造函数。 直接将数值传递给ViewModel,注意ViewmodelProvider的使用。
package com.example.demo.jetpack;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.viewmodel.CreationExtras;
public class MainVIewModelFactory implements ViewModelProvider.Factory {
private final int initialValue; // 要传递的参数
public MainVIewModelFactory(int initialValue) {
this.initialValue = initialValue;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (modelClass == MainViewModel.class) {
return (T) new MainViewModel(initialValue); // 将参数传递给 ViewModel 的构造函数
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
package com.example.demo.jetpack;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.demo.R;
public class ViewModelActivity extends AppCompatActivity {
private MainViewModel viewModel;
private TextView tv;
private Button btn;
private Button btn_clear;
private SharedPreferences.Editor edit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model);
SharedPreferences config = getSharedPreferences("config", Context.MODE_PRIVATE);
edit = config.edit();
int count = config.getInt("count", 0);
try {
tv = findViewById(R.id.tv_ViewModel);
btn = findViewById(R.id.btn_ViewModel);
viewModel = new ViewModelProvider(this, new MainVIewModelFactory(count)).get(MainViewModel.class);
btn_clear = findViewById(R.id.btn_clear_ViewModel);
btn_clear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clearCount();
}
});
refresh();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.count++;
refresh();
edit.putInt("count", viewModel.count);
edit.commit();
}
});
} catch (Exception e) {
Log.d("ViewModelActivity_test", e.toString());
}
}
private void clearCount() {
viewModel.count = 0;
tv.setText("0");
edit.putInt("count", 0);
edit.commit();
}
private void refresh() {
tv.setText(String.valueOf(viewModel.count));
}
}
Lifecycles 的中文意思就是 生命周期,所以我们可以知道它和生命周期是息息相关的!
“LifeCycles”(生命周期)是 Android 架构组件的一部分,旨在帮助开发者更好地管理 Android 应用程序中的组件(如 Activity 和 Fragment)的生命周期。生命周期库为开发者提供了在 Android 生命周期事件发生时执行操作的机制,以便更容易处理与生命周期相关的任务,例如初始化、清理、数据加载和资源释放。
Android 应用程序组件的生命周期是由系统管理的,包括以下关键事件:
onCreate()
: 组件创建时调用。onStart()
: 组件变为可见但尚未与用户交互时调用。onResume()
: 组件变为可见并与用户交互时调用。onPause()
: 组件不再与用户交互时调用。onStop()
: 组件不再可见时调用。onDestroy()
: 组件销毁时调用。生命周期库为每个生命周期事件提供了对应的类,开发者可以使用这些类来执行特定的操作。以下是生命周期库中的主要类:
LifecycleOwner
: 它是 Activity
和 Fragment
的常见接口,表示拥有生命周期的对象。Lifecycle
: 代表应用程序组件的生命周期状态,包括 CREATED
、STARTED
、RESUMED
、DESTROYED
等。LifecycleObserver
: 定义了在生命周期事件发生时要执行的回调方法。LifecycleRegistry
: 实现了 Lifecycle
接口,用于管理组件的生命周期状态。通过将 LifecycleOwner
和 LifecycleObserver
结合使用,开发者可以轻松地在组件的生命周期事件中执行操作,从而更好地管理资源和确保应用程序的正确运行。
生命周期库是 Android 架构组件的一部分,可帮助开发者编写更健壮、可维护和易于测试的 Android 应用程序。它特别适用于处理内存管理、数据加载和其他与生命周期相关的任务。
“LifeCycles”(生命周期)是 Android 应用程序中非常重要的一部分,它解决了以下几个关键问题,使应用程序更稳定、可维护且用户体验更好:
总之,生命周期库是 Android 架构组件的一部分,它旨在简化和增强应用程序开发的生命周期管理,有助于构建更稳定、高性能和易于维护的 Android 应用程序。这使得开发者能够更专注于应用程序的功能和用户体验,而无需过多关注底层生命周期事件的处理。
lifecycle的本质就是在任意一个类里面用监视器,监视活动的状态
很多时候,发现碎片或者活动处于某一个状态的时候,就不需要执行一些操作了:使用场景也比较多,因此,有了Lifecycle
一:创建一个MyObserver类:
主要使用注解,区分检测到什么状态,执行什么方法:
使用@OnLifecycleEvent()注解:里面填的就是活动的某一个状态:
二:在被监视的活动里面使用getLifecycle()方法
得到实例之后,直接调用addObserver方法():
现在就可以自动监视活动或者碎片的生命周期了。
LiveData
是 Android 架构组件中的一个类,用于在 Android 应用中以一种生命周期感知的方式管理数据。它具有以下关键特点:
LiveData
是生命周期感知的,它可以感知与之关联的组件(如 Activity 或 Fragment)的生命周期状态。这使得它能够自动管理观察者的订阅和取消订阅,以避免潜在的内存泄漏问题。LiveData
用于存储和公开实时更新的数据。它可以通知观察者(通常是 UI 组件)数据发生变化,从而实时更新界面。这对于构建实时性强的应用程序非常有用,如实时聊天、实时通知等。LiveData
实例,从而共享相同的数据。这有助于确保这些组件都看到相同的数据,而无需复杂的数据同步。LiveData
可以确保数据更新仅在观察者处于活动状态时通知,而在非活动状态时会暂停通知。这有助于避免在不必要的时候触发 UI 更新,提高性能和减少资源消耗。LiveData
可以与其他异步操作(如网络请求或数据库查询)结合使用,以简化异步数据加载和处理。它允许您将异步操作的结果直接提供给观察者,而无需手动处理线程切换。LiveData
支持数据变换,使您可以对数据进行转换和处理,然后将结果提供给观察者。这使得数据转换和映射变得更容易。LiveData
是生命周期感知的,它可以帮助避免由于观察者未正确取消订阅而导致的内存泄漏问题。LiveData 是 Android 架构组件的一部分,被广泛用于 Android 应用程序开发中,它的存在和使用有多个重要原因:
Transformations
和 MediatorLiveData
等功能,用于将数据转换和合并,以便更容易实现复杂的数据操作。LiveData 的设计目标是提供一种简化 Android 应用程序中数据管理和界面更新的方法,同时解决了与生命周期和内存泄漏相关的一些常见问题。它是 Android 应用架构中的一个重要组件,有助于创建更稳定、易维护和高性能的应用程序。
1.包装数据:
包装数据可以使用liveData的子类,使用MutableData类,可以放置任何数据。
用法如下:
完了,直接最简单的用法:
package com.example.demo.jetpack;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
int count;
private MutableLiveData<Integer> data;//将真实的LiveData隐藏起来
{
data = new MutableLiveData<>();
data.setValue(count);
}
public LiveData<Integer> get() {
return data;
}
public MainViewModel() {
}
public void plusOne() {
int temp = data.getValue();
if (null != data.getValue()) {
data.setValue(temp + 1);
}
}
public void clearNow() {
//自动装箱
data.setValue(0);
}
public MainViewModel(int count) {
this.count = count;
}
}
在主活动,调用的时候就使用这个livedata类的实例 就好了,通过调用:
等方法,实现对数据的操作,
注意:
最好将本身含有数据的LiveData设置为private,这样就不会向非ViewModel类的类暴漏的数据更少了,
就像上面的例子:
返回一个Livedata类,就只能得到数据和行使观察的方法。
这样只能调用父类的方法,但是执行的还是子类的getValue方法
map()方法的使用:
将一个liveData对象映射为另一个livedata对象。
这里将一个Integer的liveData映射成String的liveData。
switchMap()方法的使用:
这个方法使用的场景比较多:目前我们对viewModel的学习都是livedata在ViewModel中创建的前提下,但是很多情况下liveData是一个方法返回的数据,经过某个方法的操作:比如向服务器申请数据之后,返回了数据,或者在数据库中查找对应的对象,
假设在一个getUser方法定义在ViewModel的子类中,返回一个livedata类,我们要在活动中添加观察,可以这样写吗:
viewModel.getUser().observe(this, new Observer<User>() {
@Override
public void onChanged(User user) {
//具体发现观察到数据变化之后的逻辑操作
}
});
记住,千万不能这么做,因为每次返回的数据都不一样,但是观察的却是最初的那一个livedata,从而根本无法观察livedata,这种情况下livedata是不可观察的。
switchMap就是应对这种情况的,如果ViewModel的某个livedata是由某个方法返回的,我们就可以用这个方法将这个livedata转化为另一个可观察的livedata对象。
使用switchMap方法,将现在的livedata转化为重新申请的livedata,对于活动来说,直接就可以使用livedata z了
补充:
这种情况下是调用geUser方法的时候有参数,可以确定livedata发生变化,然后当没有任何参数的时候,我们怎么办呢?
这样实现了对livedata数据的更新
总结:
Room 是一个用于 Android 应用的持久性数据库库,它是 Google 推出的一部分 Android 架构组件。Room 的主要目标是简化数据库操作,提供更好的类型安全性,同时提供方便的本地数据库访问方式。Room 在 SQLite 数据库之上构建,因此它继承了 SQLite 的强大功能,同时提供了更高层次的抽象,以便于开发者使用。
Room 提供以下主要组件:
RoomDatabase
。它负责管理数据库的连接,提供 DAO 的实例,以及处理数据库版本迁移。基本使用:
实现数据库基本操作:增删改查
一:创建实体类:
按照需求,创建要在数据库中存储的实体类
@Entity
,框架才能认识这是你所要存储的实体类@PrimarKey
,设置属性autoGenerate为true,主键会自己增长,如果想要设置元素的名字:使用注释:@ColumnInfo
设置属性name为你想要的值,package com.example.demo.jetpack.room.database;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/**
* @author winiymissl
*/
@Entity
public class User {
@PrimaryKey(autoGenerate = true)
long id;
int age;
String name;
String sex;
public User(String name, int age, String sex) {
this.age = age;
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + ", sex='" + sex + '\'' + '}';
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
二:创建Dao接口
这是Room框架最重要的地方,在这里里面实现增删改查的操作
在增加前面加上:@Insert
参数处放着实体类就好:这里使用User作为一个实体类
/**
* Desc:插入用户信息
*/
@Insert
void inertUser(User user);
删除,修改和查看:
/**
* Desc:更新用户信息
*/
@Update
void updateUser(User user);
/**
* @param user 传入一个User类
*/
@Delete
void delUser(User user);
/**
* @return 返回User集合
*/
@Query("select * from User")
List<User> queryAll();
/**
* @param age 删除输入的id对应的数据
* @return 返回User集合
*/
@Query("select * from User where age =:age")
List<User> query(int age);
在接口上面加上注释@Dao
,
三:创建database类:
在单线程应用中,记得使用单例模式,使用一个数据库;
package com.example.demo.jetpack.room.database;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.DatabaseConfiguration;
import androidx.room.InvalidationTracker;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import kotlin.jvm.Synchronized;
/**
* @Author winiymissl
* @Date 2023-10-22 14:37
* @Version 1.0
*/
@Database(version = 1, entities = User.class)
public abstract class MyDataBase extends RoomDatabase {
public abstract Dao userDao();
private static MyDataBase myDataBase = null;
// private static Context context;
// public MyDataBase(Context context) {
// this.context = context;
// }
@Synchronized
public static MyDataBase getDatabase(Context context) {
if (myDataBase == null) {
myDataBase = Room.databaseBuilder(context.getApplicationContext(), MyDataBase.class, "app_database").allowMainThreadQueries().build();
return myDataBase;
}
return myDataBase;
}
@Override
public void clearAllTables() {
}
@NonNull
@Override
protected InvalidationTracker createInvalidationTracker() {
return null;
}
@NonNull
@Override
protected SupportSQLiteOpenHelper createOpenHelper(@NonNull DatabaseConfiguration databaseConfiguration) {
return null;
}
}
注意:数据库的操作必须放在子线程,在创建build的时候加上方法allowMainThreadQueries(),
就可以了,但是最好只在测试中使用。在发行版中就不能再使用这个方法了。
Room数据库的升级:
data数据库的每一次升级都需要自己编写更新的逻辑
当然为了保留用户的数据,我们就必须使用方法二:
addMigrations()
,添加刚才的migration。package com.example.demo.jetpack.room.database;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.DatabaseConfiguration;
import androidx.room.InvalidationTracker;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import kotlin.jvm.Synchronized;
/**
* @Author winiymissl
* @Date 2023-10-22 14:37
* @Version 1.0
*/
@Database(version = 2, entities = {User.class, Book.class})
public abstract class MyDataBase extends RoomDatabase {
public abstract UserDao userDao();
public abstract BookDao bookDao();
private static MyDataBase myDataBase = null;
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("create table Book (id integer primary key autoincrement not null, name text not null, pages integer not null)");
}
};
public static MyDataBase getDatabase(Context context) {
if (myDataBase == null) {
myDataBase = Room.databaseBuilder(context.getApplicationContext(), MyDataBase.class, "app_database").addMigrations(MIGRATION_1_2).allowMainThreadQueries().build();
return myDataBase;
// .addMigrations(MIGRATION_1_2)
}
return myDataBase;
}
//......
}
workManager是一个用于管理任务的高级工具,可以对后台服务,实现更加人性化的操作:
WorkManager 是 Android Jetpack 库的一部分,用于帮助您轻松管理后台任务和异步工作。它提供了一个强大的、高度可定制的任务调度框架,用于执行延迟任务、周期性任务和与系统约束(例如充电状态、网络连接、设备空闲等)相关的任务。WorkManager 旨在使后台任务的管理变得更加简单,并确保这些任务在不同版本的 Android 上都能可靠地运行。
总之,WorkManager 是 Android 开发中的一种强大工具,使后台任务的管理和调度变得更加容易,同时考虑了设备约束和可靠性。它有助于开发者更好地管理后台任务,提高应用的性能和用户体验。无论是数据同步、通知、定期任务还是其他后台操作,WorkManager 都是一个强大的解决方案。
基本用法:
第一步:定义一个后台任务
每一个后台必须继承worker类,并调用它唯一的构造函数。然后重新父类中的方法doWork()
,在这里面编写具体的逻辑,
dowork不会运行在主线程,因此可以执行耗时的逻辑,dowork要求返回一个Result对象,用于表示任务运行的结果,
setBackoffCriteria()
重新执行任务,第二步:配置任务:
这其实是最复杂的一步,因为可以配置的内容非常多,现在只进行最基本的配置
使用OneTimeWorkRequest
类设置运行一次的任务:
OneTimeWorkRequest build = new OneTimeWorkRequest.Builder(SimpleWork.class).build();
得到一个WorkManager实例,然后将Work的扩展类,放入运行队列里面.
主要活动:
package com.example.demo.jetpack.workmanager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import android.os.Bundle;
import com.example.demo.R;
public class SimpleWorkActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_work);
OneTimeWorkRequest build = new OneTimeWorkRequest.Builder(SimpleWork.class).build();
WorkManager.getInstance(this).enqueue(build);
}
}
第三步:传入workManager
WorkManager.getInstance(this).enqueue(build);
传入到队列里面.
复杂用法(实现配置更多功能):
前面创建work类,重写doWork方法都和前面一样。区别在于创建任务实例的时候,配置的信息,使用的方法会更多。
我们如果需要取消任务, 可以直接通过任务请求的id取消(通过方法 cancelWorkById()),如果通过Tag的话,可以一次删除多个任务请求。(cancelAllWorkByTag())
cancelAllWork()
用于取消所有任务请求
这个就是任务的id,是由系统自动分配的。
setBackoffCriteria(),
setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
这方法一共有三个参数:
这两个值是用于通知任务运行结果的,可以在活动中进行监听,
WorkManager.getInstance(this).getWorkInfoByIdLiveData(build.getId()).observe(this, new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
if (WorkInfo.State.SUCCEEDED == workInfo.getState()) {
}
if (WorkInfo.State.FAILED == workInfo.getState()) {
}
}
});
使用方法getWorkInfoByIdLiveData()
,参数就是系统给任务请求分配的id,方法返回LiveData类,添加监听,对返回结果进行判断。
定义了多个任务,第一个使用方法beginWith()
,剩下的任务只需要接着用**then()**方法就好了而且,必须是前一个方法执行成功才会执行后面的任务。