Android---Jetpack之Room

目录

应用实现

数据库升级

异常处理

Schema 文件

销毁和重建策略

 预填充数据库


Android 采用 SQLite 作为数据库存储,开源社区常见的 ORM(Object Relational Mapping)库有ORMLite、GreenDAO等。Room 和其它库一样,也是在 SQLite 上提供了一层封装。

 \bullet Entity:实体类,对应的是数据库的一张表结构,使用注解 @Entity 标记。相当于 JavaBean

\bullet Dao:包含访问数据库的一些列方法,使用注解 @Dao 标记。

\bullet DataBase:数据库持有者,作为与应用持久化相关数据的底层连接的主要接入点。使用注解 @Database 标记,另外需满足以下条件:定义的类必须是一个继承于 RoomDatabase 的抽象类,在注解中需要定义与数据库相关联的实体类列表。包含一个没有参数的抽象方法并且返回一个 Dao对象。

优化:当数据发生变化时,通过 LiveData 通知 View 层,实现数据自动更新。 

 Room + ViewModel + LiveData

应用实现

步骤1:使用 room,在build.gradle(app级)添加 room 依赖。

implementation 'androidx.room:room-runtime:2.3.0'
annotationProcessor 'androidx.room:room-compiler:2.3.0'

 注意:当你添加了 room 依赖后,可能出现如下错误

3 issues were found when checking AAR metadata:

原因是你的 room 版本过高或者 Gradle 版本过低,所以可以通过提升 Gradle 版本或者使用低版本的 room。 

步骤2:布局文件

avtivity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.11" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.22" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="205dp" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="添加"
        android:onClick="mInsert"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toStartOf="@+id/guideline4"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="mDelete"
        android:text="删除"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline4"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="修改"
        android:onClick="mUpdate"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toStartOf="@+id/guideline4"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="清空"
        android:onClick="mClear"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline4"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="409dp"
        android:layout_height="584dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline3"
        app:layout_constraintVertical_bias="0.428" />
</androidx.constraintlayout.widget.ConstraintLayout>

item.xml(对学生信息的展示是用的 RecyclerView) 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="70dp">

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="19"
        android:textSize="20sp"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline7"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Jack"
        android:textSize="24sp"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline6"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="1.0" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="102dp" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="300dp" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1"
        android:textSize="20sp"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toStartOf="@+id/guideline6"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

步骤3:创建 Entity 实体类

Student.java

package com.example.room2;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;

@Entity(tableName = "student")
public class Student {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
    public int id;

    @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
    public String name;

    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    public int age;

//    @ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER)
//    public int sex;

    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    /**
     *TODO room 只需要一个构造方法,当我们有多个的时候,打上 @Ignore 标签,Room 就不会管它了
     * 上面的构造方法给 Room 用,下面的构造方法就可以给我们自己用
     */

    @Ignore
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Ignore
    public Student(int id) {
        this.id = id;
    }
}

步骤4: Dao 类

StudentDao.java

package com.example.room2;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

@Dao
public interface StudentDao {
    @Insert
    void insertStudent(Student... students);

    @Delete
    void deleteStudent(Student... students);

    @Query("DELETE FROM student")
    void deleteAllStudent();

    @Update
    void updateStudent(Student... students);

    @Query("SELECT * FROM STUDENT")
    LiveData<List<Student>> getAllStudentsLive();

    @Query("SELECT * FROM student WHERE id = :id")
    List<Student> getStudentById(int id);
}

 步骤5:Database 能够返回一个 Dao 对象

package com.example.room2;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;

/**
 * 通过 DataBase 我们可以拿到 Dao
 */
@Database(entities = {Student.class}, version = 1, exportSchema = false)
public abstract class MyDataBase extends RoomDatabase {

    private static final String DATABASE_NAME = "my_db.db";
    private static MyDataBase mInstance; // 单例

    //private MyDataBase(){}// Room 里不需要,有了反而会报错

    public static synchronized MyDataBase getInstance(Context context){
        if (mInstance == null) {
            //mInstance = new MyDataBase();// room 不这么写
            mInstance = Room.databaseBuilder(context.getApplicationContext(),
                            MyDataBase.class,
                            DATABASE_NAME)
                            .build();
        }
        return mInstance;
    }


    /**
     * 返回一个 Dao
     */
    public abstract StudentDao getStudentDao();

}

 步骤6:Repository 与 Room 打交道

studentRepository.java

package com.example.room2;

import android.content.Context;
import android.os.AsyncTask;

import androidx.lifecycle.LiveData;

import java.util.List;

/**
 * 持有 DAO 与 ROOM 打交道
 */
public class StudentRepository {
    private StudentDao studentDao;

    public StudentRepository(Context context) {
        MyDataBase dataBase = MyDataBase.getInstance(context);
        this.studentDao = dataBase.getStudentDao();
    }

    /**
     * 插入
     * @param students 可变参数
     */
    public void insertStudents(Student... students){
        new insertStudentsTask(studentDao).execute(students);
    }
    // 异步操作
    class insertStudentsTask extends AsyncTask<Student, Void, Void> {

        private StudentDao studentDao;

        public insertStudentsTask(StudentDao studentDao) {
            this.studentDao = studentDao;
        }

        @Override
        protected Void doInBackground(Student... students) {
            studentDao.insertStudent(students);
            return null;
        }
    }

    /**
     * 更新
     * @param students 可变参数
     */
    public void updateStudent(Student... students){
        new updateStudentsTask(studentDao).execute(students);
    }
    //异步操作
    class updateStudentsTask extends AsyncTask<Student, Void, Void>{
        private StudentDao studentDao;

        public updateStudentsTask(StudentDao studentDao) {
            this.studentDao = studentDao;
        }
        @Override
        protected Void doInBackground(Student... students) {
            studentDao.updateStudent(students);
            return null;
        }
    }

    /**
     * 删除
     * @param students 可变参数
     */
    public void deleteStudent(Student... students){
        new deleteStudentsTask(studentDao).execute(students);
    }
    //异步操作
    class deleteStudentsTask extends AsyncTask<Student, Void, Void>{
        private StudentDao studentDao;

        public deleteStudentsTask(StudentDao studentDao) {
            this.studentDao = studentDao;
        }
        @Override
        protected Void doInBackground(Student... students) {
            studentDao.deleteStudent(students);
            return null;
        }
    }

    /**
     * 删除所以数据
     */
    public void deleteAllStudents(){
        new deleteAllStudentsTask(studentDao).execute();
    }
    //异步操作
    class deleteAllStudentsTask extends AsyncTask<Void, Void, Void>{
        private StudentDao studentDao;

        public deleteAllStudentsTask(StudentDao studentDao) {
            this.studentDao = studentDao;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            studentDao.deleteAllStudent();
            return null;
        }
    }

    /**
     * 获取所以数据,
     * LiveData 的方式与 UI 界面进行数据绑定
     */
    public LiveData<List<Student>> getAllStudentsLive(){
        return studentDao.getAllStudentsLive();
    }
}

 步骤7:ViewModel,与 Repository 打交道

StudentViewModel.java

package com.example.room2;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import java.util.List;

/**
 * 使用 AndroidViewModel 就可以有上下文
 */
public class StudentViewModel extends AndroidViewModel {

    private StudentRepository studentRepository;

    public StudentViewModel(@NonNull Application application) {
        super(application);
        studentRepository = new StudentRepository(application);
    }

    /**
     * 插入
     */
    public void insertStudents(Student... students){
        studentRepository.insertStudents(students);
    }
    /**
     * 更新
     */
    public void deleteStudent(Student... students){
        studentRepository.deleteStudent(students);
    }
    /**
     * 删除
     */
    public void deleteAllStudents(){
        studentRepository.deleteAllStudents();
    }
    /**
     * 删除所以
     */
    public void updateStudent(Student... students){
        studentRepository.updateStudent(students);
    }

    /**
     * 获取所以数据
     */
    public LiveData<List<Student>> getAllStudentsLive(){
        return studentRepository.getAllStudentsLive();
    }
}

步骤8: 给 RecyclerView 添加适配器

StudentRecyclerViewAdapter.java

package com.example.room2;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class StudentRecyclerViewAdapter extends RecyclerView.Adapter {

    List<Student> students;

    public StudentRecyclerViewAdapter(List<Student> students) {
        this.students = students;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View root = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
        return new MyViewHolder(root);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Student student = students.get(position);
        TextView tvId = holder.itemView.findViewById(R.id.textView);
        tvId.setText(String.valueOf(student.id));
        TextView tvName = holder.itemView.findViewById(R.id.textView2);
        tvName.setText(student.name);
        TextView tvAge = holder.itemView.findViewById(R.id.textView3);
        tvAge.setText(String.valueOf(student.age));
    }

    @Override
    public int getItemCount() {
        return students == null ? 0 : students.size();
    }

    static class MyViewHolder extends RecyclerView.ViewHolder{

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    public void setStudents(List<Student> students){
        this.students = students;
    }
}

步骤9:MainActivity.java 

package com.example.room2;

import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    StudentViewModel viewModel;
    private StudentRecyclerViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        List<Student> students = new ArrayList<>();
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new StudentRecyclerViewAdapter(students);
        recyclerView.setAdapter(adapter);

        viewModel = new ViewModelProvider(this,
                new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(StudentViewModel.class);
        //TODO 监听 LiveData 数据的改变 --> 更新 UI
        viewModel.getAllStudentsLive().observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(List<Student> students) {
                adapter.setStudents(students);
                adapter.notifyDataSetChanged();
            }
        });
    }

    public void mInsert(View view) {
        Student s1 = new Student("Jack", 18);
        Student s2 = new Student("Rose", 20);
        viewModel.insertStudents(s1, s2);
    }


    public void mDelete(View view) {
        Student s1 = new Student(2);
        viewModel.deleteStudent(s1);
    }


    public void mUpdate(View view) {
        Student s1 = new Student(3, "Json", 20);
        viewModel.updateStudent(s1);
    }


    public void mClear(View view) {
        viewModel.deleteAllStudents();
    }
}

数据库升级

使用 Migration 升级数据库

问题:如果用户设备上数据库版本为1,而当前要安装的 App 数据库版本为3,怎么办?

Room 会先判断当前有没有直接从1到3的方案,如果有,就直接执行从1到3的升级方案,如果没有,那么 Room 会按照顺序先后执行 Migration(1,2)、Migration(2,3)以完成升级。

在MyDataBase 里添加如下代码:

    /**
     * 数据库升级
     */
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE student ADD COLUMN sex INTEGER NOT NULL DEFAULT 1");
        }
    };

上面代码是在 student 表里添加了一个字段,所以我们的 Student.java 实体类也要添加这个属性

    @ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER)
    public int sex;

最后,在 MyDataBase.java修改如下两个地方,即课完成版本的升级。

异常处理

假设我们将数据库版本升级到4,却没有为此写相应的 Migration,则会出现一个 IllegalStateException 异常,加入 fallbackToDestructiveMigration(),该方法在出现升级异常时,重建数据表,同时数据也会丢失

Schema 文件

Room 在每次数据库升级过程中,都会导出一个 Schema 文件,这是一个 json 格式的文件,其中包含了数据库的基本信息,有了该文件,开发者能清楚的知道数据库的历次变更情况,极大的方便了开发者排查问题。

销毁和重建策略

在 SQLite 中修改表结构比较麻烦,例如,我们想将 Student 表中 sex 字段类型从 INTEGER 修改为 TEXT,最好的方式是采用销毁与重建策略,大致分为以下步骤:

  \bullet 创建一张符合表结构要求的临时表 temp_student

  \bullet 将数据从旧表 student 复制到临时表 temp_student

  \bullet 删除旧表 student

  \bullet 将临时表 temp_student 重命名为 student

在 MyDataBase.java 里添加如下代码:

    /**
     * 修改一张表的字段
     */
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            // 1. 创建一张临时表
            database.execSQL("CREATE TABLE temp_student (" +
                    "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                    "name TEXT,"+
                    "age INTEGER NOT NULL,"+
                    "sex TEXT DEFAULT 'M')");
            // 2. 复制数据到临时表
            database.execSQL("INSERT INTO temp_student (name, age, sex)" +
                    "SELECT name, age, sex FROM student");
            // 3. 删除旧表 student
            database.execSQL("DROP TABLE student");
            // 4. 将临时表 temp_student 重命名为 student
            database.execSQL("ALTER TABLE temp_student RENAME TO student");
        }
    };

预填充数据库

有时候我们希望应用自带一些数据供我们使用,我们可以将数据库文件放入 assets 目录一起打包发布,在用户首次打开 App 时,使用 createFromAsset() 和 createFromFile() 创建 Room 数据库。

 

完整demo

上面应用的实现是在 room2 model 里。

链接:https://pan.baidu.com/s/1_Dm1BHW_hq5V_8K12sjjLQ 
提取码:4jti

猜你喜欢

转载自blog.csdn.net/qq_44950283/article/details/129897537