Room + ViewModel +LiveData 实践案例&原理

3,127 阅读4分钟

PreView

Room + ViewModel + LiveData 是JetPack中的组件,是Android 2019 最重要的技术更新之一,使用JetPack 可以帮助我们快速打造稳定,敏捷的APP。

Coroutine 则是Kotlin 支持的流式编程风格,支持线程调度,可以慢慢取代RxJava了。

1、Room

1)啥是Room?

    Room是在数据库组件,主要是对Sqlite的操作做了一层包装,通过注解的方式方便使用。

2)如何用?

 Room 主要由三个部分组成;
 @Database 数据库
 @Dao 表 (定义增删改查)
 @Entity 数据字段

@Database(entities = [VideoInfo::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
 abstract fun videoInfoDao(): VideoInfoDao
 abstract fun screenShotInfoDao(): ScreenShotInfoDao]
}
@Dao
interface VideoInfoDao {

 @Query("SELECT * from VideoInfo")
 fun getAll(): LiveData<List<VideoInfo>>

 @Insert
 suspend fun insert(item: VideoInfo)

 @Insert
 suspend fun insertAll(items: List<VideoInfo>)

 @Delete
 suspend fun delete(item: VideoInfo): Int

 @Update(onConflict = OnConflictStrategy.REPLACE)
 suspend fun update(item: VideoInfo): Int

}
@Entity
class VideoInfo {
 @PrimaryKey(autoGenerate = true)
 var id: Long = 0
 @NonNull
 lateinit var title: String
 @NonNull
 lateinit var filePath: String
 @NonNull
 var width: Int = 0
 @NonNull
 var height: Int = 0
 @NonNull
 var fileSize: Long = 0
 @NonNull
 var dateAdded: Long = 0
 @NonNull
 var duration: Long = 0
 var coverImagePath: String? = null
 var albumVideoId: Int = 0
}

3)原理

 Q:这时候我们不禁要问,通过三个注解,如何实现对数据库的操作?
 @Database
 @Dao
 @Entity

  R:
  以@DataBase为例:
  
  @Target(ElementType.TYPE)
  @Retention(RetentionPolicy.CLASS)
  public @interface Database {
  }
  
  
  在build.gradle 我们引入了Room的编译包"androidx.room:room-compiler:$room_version"
  主要作用就是让这三个注解标注的接口在编译阶段生成对应的代码实现。
  比如AppDataBase会生成 AppDatabase_Impl 
  
  public final class AppDatabase_Impl extends AppDatabase {
    private volatile VideoInfoDao _videoInfoDao;
    private volatile ScreenShotInfoDao _screenShotInfoDao;
 }
 
 AppDatabase_Impl 主要做两个工作
 1)数据库初始化,支持LiveData
 2)创建DAO操作
  
  

4)优点&缺点

优点:
1、接入使用方便,代码清晰,比GreenDao 好用。
2、可以在build中加入配置文件,生成data创建的json信息,便于查看表的创建情况,nice
javaCompileOptions {
        annotationProcessorOptions {
            arguments = [
                    "room.schemaLocation":"$projectDir/schemas".toString(),
                    "room.incremental":"true",
                    "room.expandProjection":"true"]
        }
    }
3、query 支持LiveData

缺点:
1、Dao 不支持泛型
2、update & delete 不支持LiveData
3、suspend 和query 的LiveData 不兼容,如下面的代码,生成Dao_Impl的时候会报错,suspend & LiveData不兼容
suspend fun getAll(): LiveData<List<VideoInfo>>

2、ViewModel

1)ViewModel 是啥?

  当下android最流行的技术框架是MVP & MVVM ,其中ViewModel就是这个VM的,MVVM设计模式可以很好的把Model和View层隔离,他们以ViewModel最为桥梁来实现数据的组装,然后丢给View层去做显示。

2)ViewModel 如何用?

 ViewModelProvider(fragment).get(VideoInfoViewModel::java.class)
 如果需要传参则可以通过ViewModelProvider.Factory.create()

3)ViewModel 原理

alt

从图上可知,ViewModel 生命周期跟随owner,假如owner是Fragment/Activity,则一直跟随到死。


 Q1:如何实现和owner生命周期绑定?
 R1: 例如ViewModelProvider(fragment).get(VideoInfoViewModel::java.class)
 在fragment 创建一个mViewModelStore,在ViewProvider创建ViewModel,然后把ViewModel存到mViewModelStore;在fragment的onDestory的时候,清空mViewModelStore中的ViewModel;从而实现了追随fragment的生命,直到onDestroy的时候一起归西。
 需要注意的一点是,如果是横竖屏切换的情况下,ViewModel并不会销毁,onDestroy里面做了判断,
 @CallSuper
public void onDestroy() {
    mCalled = true;
    FragmentActivity activity = getActivity();
    boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
    if (mViewModelStore != null && !isChangingConfigurations) {
        mViewModelStore.clear();
    }
}

 Q2:从上面的ViewModel生命图可以看出,Activity 横屏,重建Activity之后,ViewModel 生命依旧坚挺,横竖屏切换如何做到ViewModel 不被销毁?
 R2:对应的每个androidx下的activity 也有一个mViewModelStore用于存放ViewModel,横竖屏切换的时候会调用ComponentActivity::onRetainNonConfigurationInstance 把mViewModelStore保存在NonConfigurationInstances中,在横竖屏切换完成的时候,FragmentActivity调用onCreate的时候,从getLastNonConfigurationInstance 中又取回来了。

4)ViewModel 优点&缺点

 优点:
 1)把View & Model 分离
 2)ViewModel的生命周期挂载在fragment/activity上,不用去维护ViewModel的生命周期,fragment/activity 销毁跟随销毁,有效防止model层的CallBack的内存泄露。
 3)同一个Activity下的fragments 可以直接数据通讯。
缺点:
1)Activity之间还没办法数据通讯,涉及跨进程。
2)使用起来有点奇怪,需要传参和不需要传参是两种写法....郁闷
能不能统一成ViewModelProvider(fragment).create(VideoInfoViewModel::java.class).with(...parmas)

LiveData

1)LiveData是啥?

 从名字来看是实时数据,实时数据?意思就是说,在model层更新数据之后,view层可以收到更新的数据。

2)LiveData如何用?

     liveData.observe(fragment, androidx.lifecycle.Observer { result ->
                videoListAdapter!!.data = result
                refresh()
            })

liveData直接设置observe监听。

3)LiveData原理

 LiveData主要有两个特性:
 1、和owner(fragment/activity)的生命周期绑定,如果owner destroy了liveData就自动失效了,不会再更新数据。
 2、把model层更新的数据,及时地传给view层。
 下面我们看下面代码的过程
 liveData.observe(fragment, androidx.lifecycle.Observer { result ->
            videoListAdapter!!.data = result
            refresh()
        })
 1)把observer包装成wrapper = LifecycleBoundObserver(owner, observer);
 2)lifecycle增加观察者,owner.getLifecycle().addObserver(wrapper);
 fragment的生命周期变化都会反馈到lifecycle,lifecycle又会反馈给LifecycleBoundObserver,调用onStateChanged传递数据;如果lifecycle::onDestroy了,liveData 就自己移除Observer,防止因为Observer内部调用了外部fragment导致的内存泄露。
 
 public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        activeStateChanged(shouldBeActive());
    }
    
 3)livedata.setValue(mValue) 如何把更新的数据通知给view层?
 通过livedata::dispatchingValue 把数据更新分发给observer::onChanged(mValue)

4)LiveData优点

 优点:
 1)绑定了fragment/activity的生命周期,不用我们自己去维护生命周期,有效防止了内存泄露发生,据说用了这个内存泄露减少了43%
 2)解耦,view & model 解耦。
 3)可以postvalue在子线程更新数据,然后再把数据切换给主线程。

总:Room+ViewModel+LiveData 搭配使用,挺好的MVVM模式架构,代码逻辑清晰。美中不足的是,Room只有query的返回值支持LiveData,delete&update不支持,kotlin协程和liveData不兼容;ViewModel 如果能把传参和非传参入口统一一下就好,变得更好用些。