1.1 room
android 官方推荐的数据库框架,room主要包含三个组件:roomDatabase,entity,Dao. 使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,
然后再将对这些实体的所有更改保存回数据库中。最后,应用使用实体来获取和设置与数据库中的表列相对应的值。
1.2 liveData
LiveData是可以在给定生命周期内观察到的数据持有者类。 这意味着可以将Observer与LifecycleOwner成对添加,并且只有在配对的LifecycleOwner处于活动状态时,才会向该观察者通知有关包装数据的修改。
如果LifecycleOwner的状态为STARTED或RESUMED,则将其视为活动状态。
通过observeForever添加的观察者被视为始终处于活动状态,因此将始终收到有关修改的通知。 对于这些观察者,您应该手动调用removeObserver。
liveData可以订阅数据库的变化,在子线程中查询数据,并且在主线程中更新UI。
1.3 ViewModel
ViewModel是一个类,负责为Activity或Fragment准备和管理数据。它还处理活动/片段与应用程序其余部分的通信(例如,调用业务逻辑类)。
始终与范围(片段或活动)相关联地创建ViewModel,只要范围是活动的,ViewModel就会保留。例如。如果是活动,则直到完成。换句话说,这意味着如果ViewModel的所有者因配置更改(例如旋转)而被销毁,则不会销毁它。所有者的新实例将重新连接到现有的ViewModel。
ViewModel的目的是获取并保留活动或片段所需的信息。活动或片段应能够观察ViewModel中的更改。 ViewModel通常通过LiveData或Android数据绑定公开此信息。您也可以使用自己喜欢的框架中的任何可观察性构造。
ViewModel的唯一责任是管理UI的数据。它绝不能访问您的视图层次结构或保留对活动或片段的引用。
1.4 RecyclerView
用来显示数据,
第一步:添加依赖
room,recyclerview,lifecycle.
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
// UI
implementation "com.google.android.material:material:$rootProject.materialVersion"
使用java8,在模块的android下
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
在project 的gradle中添加依赖版本号:
ext {
roomVersion = '2.2.1'
archLifecycleVersion = '2.2.0-rc02'
coreTestingVersion = '2.1.0'
materialVersion = '1.0.0'
}
第二步:创建room数据库
①创建bean类
②创建Dao接口
③创建roomDataBase
④创建Reposite
entity:
/**
* 每个@Entity类代表一个SQLite表。
* 注释您的类声明以表明它是一个实体。如果希望表名与类名不同,则可以指定表名。这将表命名为“ word_table”。
* */
@Entity(tableName = "word_table")
public class Word {
/**
* 每个实体都需要一个主键。为了简单起见,每个单词都充当其自己的主键。
* */
@PrimaryKey
/**
* 表示参数,字段或方法的返回值永远不能为null。
* */
@NonNull
/**
* 如果希望与成员变量的名称不同,请在表中指定列的名称。
* */
@ColumnInfo(name = "word")
private String mWord;
public Word(@NonNull String word) {this.mWord = word;}
public String getWord(){return this.mWord;}
}
Dao接口
/**
* 功能:
* 1.按字母顺序排列所有单词
* 2.插入一个词
* 3.删除所有单词
* */
@Dao
public interface WordDao {
/**
* 如果冲突中选择的策略与列表中已有的单词完全相同,则会忽略该单词
* */
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insert(Word word);
@Query("DELETE FROM word_table ")
void deleteAll();
@Query("SELECT * from word_table ORDER BY word ASC")
LiveDatagetAlphabetizedWords();
}
创建roomDatabase:
**
* Room是SQLite数据库之上的数据库层。
* Room负责处理您以前使用NET处理的普通任务SQLiteOpenHelper。
* Room使用DAO向其数据库发出查询。
* 默认情况下,为避免UI性能下降,Room不允许您在主线程上发出查询。当Room查询返回时LiveData,查询将自动在后台线程上异步运行。
* Room提供了SQLite语句的编译时检查。
* */
@Database(entities = {Word.class, People.class},version = 2,exportSchema = false)
public abstract class WordRoomDatabase extends RoomDatabase {
/**
* 您使用注释该类为Room数据库,@Database并使用注释参数声明该数据库中的实体并设置版本号。每个实体对应一个将在数据库中创建的表。
* 数据库迁移不在此代码实验室的范围内,因此exportSchema在此处设置为false以避免生成警告。
* 在实际的应用程序中,您应考虑为Room设置目录以用于导出架构,以便可以将当前架构签入版本控制系统
* */
public abstract WordDao wordDao();
public abstract PeopleDao peopleDao();
/**
* 我们定义了singleton,WordRoomDatabase,以防止同时打开多个数据库实例。
* */
private static volatile WordRoomDatabase INSTANCE;
private static final int NUMBER_OF_THREADS = 4;
/**
* 我们创建了一个ExecutorService带有固定线程池的,您将使用该池在后台线程上异步运行数据库操作。
* */
public static final ExecutorService databaseWriteExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);
/**
* 您通过为每个@Dao创建一个抽象的“ getter”方法来使数据库提供其DAO。
* */
/**
* getDatabase返回单例。它将在首次访问数据库时使用Room的数据库构建器RoomDatabase在类的应用程序上下文
* 中创建一个对象WordRoomDatabase并将其命名,从而创建数据库"word_database"。
* */
public static WordRoomDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (WordRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
WordRoomDatabase.class, "word_database")
.addCallback(sRoomDatabaseCallback)
.addMigrations(MIGRATION_1_2)
.build();
}
}
}
return INSTANCE;
}
private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
// If you want to keep data through app restarts,
// comment out the following block
databaseWriteExecutor.execute(() -> {
// Populate the database in the background.
// If you want to start with more words, just add them.
WordDao dao = INSTANCE.wordDao();
dao.deleteAll();
Word word = new Word("Hello");
dao.insert(word);
word = new Word("World");
dao.insert(word);
PeopleDao dao1 = INSTANCE.peopleDao();
People people = new People();
dao1.deleteAll();
people.setName("张三");
people.setAge(19);
people.setSex("男");
People people1 = new People();
people1.setName("李红");
people1.setAge(20);
people1.setSex("女");
dao1.insert(people1);
});
}
};
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `people_table` (`name` TEXT PRIMARY KEY NOT NULL, `age` INTEGER NOT NULL,'sex' TEXT NOT NULL)");
}
};
/* static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE people_table "
+ " ADD COLUMN pub_year INTEGER");
}
};*/
}
创建Repository:
public class WordRepository {
private WordDao mWordDao;
private LiveDatamAllWords;
private PeopleDao mPeopleDao;
private LiveDatamPeoples;
// 请注意,为了对WordRepository进行单元测试,必须删除Application依赖项。
// 这增加了复杂性和更多的代码,并且该示例与测试无关。
// See the BasicSample in the android-architecture-components repository at
// https://github.com/googlesamples
public WordRepository(Application application) {
WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
mWordDao = db.wordDao();
mAllWords = mWordDao.getAlphabetizedWords();
mPeopleDao = db.peopleDao();
mPeoples = mPeopleDao.getAllPeople();
}
/**
* words的方法
* */
// Room在单独的线程上执行所有查询。
// 观察到的LiveData将在数据更改时通知观察者。
public LiveDatagetAllWords() {
return mAllWords;
}
// 您必须在非UI线程上调用此函数,否则您的应用程序将引发异常。
// Room确保您不会在主线程上执行任何长时间运行的操作,从而阻止了UI。
public void insert(Word word) {
WordRoomDatabase.databaseWriteExecutor.execute(() -> {
mWordDao.insert(word);
});
}
/**
* people的方法
* */
public LiveDatagetmPeoples() {
return mPeoples;
}
public void insert(People people){
WordRoomDatabase.databaseWriteExecutor.execute(()->{mPeopleDao.insert(people);});
}
}
第三步:创建ViewModel
**
* A ViewModel以生命周期感知的方式保存应用程序的UI数据,以在配置更改后生存下来。
* 将应用程序的UI数据与Activity和Fragment类分开,可以更好地遵循单一职责原则:
* 您的活动和片段负责将数据绘制到屏幕上,而您ViewModel可以负责保存和处理UI所需的所有数据。
* */
public class WordViewModel extends AndroidViewModel {
/**
* 1.创建了一个名为的类WordViewModel,该类获取Application作为参数并扩展AndroidViewModel。
* 2.添加了一个私有成员变量来保存对存储库的引用。
* 3.添加了一种getAllWords()方法来返回缓存的单词列表。
* 4.实现了创建的构造函数WordRepository。
* 5.在构造函数中,allWords使用存储库初始化LiveData。
* 6.创建了一个包装insert()方法,该方法调用存储库的insert()方法。这样,insert()UI 的实现就被封装了。
* */
private WordRepository mRepository;
private LiveDatamAllWords;
private LiveDatamPeoples;
public WordViewModel(@NonNull Application application) {
super(application);
mRepository = new WordRepository(application);
mAllWords = mRepository.getAllWords();
mPeoples = mRepository.getmPeoples();
}
public LiveDatagetAllWords() { return mAllWords; }
public void insert(Word word) { mRepository.insert(word); }
public LiveDatagetmPeoples() { return mPeoples; }
public void insert(People people) { mRepository.insert(people); }
}
第四步:创建RecyclerView布局
public class WordListAdapter extends RecyclerView.Adapter {
class WordViewHolder extends RecyclerView.ViewHolder {
private final TextView wordItemView;
private WordViewHolder(View itemView) {
super(itemView);
wordItemView = itemView.findViewById(R.id.textView);
}
}
private final LayoutInflater mInflater;
private List mWords; // Cached copy of words
public WordListAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
@NonNull
@Override
public WordViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recyclerview_item,parent,false);
return new WordViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull WordViewHolder holder, int position) {
if (mWords != null) {
Word current = mWords.get(position);
holder.wordItemView.setText(current.getWord());
} else {
// Covers the case of data not being ready yet.
holder.wordItemView.setText("No Word");
}
}
public void setWords(List words){
mWords = words;
notifyDataSetChanged();
}
// getItemCount() is called many times, and when it is first called,
// mWords has not been updated (means initially, it's null, and we can't return null).
@Override
public int getItemCount() {
if (mWords != null)
return mWords.size();
else return 0;
}
}
第五步:在MainActivity获取ViewModel的实例,并通过ViewModel对数据库进行增删改查
private void init(){
recyclerview = findViewById(R.id.recyclerview);
adapter = new WordListAdapter(this);
recyclerview.setLayoutManager(new LinearLayoutManager(this));
recyclerview.setAdapter(adapter);
mWordViewModel = new ViewModelProvider(this).get(WordViewModel.class);
mWordViewModel.getAllWords().observe(this, new Observer() {
@Override
public void onChanged(@Nullable final List words) {
// Update the cached copy of the words in the adapter.
adapter.setWords(words);
}
});
mWordViewModel.getmPeoples().observe(this, new Observer() {
@Override
public void onChanged(List people) {
for(People people1 :people) {
Log.d("people Data", "name:" + people1.getName()+"age:"+people1.getAge()+"sex:"+people1.getSex());
}
}
});
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, NewWordActivity.class);
startActivityForResult(intent, NEW_WORD_ACTIVITY_REQUEST_CODE);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == NEW_WORD_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
Word word = new Word(data.getStringExtra(NewWordActivity.EXTRA_REPLY));
mWordViewModel.insert(word);
} else {
Toast.makeText(
getApplicationContext(),
R.string.empty_not_saved,
Toast.LENGTH_LONG).show();
}
}