系统控件
TextView
TextView 的 setText 方法接受的必须是一个 String,否则会报错
private TextView infoText;
infoText = (TextView) findViewById(R.id.infoText);
infoText.setText(String.valueOf(mainViewModel.counter));
数据持久化
SharedPreferences
SharedPreferences 可以提供简单数据持久化存储
存储
通过 sharedPreferences 中的 edit() 生成 editor 的实例,通过一系列的 set 方法进行数据的存储
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor editor;
editor = sharedPreferences.edit();
editor.putInt("count_reserved", mainViewModel.counter);
editor.apply();
读取
通过 PreferenceManager.getDefaultSharedPreferences(this) 获取到对应的 SharedPreferences 对象,然后通过一系列的 get 方法进行读取,第一个参数为自定义的名字,第二个为默认值
private SharedPreferences sharedPreferences;
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
int countReserved = sharedPreferences.getInt("count_reserved", 0);
Room
ORM 框架,简单化 SQLite 数据库的使用
包含三部分:
- Entity,实体类,每一个实体类代表一张对应的表
- Dao,数据访问对象,封装数据库的各项操作
- Database,定义数据库的关键信息
添加依赖:
def room_version = "2.4.1"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
配置
User 实体类:
@Entity // 表示这是一个 Entity,get 和 set 方法需要写明
public class User {
@PrimaryKey(autoGenerate = true) // 设置主键,并设置为自动增长
private long id;
private String firstName;
private String lastName;
private int age;
public User(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
'}';
}
}
UserDao:
利用 @Insert,@Update,@Delete 可以进行单个对象的操作,但是对于需要有 where 条件的操作,则需要使用到 @Query,@Query 采用 SQL 语句的形式执行命令,如果需要指定参数,则在 SQL 命令中所要添加的参数前加上 : 然后通过参数传入方法,具体使用如下:
@Dao
public interface UserDao {
@Insert
public long insertUser(User user);
@Update
public void updateUser(User newUser);
@Query("select * from User")
public List<User> loadAllUsers();
@Query("select * from User where age > :age")
public List<User> loadUsersOlderThan(int age);
@Delete
public void deleteUser(User user);
@Query("delete from User where lastName = :lastName")
public int deleteUserByLastName(String lastName);
}
Database:
抽象方法继承 RoomDatabase,一般不改变
@Database(version = 1, entities = {User.class}) // 标记这是一个 Database,同时设置版本号,实体类
public abstract class AppDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "app_database";
private static AppDatabase databaseInstance;
// 单例模式创建数据库实例,保证全局只有一个数据库,synchronized 保证线程安全
public static synchronized AppDatabase getDatabaseInstance(Context context) {
if (databaseInstance == null) {
databaseInstance = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME).build();
}
return databaseInstance;
}
public abstract UserDao userDao(); // 抽象方法,具体在 Room 底层实现
}
增删改查具体使用
耗时逻辑写在子线程中,不能放在主线程内
UserDao userDao = AppDatabase.getDatabaseInstance(this).userDao();
User user1 = new User("Tom", "Brady", 40);
User user2 = new User("Tom", "Hanks", 63);
Button addData = (Button) findViewById(R.id.addData);
Button updateData = (Button) findViewById(R.id.updateData);
Button deleteData = (Button) findViewById(R.id.deleteData);
Button queryData = (Button) findViewById(R.id.queryData);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
user1.setId(userDao.insertUser(user1));
user2.setId(userDao.insertUser(user2));
}
}).start();
}
});
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
user1.setAge(42);
userDao.updateUser(user1);
}
}).start();
}
});
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
userDao.deleteUserByLastName("Hanks");
}
}).start();
}
});
queryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
for (User user : userDao.loadAllUsers()) {
Log.d(TAG, user.toString());
}
}
}).start();
}
});
}
升级数据库
开发测试阶段可以直接使用 fallbackToDestructiveMigration(),表示在每一次版本号更新后都会重建数据库,之前所有保存的数据都丢失
databaseInstance = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()
.build();
升级到版本2
添加数据库表:Book
@Entity
public class Book {
@PrimaryKey(autoGenerate = true)
private long id = 0;
private String name;
private int pages;
private String author;
public Book(String name, int pages) {
this.name = name;
this.pages = pages;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPages() {
return pages;
}
public void setPages(int pages) {
this.pages = pages;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", pages=" + pages +
'}';
}
}
添加 BookDao:
@Dao
public interface BookDao {
@Insert
public Long insertBook(Book book);
@Query("select * from Book")
public List<Book> loadAllBooks();
}
修改 Database:
@Database(version = 2, entities = {User.class, Book.class}) // 修改版本号
private static final Migration MIGRATION_1_2 = new Migration(1, 2) { // 通过 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)");
}
};
databaseInstance = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2) //添加更新操作
.build();
升级到版本3
添加 author 列到 Book 表中
Book 实体类:
@Entity
public class Book {
@PrimaryKey(autoGenerate = true)
private long id = 0;
private String name;
private int pages;
private String author;
public Book(String name, int pages) {
this.name = name;
this.pages = pages;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPages() {
return pages;
}
public void setPages(int pages) {
this.pages = pages;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", pages=" + pages +
", author='" + author + '\'' +
'}';
}
}
Database:
@Database(version = 3, entities = {User.class, Book.class}) // 修改版本号
public static final Migration MIGRATION_2_3 = new Migration(2, 3) { // 添加相应的 Migration
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("alter table Book" +
"add column author text not null default 'unknown'");
}
};
databaseInstance = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3) // 可以连续升级到版本3,如果用户数据库处于版本1,则升级两次操作
.build();
报错
Schema export directory is not provided to the annotation processor so we cannot export the schema. You can either provide room.schemaLocation annotation processor argument OR set exportSchema to false.
安卓room构建错误
-
解决方案一
Database 设置 exportSchema 注解为 false(不建议)
@Database(version = 2, entities = {User.class, Book.class}, exportSchema = false)
-
解决方案二
在项目中gradle中通过 annotationProcessorOptions 注解,为
room.schemaLocation
指定schemas的子文件夹android { compileSdk 32 defaultConfig { applicationId "com.lsilencej.counter" minSdk 24 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // 指定 room.schemaLocation 生成的文件路径 javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } } } }
Jetpack
ViewModel
通过 ViewModel 来存储数据,为了不让 Activity 处理太多东西,最主要的是维持数据的持久性存储,而不是每次重建 Activity 数据丢失,MVVM 框架中的第一个 V
在此之前需要导入相关依赖:(具体版本参考谷歌开发者文档)
def lifecycle_version = "2.4.0"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
新建 MainViewModel 继承 ViewModel:
public class MainViewModel extends ViewModel {
public int counter = 0;
public MainViewModel(int countReserved) {
this.counter = countReserved;
}
}
初始化:( ViewModelProvider 传入 Activity 实例,get 传入 ViewModel )
private MainViewModel mainViewModel;
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
如果需要和参数绑定,则需要新建一个 MainViewModelFactory 类实现 ViewModelProvider.Factory 接口,同时重写 create 方法:
public class MainViewModelFactory implements ViewModelProvider.Factory {
private int countReserved;
public MainViewModelFactory(int countReserved) {
this.countReserved = countReserved;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
return (T) new MainViewModel(countReserved);
}
}
同时初始化也需要增加一个参数:
private MainViewModel mainViewModel;
mainViewModel = new ViewModelProvider(this, new MainViewModelFactory(countReserved)).get(MainViewModel.class);
LifecycleEventObserver
生命周期监听器
实现接口,重写 onStateChanged 方法,能够在类中感知到 Activity 的生命周期,并进行相关操作:
public class MyObserver implements LifecycleEventObserver {
private static final String TAG = "MyObserver";
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
switch (event) {
case ON_CREATE:
Log.d(TAG, "ON CREATE");
break;
case ON_STOP:
Log.d(TAG, "ON STOP");
break;
default:
break;
}
}
}
同时在相关 Activity 中 add 进去:
getLifecycle().addObserver(new MyObserver());
LiveData
响应式组件,观察数据变化,通常与 ViewModel 结合使用,当 ViewModel 处理耗时线程时使用
初始化,通过 getValue() 和 setValue() 方法来取放值,postValue() 用于在子线程中给 LiveData 设置数据:
private MutableLiveData<Integer> counter = new MutableLiveData<>();
counter.setValue(countReserved);
int count = 0;
if (counter.getValue() != null) {
count = counter.getValue();
}
counter.setValue(count + 1);
WorkManager
处理定时任务,适合执行一些定期和服务器交互的任务,比如周期性同步数据等等
添加依赖:
def work_version = "2.7.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
-
定义一个后台任务,并实现具体逻辑
public class SimpleWorker extends Worker { // 继承 Worker 类 private static final String TAG = "SimpleWorker"; private Context context; private WorkerParameters workerParameters; public SimpleWorker(Context context, WorkerParameters workerParameters) { super(context, workerParameters); // 调用构造函数 this.context = context; this.workerParameters = workerParameters; } @NonNull @Override public Result doWork() { // 重写 doWork() 方法,可以执行耗时逻辑 Log.d(TAG, "do work is SimpleWorker"); return Result.success(); // 返回结果 } }
-
配置后台任务
WorkRequest workRequest = new OneTimeWorkRequest.Builder(SimpleWorker.class).build(); // 执行一次后台任务 WorkRequest workRequest = new PeriodicWorkRequest.Builder(SimpleWorker.class, 15, TimeUnit.MINUTES).build(); // 周期性后台任务,传入的运行时间间隔不能少于 15min
-
通过 enqueue() 方法执行
WorkManager.getInstance(MainActivity.this).enqueue(workRequest);
其他操作
WorkRequest workRequest = new OneTimeWorkRequest.Builder(SimpleWorker.class)
.setInitialDelay(5, TimeUnit.MINUTES) // 指定延迟 5min 后执行
.addTag("simple") // 添加标签
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS) // 如果返回的是 Result.retry(),则 10s 继续执行,第一个参数如果是 LINEAR,则表示下次重试时间以线性方式延迟,如果是 EXPONENTIAL 则表示下次重试时间以指数方式延迟
.build();
WorkManager.getInstance(MainActivity.this).cancelAllWorkByTag("simple"); // 取消指定标签的后台任务,标签可以包括多个后台任务
WorkManager.getInstance(MainActivity.this).cancelWorkById(workRequest.getId()); // 取消指定 id 的后台任务,只能取消一个
WorkManager.getInstance(MainActivity.this).cancelAllWork(); // 取消所有后台任务
WorkManager.getInstance(MainActivity.this)
.getWorkInfoByIdLiveData(workRequest.getId()) // 返回的是 LiveData
.observe(MainActivity.this, new Observer<WorkInfo>() { // 监听后台的运行结果
@Override
public void onChanged(WorkInfo workInfo) {
if (workInfo.getState() == WorkInfo.State.SUCCEEDED) {
Log.d(TAG, "do work succeeded");
} else if (workInfo.getState() == WorkInfo.State.FAILED) {
Log.d(TAG, "do work failed");
}
}
});
WorkManager.getInstance(MainActivity.this) // 链式任务
.beginWith(sync)
.then(compress)
.then(upload)
.enqueue()