Room库是对SQLite数据库的抽象,使用上更加方便高效。
它包含以下几部分:
Entity:它代表数据库中一条数据(数据库表中的一行)。
Dao:包含用于访问数据库的方法。
Database:数据库持有者,充当与应用程序持久化的、关系型的数据的底层连接的主要访问点。用
@Database
注解的类应满足以下条件:
1.是一个继承 RoomDatabase 的抽象类。
2.在注释中包含与数据库相关联的实体列表。
3.包含一个具有0个参数的抽象方法,并返回用@Dao注释的类。
4.在运行时,可以通过调用Room.databaseBuilder()或Room.inMemoryDatabaseBuilder()获取数据库实例。Migration:数据库版本迁移,数据发生改变时就需要从低版本迁移到高版本。
AsyncTask:开启子线程。数据库操作比较耗时,需开启子线程。
Repository:将数据访问放到这一层,目的是与应用程序解耦。
RecyclerView、RecyclerView Adapter
文中的Demo:
RoomLiveDataDemo。
room_AsyncTaskAndRepository。
room_recyclerView。
一. Entity、Dao、Database、liveData
接下来使用Entity、Dao、Database来创建一个数据库工程,并结合ViewModel中的liveData。
1. 要使用room,先根据自己的需要添加相应的依赖
在应用或模块的 build.gradle 文件中添加所需工件的依赖项:
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
2. 分别创建Entity、Dao、Database文件
Entity:
package com.example.roomdemo;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity
public class Word {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "English")
private String word;
@ColumnInfo(name = "chinese")
private String chinese;
public Word(String word, String chinese) {
this.word = word;
this.chinese = chinese;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public String getChinese() {
return chinese;
}
public void setChinese(String chinese) {
this.chinese = chinese;
}
}
Dao:
package com.example.roomdemo;
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 WordDao {
@Insert
void insertWords(Word... words);
@Update
void updateWords(Word... words);
@Delete
void deleteWords(Word... words);
@Query("DELETE FROM WORD")
void deleteAllWords();
@Query("SELECT * FROM WORD ORDER BY ID DESC")
LiveData> getAllWords();
}
Database:
package com.example.roomdemo;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {Word.class}, version = 1, exportSchema = false)
public abstract class WordDatabase extends RoomDatabase {
public abstract WordDao getWordDao();
}
在MainActivity中的使用:
package com.example.roomdemo;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.room.Room;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.List;
public class MainActivity extends AppCompatActivity {
WordDatabase wordDatabase;
WordDao wordDao;
TextView textView;
Button buttonInsert, buttonDelete, buttonUpdate, buttonClear;
LiveData> allWordsLive;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
wordDatabase = Room.databaseBuilder(this, WordDatabase.class, "word_database").allowMainThreadQueries().build();
wordDao = wordDatabase.getWordDao();
textView = findViewById(R.id.textView);
allWordsLive = wordDao.getAllWords();
allWordsLive.observe(this, new Observer>() {
@Override
public void onChanged(List words) {
StringBuilder text = new StringBuilder();
for (Word word: words) {
text.append(word.getId()).append("、").append(word.getWord()).append(":").append(word.getChinese()).append("\n");
}
textView.setText(text);
}
});
buttonInsert = findViewById(R.id.button1);
buttonDelete = findViewById(R.id.button2);
buttonUpdate = findViewById(R.id.button3);
buttonClear = findViewById(R.id.button4);
buttonInsert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Word word1 = new Word("Hello", "你好");
Word word2 = new Word("World", "世界");
wordDao.insertWords(word1, word2);
}
});
buttonDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Word word = new Word("aa", "bb");
word.setId(30);
wordDao.deleteWords(word);
}
});
buttonUpdate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Word word = new Word("Thanks", "谢谢");
word.setId(29);
wordDao.updateWords(word);
}
});
buttonClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
wordDao.deleteAllWords();
}
});
}
}
界面就不说,可以看RoomLiveDataDemo。
二. AsyncTask和Repository的使用
接下来,我们将上个Demo进行如下优化:
- 将DataBase做成单粒。
public abstract class WordDatabase extends RoomDatabase {
private static WordDatabase INSTANCE;
static synchronized WordDatabase getDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), WordDatabase.class,"wordDatabase").build();
}
return INSTANCE;
}
public abstract WordDao getWordDao();
}
- 使用AsyncTask开启子线程,将操作数据库等耗时操作放到子线程中执行。
static class InsertAsyncTask extends AsyncTask {
private WordDao wordDao;
public InsertAsyncTask(WordDao wordDao) {
this.wordDao = wordDao;
}
@Override
protected Void doInBackground(Word... words) {
wordDao.insertWords(words);
return null;
}
}
注意:AsyncTask类作为内部类时,需用static修饰,否则会内存泄漏。
- 将数据操作的部分封装到repository中。ViewModel只负责管理界面上的数据,而数据的获取应该交给repository管理
完整代码可切到room_AsyncTaskAndRepository分支查看。
三. 结合RecyclerView的使用
RecyclerView需要Adapter去设置数据:
recyclerView = findViewById(R.id.recyclerView);
myAdapter1 = new MyAdapter(false);
myAdapter2 = new MyAdapter(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(myAdapter1);
这里创建了两个adapter,一个是普通的线形列表,一个是卡片列表。recyclerView需要设置setLayoutManager,这里设置的线形布局LinearLayoutManager,如果需要网格布局,可以设置GridLayoutManager。
adapter中需要一个内部类ViewHolder,去承载子视图,我理解像iOS的contentView:
public class MyAdapter extends RecyclerView.Adapter {
ListallWords = new ArrayList<>();
boolean useCardView;
public MyAdapter(boolean useCardView) {
this.useCardView = useCardView;
}
public void setAllWords(List allWords) {
this.allWords = allWords;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView;
if (useCardView) {
itemView = inflater.inflate(R.layout.cell_card,parent,false);
} else {
itemView = inflater.inflate(R.layout.cell_normal,parent,false);
}
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
Word word = allWords.get(position);
holder.textViewNumber.setText(String.valueOf(position + 1));
holder.textViewEnglish.setText(word.getWord());
holder.textViewChinese.setText(word.getChinese());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri uri = Uri.parse("https://www.baidu.com");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
holder.itemView.getContext().startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return allWords.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView textViewNumber, textViewEnglish, textViewChinese;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
textViewNumber = itemView.findViewById(R.id.textViewNumber);
textViewEnglish = itemView.findViewById(R.id.textViewEnglish);
textViewChinese = itemView.findViewById(R.id.textViewChinese);
}
}
}
完整Demo,请参考上面room_recyclerView。
四. 数据库版本迁移
数据库版本迁移,简单来说,就是保证现有数据不丢失情况下,对数据库结构进行一些改动,比如添加、删除一些字段。
1. 添加字段
比如在Word中加一个test字段:
因为数据库结构已改变,重新运行会报错:
Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
如果不在乎数据丢不丢失,可以直接用fallbackToDestructiveMigration,它会将原有数据库删除,重新创建数据库。
做数据库迁移需要:
- 首先要改Database版本号。
- 其次设置迁移策略。
如下图红色标出了Database中针对此次变更所做的修改:
2. 删除字段
删除字段,迁移时比较麻烦,sqlite没有直接的drop操作,需要以下四步操作来做迁移:
- 创建一个新表。
- 将旧表中对应的内容插入到新表中。
- 删除旧表。
- 将新表改名为旧表的名。