封面
LiveData
LiveData 能够解决的问题
1 加载页面或者其他方式触发请求服务端获取数据
从服务端成功获取到想要的数据需要渲染到界面上
我们要自己手动来实现第 2 步还是比较繁琐, 这是 LiveData 挺身而出解决掉开发人员的痛点
LiveData 的定义
LiveData 是可以观察的数据持有者, 与 Lifecycle 有紧密关系. 其中两点值得注意就是一个是说明他数据载体并且是他的变化是可以被察觉的, 第二个就是他与生命周期是有关系的, 也就是他知道生命周期是否发生了变化.
创建 LiveData
上一次分享中我们使用 ViewModel 的基础上进行改造, 将 String 类型修改为 MutableData<String>
private MutableData<String> myRandomNumber;
接下来看一看 MutableData 这个类, 他继承了 LiveData, 提供两个方法分别是 postValue 和 setValue(在 UI 线程执行),postValue (运行在 Background 线程)用于请求服务端后更新值时候来使用.
- public class MutableData<T> extends LiveData<T> {
- @Override
- protected void postValue(T value) {
- super.postValue(value);
- }
- @Override
- protected void setValue(T value) {
- super.setValue(value);
- }
- }
然后改造我们 getNumber 和 create 方法
- public MutableData<String> getNumber(){
- Log.i(TAG, "Get Number:");
- if(myRandomNumber == null){
- myRandomNumber = new MutableData<>();
- createNumber();
- }
- return myRandomNumber;
- }
- public void createNumber(){
- Log.i(TAG, "createNumber:");
- Random random = new Random();
- myRandomNumber.setValue("Number:" + (random.nextInt(10 - 1) + 1));
- }
如果 myRandomNumber 我们就创建一个 new MutableData<>() 实例赋值给他
在 createNumber 里通过 setValue 方法给 myRandomNumber 进行赋值
在 Activity 添加按钮来模拟获取数据更新界面动作
- <Button Android:layout_width="wrap_content" Android:layout_height="wrap_content"
- Android:text="更新数据"
- Android:onClick="updateRandNum"
- App:layout_constraintEnd_toEndOf="@+id/main2_textView" Android:layout_marginEnd="8dp"
- Android:layout_marginBottom="8dp" App:layout_constraintBottom_toBottomOf="parent"/>
我们通过 createNumber 函数模拟获取数据, 让 button 触发 updateRandNum 方法.
- public void updateRandNum(View view) {
- model.createNumber();
- }
通过 getNumber 方法获取数据, 然后在 observer 方法中给出 Observer 用于处理当数据发生改变后如何更新界面, 这样就不数据和界面联系到一起了.
- LiveData<String> randomNum = model.getNumber();
- randomNum.observe(this, new Observer<String>() {
- @Override
- public void onChanged(@Nullable String s) {
- textView.setText(s);
- }
- });
图
随着 Activity 一旦被销毁, 这种联系也就不复存在.
Observable 数据是可以被观察来更新界面
在 onCreate 可以定义 LiveData 更新如何更新界面
使用 LiveData 的好处
我们可以关注业务, 无需为数据更新后如何更新界面而花费精力
当 LifecycleOwner 被 destroy 了, 这种观察与被观察关系也就自动结束了
随意不会导致内存溢出的问题
什么是 Room
其实 Room 是对 SQLite 进行包裹从而提供更好用 API
ROOM 的优势
在应用中有时候也需要数据在本地进行存储, 早期开发者一定熟悉或者使用过轻量级的 Android 小型数据 SQLite, 不过近期 google 推出 Room 可能相比 SQLite 会更好好用, 而且优势明显.
Room 无需了解 SQL 一些查询语句
Room 提供了编译期间对 SQLite 的语法正确性的检查
在 Room 可以轻松地将数据映射到 java 对象无需模板代码
Room 可以轻松地 ViewModel 和 LiveData 相关联.
引入 Room 依赖
要使用 Room 我们需要引入一些依赖, 具体引入那些依赖可以参照下面代码
- def roomVersion = "1.1.1"
- implementation "android.arch.persistence.room:runtime:$roomVersion"
- annotationProcessor "android.arch.persistence.room:compiler:$roomVersion"
- androidTestImplementation "android.arch.persistence.room:testing:$roomVersion"
创建 ViewModel
这里创建 NoteViewModel 通过继承 AndroidViewModel 来实现 ViewModel
- public class NoteViewModel extends AndroidViewModel {
- private static final String TAG = "NoteViewModel";
- public NoteViewModel(@NonNull Application application) {
- super(application);
- }
- @Override
- protected void onCleared() {
- super.onCleared();
- Log.i(TAG, "onCleared:");
- }
- }
定义 Activity
在 activity 中通过之前学习来使用创建好的 noteViewModel
- public class Main2Activity extends AppCompatActivity {
- private String TAG = this.getClass().getSimpleName();
- private NoteViewModel noteViewModel;
- private MainActivityDataViewModel model;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main2);
- noteViewModel = ViewModelProviders.of(this).get(NoteViewModel.class);
- }
- public void updateRandNum(View view) {
- Snackbar.make(view,"更新",Snackbar.LENGTH_LONG)
- .setAction("更新",null).show();
- }
- }
定义 Entities
定义数据表的 schema
- @Entity(tableName = "notes")
- public class Note {
- }
通过添加注解 @Entity 将 Note 与 Room 相关联, 其中 tableName 参数为 Note 所对应的 SQLite 中的表, notes 的 Schema 就是由 Note 类的结构所确定的.
定义 DAO
创建 NoteDao 类并添加 @Dao 后, 这个类将会提供一些访问数据库的方法.
- @Dao
- public class NoteDao {
- }
定义数据库
通过为 NoteRoomDataBase 这个抽象类添加注解 @Database, 在注解中添加数据库中包含的表, 这里只添加 Note.class , 还需要指定版本号, 使用 exportSchema = false 可以抑制编译时报警.
- @Database(entities = Note.class,version = 1,exportSchema = false)
- public abstract class NoteRoomDatabase extends RoomDatabase {
- public abstract NoteDao noteDao();
- }
继承 RoomDatabase 并且是一个抽象类, 上面已经强调了
- @Database(entities = Note.class,version = 1)
- public abstract class NoteRoomDatabase extends RoomDatabase {
- public abstract NoteDao noteDao();
- private static volatile NoteRoomDatabase noteRoomInstance;
- static NoteRoomDatabase getDatabase(final Context context){
- if(noteRoomInstance == null){
- synchronized (NoteRoomDatabase.class){
- if(noteRoomInstance == null){
- noteRoomInstance = Room.databaseBuilder(context.getApplicationContext(),
- NoteRoomDatabase.class,"note_database")
- .build();
- }
- }
- }
- return noteRoomInstance;
- }
- }
熟悉 java 多线程的开发者对代码中出现的 volatile 和 synchronized 应该不会陌生, 不过不了解也没有关系, 这些就是为了防止多个线程同时来操作同一个对象.
然后使用 Room 的 databaseBuilder 方法来构建出数据库实例, 并且是单例的形式
插入数据到 Room
首先创建一个编辑界面, 这个界面是从主界面进入后, 在这个界面中可以看到一个文本输入框 (EditText) 和一个确定按钮. 输入文字后点击按钮返回到主界面同时, 把文本框的内容带回到主界面
- public class NewNoteActivity extends AppCompatActivity {
- private EditText editText;
- private Button button;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_new_note);
- button = findViewById(R.id.save_btn);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- }
- });
- }
- }
修改主界面的按钮行为, 跳转到刚刚创建好的 NewNoteActivity 界面.
- public void updateRandNum(View view) {
- // Snackbar.make(view,"更新",Snackbar.LENGTH_LONG)
- // .setAction("更新",null).show();
- Intent intent = new Intent(Main2Activity.this,NewNoteActivity.class);
- startActivityForResult(intent,1);
- }
为 Entity 添加字段, 每一个 Entity 都需要有一个主键使用 @PrimaryKey 来定义主键
- @Entity(tableName = "notes")
- public class Note {
- @PrimaryKey
- @NonNull
- private String id;
- @NonNull
- public String getId() {
- return id;
- }
- @NonNull
- public String getNote() {
- return this.note;
- }
- @NonNull
- @ColumnInfo(name = "note")
- private String note;
- public Note(String id,String note){
- this.id = id;
- this.note = note;
- }
- }
接下来在 NoteDao 接口中添加 insert(插入)方法将 Note 插入到数据库 notes 表中, 需要为此方法添加一个注解表示插入
- @Dao
- public interface NoteDao {
- @Insert
- void insert(Note note);
- }
准备好了 Dao 和 Entity 我们回到 NoteViewModel 中, 需要实例化出一个数据库对象 noteDB 而且将其与 noteDao 相关联.
- private NoteDao noteDao;
- private NoteRoomDatabase noteDB;
- public NoteViewModel(@NonNull Application application) {
- super(application);
- noteDB = NoteRoomDatabase.getDatabase(application);
- noteDao = noteDB.noteDao();
- }
然后就是实现具体 insert (插入方法), 因为对数据库的读写都是异步的, 所以这里定义了 InsertAsyncTask 任务在后台中执行插入数据的工作, 将 note 一条记录插入到数据的表 notes 中
- public void insert(Note note){
- new InsertAsyncTask(noteDao).execute(note);
- }
通过 InsertAsyncTask 任务来在后台运行插入数据的操作.
- private class InsertAsyncTask extends AsyncTask<Note, Void, Void> {
- NoteDao mNoteDao;
- public InsertAsyncTask(NoteDao noteDao) {
- this.mNoteDao = noteDao;
- }
- @Override
- protected Void doInBackground(Note... notes) {
- mNoteDao.insert(notes[0]);
- return null;
- }
- }
在 onActivityResult 方法接收从 NewNoteActivity 返回值来创建 note 插入到数据库中
- @Override
- protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- //
- final String note_id = UUID.randomUUID().toString();
- Note note = new Note(note_id,data.getStringExtra(NewNoteActivity.NOTE_ADDED));
- noteViewModel.insert(note);
- if(requestCode == 1 && resultCode == RESULT_OK){
- Toast.makeText(getApplicationContext(),"保存成功",Toast.LENGTH_LONG).show();
- }else {
- Toast.makeText(getApplicationContext(),"未保存",Toast.LENGTH_LONG).show();
- }
- }
调用 noteViewModel.insert(note); 插入数据
来源: http://www.jianshu.com/p/6136420a20b3