Room是Google提供的一个ORM库。
Room持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
Room组件架构体系
=====================================================================
Entity,Dao,Database为Room的3大基本组件,不同组件之间的关系如图
Database:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点
使用方法:用@Database来注解类,且使用 @Database注释的类应满足以下条件
是扩展RoomDatabase的抽象类。
在注释中添加与数据库关联的实体列表。
包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。
在运行时,可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 的实例。
Entity:表示数据库中的表
使用方法:用@Entit来注解实体类。
Dao:提供访问数据库的方法
使用方法:@Dao用来注解一个接口或者抽象方法。
记事本应用讲解
==================================================================
Room作为JetPack架构组件中关于SQLite数据库的架构组件,对应有着自己的知识体系。下面通过记事本Demo对Room本身组件结合MVVM架构所涉及的知识体系做一个总结.
由于涉及到另外一些组件,在有使用到时会做简要介绍.
记事本Demo效果图:
1.编写Room数据库
1.1 编写数据库实体类
@Data
@Entity(tableName = “note”)
public class Note implements Serializable {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = “title”)
private String title;
@ColumnInfo(name = “content”)
private String content;
@ColumnInfo(name = “last_update_time”)
private Date lastUpdateTime;
}
1.2 编写数据库管理类Database注意:因为实体类中存在复杂数据类型——时间类。所以在数据库管理中需要使用@TypeConverters注入转换类,对复杂类型进行统一的转换处理
@Database(entities = {Note.class},version = 1,exportSchema = false)
@TypeConverters({Converters.class})
public abstract class NoteDatabase extends RoomDatabase {
private static NoteDatabase INSTANCE;
public synchronized static NoteDatabase getINSTANCE(Context context) {
if (INSTANCE==null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),NoteDatabase.class,“note_datebase”)
.fallbackToDestructiveMigration()
.build();
}
return INSTANCE;
}
/**
在@Database中 多个entity则写多个Dao*/
public abstract NoteDao getNoteDao();
}
1.3 编写数据库操作接口Dao@Dao
public interface NoteDao {
@Insert
void insertNotes(Note… notes);
@Update
void updateNotes(Note… notes);
@Delete
void deleteNotes(Note… notes);
@Query(“delete from note”)
void deleteAllNotes();
@Query(“select * from note order by last_update_time desc”)
LiveData<List> queryAllNotes();
@Query(“select * from note where content like :pattern order by last_update_time desc”)
LiveData<List> queryNotesWithPattern(String pattern);
}
2.编写数据仓库(Repository、AsyncTask)
如果此处不引入Repository类会造成什么影响呢?为什么要引入Repository?
对数据库的操作(调用Dao)逻辑将放于ViewModel中(步骤5)。使得ViewModel中代码变得杂乱
引入仓库类,用于对数据库操作并对ViewModel暴露方法。让ViewModel专注于数据处理而非对数据库的调用,对ViewModel和Dao进一步解耦。
什么是AsyncTask?为什么需要引入AsyncTask?一个Android 已封装好的轻量级异步类,用于实现多线程、异步通信、消息传递
数据库的操作很重,一次读写操作花费 10~20ms 是很常见的,这样的耗时很容易造成界面的卡顿。所以通常情况下,条件允许情况下要避免在主线程中处理数据库。
public class NoteRepository {
private NoteDao noteDao;
private LiveData<List> allNoteLive;
public NoteRepository(Context context){
NoteDatabase database = NoteDatabase.getINSTANCE(context);
noteDao = database.getNoteDao();
allNoteLive = noteDao.queryAllNotes();
}
public LiveData<List> getAllWordLive() {
return allNoteLive;
}
public LiveData<List> queryNotesWithPattern(String pattern){
//模糊匹配注意百分号
return noteDao.queryNotesWithPattern("%"+pattern+"%");
}
public void insertNotes(Note… notes){
new InsertAsyncTask(noteDao).execute(notes);
}
public void updateNotes(Note… notes){
new UpdateAsyncTask(noteDao).execute(notes);
}
public void deleteNotes(Note… notes){
new DeleteAsyncTask(noteDao).execute(notes);
}
public void deleteAllNotes(){
new DeleteAllAsyncTask(noteDao).execute();
}
//创建副线程类,继承AsyncTask实现
static class InsertAsyncTask extends AsyncTask<Note, Void, Void>{
private NoteDao noteDao;
InsertAsyncTask(NoteDao noteDao) {
this.noteDao = noteDao;
}
@Override
protected Void doInBackground(Note… notes) {
noteDao.insertNotes(notes);
return null;
}
}
static class UpdateAsyncTask extends AsyncTask<Note, Void, Void>{
private NoteDao noteDao;
UpdateAsyncTask(NoteDao noteDao) {
this.noteDao = noteDao;
}
@Override
protected Void doInBackground(Note… notes) {
noteDao.updateNotes(notes);
return null;
}
}
static class DeleteAllAsyncTask extends AsyncTask<Note, Void, Void>{
private NoteDao noteDao;
DeleteAllAsyncTask(NoteDao noteDao) {
this.noteDao = noteDao;
}
@Override
protected Void doInBackground(Note… notes) {
noteDao.deleteAllNotes();
return null;
}
}
static class DeleteAsyncTask extends AsyncTask<Note, Void, Void>{
private NoteDao noteDao;
DeleteAsyncTask(NoteDao noteDao) {
this.noteDao = noteDao;
}
@Override
protected Void doInBackground(Note… notes) {
noteDao.deleteNotes(notes);
return null;
}
}
}
3.编写ViewModel+LiveData
为什么要使用ViewModel?
ViewModel 是数据与 UI 分离的中间层,提供了一个将数据转换为 UI 友好型数据的场所。其次,它也提供了多 Fragment 复用相同 ViewModel 的机制。
为什么要使用LiveData?LiveData 是一个可以感知 Activity 、Fragment生命周期的数据容器。当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。
此处为了方便,数据和界面的交互放在了Activity中,读者有需要可以使用Databinding对Activity进行进一步解耦.
public class NoteViewModel extends AndroidViewModel {
/**
使用数据仓库处理好的数据库交互逻辑*/
private NoteRepository repository;
public NoteViewModel(@NonNull Application application) {
super(application);
repository = new NoteRepository(application);
}
public LiveData<List> getAllNoteLive() {
return repository.getAllWordLive();
}
public LiveData<List> queryNotesWithPattern(String pattern){
return repository.queryNotesWithPattern(pattern);
}
public void insertNotes(Note… notes){
repository.insertNotes(notes);
}
public void updateNotes(Note… notes){
repository.updateNotes(notes);
}
public void deleteNotes(Note… notes){
repository.deleteNotes(notes);
}
public void deleteAllNotes(){
repository.deleteAllNotes();
}
}
4.编写界面
界面引入了RecyclerView,代替了传统ListView。如果没有使用过RecyclerView可以参照
Android 控件 RecyclerView
其中涉及到矢量图的使用,如果没有使用过矢量图可以参照
Android中使用矢量图(快速运用)
4.1 编写主页的Fragmentfragment_notes.xml
<?xml version="1.0" encoding="utf-8"?><androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/mainFragment"
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=".base.NotesFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width=“match_parent”
android:layout_height=“match_parent” />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“bottom|center”
android:layout_margin=“16dp”
android:clickable=“true”
app:srcCompat="@drawable/ic_add_white_24dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
cell_card.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout 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=“wrap_content”
android:clickable=“true”
android:orientation=“vertical”>
<androidx.cardview.widget.CardView
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_marginLeft=“8dp”
android:layout_marginTop=“8dp”
android:layout_marginRight=“8dp”
android:foreground="?selectableItemBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline1"
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:orientation=“vertical”
app:layout_constraintGuide_percent=“0.85” />
<ImageView
android:id="@+id/imageView"
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
app:layout_constraintBottom_toBottomOf=“parent”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf="@+id/guideline1"
app:layout_constraintTop_toTopOf=“parent”
app:srcCompat="@drawable/ic_keyboard_arrow_right_black_24dp" />
<TextView
android:id="@+id/textView_title"
android:layout_width=“0dp”
android:layout_height=“wrap_content”
android:layout_marginStart=“8dp”
android:layout_marginLeft=“8dp”
android:layout_marginTop=“8dp”
android:text=“TextView”
android:textSize=“24sp”
app:layout_constraintBottom_toTopOf="@+id/textView_time"
app:layout_constraintEnd_toStartOf="@+id/guideline1"
app:layout_constraintHorizontal_bias=“0.05”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toTopOf=“parent” />
<TextView
android:id="@+id/textView_time"
android:layout_width=“0dp”
android:layout_height=“wrap_content”
android:layout_marginBottom=“8dp”
android:text=“TextView”
app:layout_constraintBottom_toBottomOf=“parent”
app:layout_constraintEnd_toStartOf="@+id/guideline1"
app:layout_constraintStart_toStartOf="@+id/textView_title"
app:layout_constraintTop_toBottomOf="@+id/textView_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
4.2 编写主界面映射Fragment界面 为什么要这样处理?在fragment中编写好界面后,只需要在main_activity将界面映射过来,之后页面的切换与数据传递交由Navigation作为导航管理Fragment。期间你的路径与与数据设置只需在可视化中简单操作即可。在main_activity只需要做很少的处理即可
main_activity.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">
<fragment
android:id="@+id/fragment"
android:name=“androidx.navigation.fragment.NavHostFragment”
android:layout_width=“0dp”
android:layout_height=“0dp”
app:defaultNavHost=“true”
app:layout_constraintBottom_toBottomOf=“parent”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toTopOf=“parent”
app:navGraph="@navigation/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
5.编写RecyclerView的适配器
public class MyAdapt extends ListAdapter<Note, MyAdapt.MyViewHolder> {
public MyAdapt() {
super(new DiffUtil.ItemCallback() {
@Override
public boolean areItemsTheSame(@NonNull Note oldItem, @NonNull Note newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull Note oldItem, @NonNull Note newItem) {
return oldItem.getContent().equals(newItem.getContent()) &&
oldItem.getLastUpdateTime().equals(newItem.getLastUpdateTime());
}
});
}
/**
在适配器中创建 ViewHolder,选择item注入*/
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View itemView = layoutInflater.inflate(R.layout.cell_card, parent, false);
return new MyViewHolder(itemView);
}
/**
对每条item进行数据绑定
经常被呼叫,每次滚入滚出都会调用,所以监听绑定放入onCreateViewHolder中
*/
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Note note = getItem(position);
holder.textView_title.setText(note.getTitle());
//对日期格式化再输出
@SuppressLint(“SimpleDateFormat”) SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
holder.textView_time.setText(simpleDateFormat.format(note.getLastUpdateTime()));
holder.itemView.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putSerializable(“note”, note);
//传递参数
NavController navController = Navigation.findNavController(v);
navController.navigate(R.id.action_notesFragment_to_addFragment, bundle);
});
}
/**
自定义 holder对应 item
内部类最好使用static修饰,防止内存泄漏
*/
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView_title, textView_time;
MyViewHolder(@NonNull View itemView) {
super(itemView);
textView_title = itemView.findViewById(R.id.textView_title);
textView_time = itemView.findViewById(R.id.textView_time);
}
}
}
6.编写Fragment(对不同Fragment功能进行简要介绍)
6.1 编写主界面Fragment逻辑
主界面一些逻辑处理比较复杂,涉及到一些功能如 搜索 、清空 、数据观察 、 观察移除(数据观察这块需要注意传入的环境,博主之前没传好出现一些比较奇怪的bug,注释有标明)。包括扩展功能如:撤销删除、滑动删除 、 矢量图定点绘制(需要有一定的图形代码编写基础)
public class NotesFragment extends Fragment {
//final String TAG = “mainTag”;
//视图层
private NoteViewModel noteViewModel;
private RecyclerView recyclerView;
private MyAdapt myAdapt;
//数据层
private LiveData<List> noteLive;
private FragmentActivity fragmentActivity;
//操作标识,只有更新时候才上移。更新删除保持不动
private boolean undoAction;
/**
实时保存数据列表,防止通过liveData时直接获取元素时因为异步获取,发生空指针异常
主要用于标记滑动删除中的撤销
*/
private List allNotes;
public NotesFragment() {
// 显示菜单栏目
setHasOptionsMenu(true);
}
/**
当复合的选项菜单被选中,其监听在此处处理。如:清空数据功能*/
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
//多个选项菜单,根据不同菜单项的R.id进行匹配操作
if (item.getItemId() == R.id.clear_data) {//清空数据前需要弹窗确认
AlertDialog.Builder builder = new AlertDialog.Builder(fragmentActivity);
builder.setTitle(“清空数据”);
builder.setPositiveButton(“确定”, (dialog, which) -> noteViewModel.deleteAllNotes());
builder.setNegativeButton(“取消”, (dialog, which) -> {
});
builder.create();
builder.show();
}
return super.onOptionsItemSelected(item);
}
/**
初始化菜单栏,并实现显式菜单项功能show*/
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_menu, menu);
//搜索
SearchView searchView = (SearchView) menu.findItem(R.id.app_bar_search).getActionView();
//控制搜索框长度
int maxWidth = searchView.getMaxWidth();
searchView.setMaxWidth((int) (0.5 * maxWidth));
//设置搜索框的实时监听
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
//去除多余前后空格
String pattern = newText.trim();
noteLive = noteViewModel.queryNotesWithPattern(pattern);
/*
注意:重新赋予LiveData后最好先移除之前的观察。
大坑:观察的移除和注入都必须是getViewLifecycleOwner获取的LifecycleOwner。其对应fragment的生命周期
*/
noteLive.removeObservers(getViewLifecycleOwner());
//对LiveData重新进行观察,注意Owner的生命周期,需要注入fragment的owner
noteLive.observe(getViewLifecycleOwner(), notes -> {
//备份列表
allNotes = notes;
//将观察的数据注入RecycleAdapt中
myAdapt.submitList(notes);
});
//修改为返回true后事件不会再向下传递,默认false会继续传递
return true;
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_notes, container, false);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
fragmentActivity = requireActivity();
//初始化当前页面所用ViewModel,注入activity
noteViewModel = new ViewModelProvider(fragmentActivity).get(NoteViewModel.class);
//初始化recyclerView
recyclerView = fragmentActivity.findViewById(R.id.recyclerView);
myAdapt = new MyAdapt();
//recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));//大坑
recyclerView.setLayoutManager(new LinearLayoutManager(fragmentActivity));//大坑,不设置布局不显示
recyclerView.setAdapter(myAdapt);
//观察数据列表
noteLive = noteViewModel.getAllNoteLive();
//需要注入fragment的owner
noteLive.observe(getViewLifecycleOwner(), notes -> {
//Log.d(TAG, "onChanged: " + notes);
//读取当前显示列表的个数
int temp = myAdapt.getItemCount();
//备份列表
allNotes = notes;
//如果数据变化后的元素 > 变化前的个数 说明是添加操作,进行
if (notes.size() > temp && !undoAction) {
/*
滚动到首部,增强视觉效果
注意定时任务,否则太快会定位到第二行
*/
new Timer().schedule(new TimerTask() {
public void run() {
写在最后在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。
如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!
加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。 |