irpas技术客

Android JetPack架构——结合记事本Demo一篇打通对Sqlite的增删改查结合常用jetpack架构应用_普通网友

irpas 584

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 编写主页的Fragment

fragment_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.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Android #Room持久性库在 #sqlite