Jetpack-架构组件
Room
“Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
”
所以Room
就是一个数据库框架。问题来了,市面上那么多数据库组件,比如ormLite,greendao
等等,为什么google还要出一个room,有什么优势呢?
- 性能优势,一次数据库操作主要包括:构造sql语句—编译语句—传入参数—执行操作。
ORMLite
主要在获取参数属性值的时候,是通过反射获取的,所以速度较慢。GreenDao
在构造sql语句的时候是通过代码拼接,所以较慢。Room
是通过接口方法的注解生成sql语句,也就是编译成字节码的时候就生成了sql语句,所以运行起来较快。 - 支持jetpack其他组件(比如LiveData,Paging)以及RxJava,这就好比借助了当前所在的优势环境,就能给你带来一些得天独厚的优势。当然实际使用起来也确实要方便很多,比如
liveData
结合,就能在数据查询后进行自动UI更新。
既然Room这么优秀,那就用起来吧。Room的接入主要有三大点:DataBase、Entity、Dao
。分别对应数据库,表和数据访问。
1)首先导入库:
apply plugin: 'kotlin-kapt' dependencies { def room_version = "2.2.5" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor // optional - Kotlin Extensions and Coroutines support for Room implementation "androidx.room:room-ktx:$room_version" // optional - RxJava support for Room implementation "androidx.room:room-rxjava2:$room_version" }
2)建立数据库类,声明数据库表成员,数据库名称,数据库版本,单例等等
@Database(entities = arrayOf(User::class), version = 1) abstract class UserDb : RoomDatabase() { abstract fun userDao(): UserDao companion object { private var instance: UserDb? = null @Synchronized fun get(context: Context): UserDb { if (instance == null) { instance = Room.databaseBuilder(context.applicationContext, UserDb::class.java, "StudentDatabase").build() } return instance!! } } }
3)建表,可以设置主键,外键,索引,自增等等
@Entity data class User(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
4)Dao,数据操作
@Dao interface UserDao { @Query("SELECT * FROM User") fun getAllUser(): DataSource.Factory<Int, User> @Query("SELECT * FROM User") fun getAllUser2(): LiveData<List<User>> @Query("SELECT * from user") fun getAllUser3(): Flowable<List<User>> @Insert fun insert(users: List<User>) }
然后就可以进行数据库操作了,很简单吧。
官方文档
Demo代码地址
Paging
“分页库可帮助您一次加载和显示一小块数据。按需载入部分数据会减少网络带宽和系统资源的使用量。
”
所以Paging
就是一个分页库,主要用于Recycleview列表展示。下面我就结合Room说说Paging的用法。使用Paging主要注意两个类:PagedList和PagedListAdapter
。
1)PagedList
用于加载应用数据块,绑定数据列表,设置数据页等。结合上述Room
的Demo我继续写了一个UserModel
进行数据管理:
class UserModel(app: Application) : AndroidViewModel(app) { val dao = UserDb.get(app).userDao() var idNum = 1 companion object { private const val PAGE_SIZE = 10 } //初始化PagedList val users = LivePagedListBuilder( dao.getAllUser(), PagedList.Config.Builder() .setPageSize(PAGE_SIZE) .setEnablePlaceholders(true) .build() ).build() //插入用户 fun insert() = ioThread { dao.insert(newTenUser()) } //获取新的10个用户 fun newTenUser(): ArrayList<User> { var newUsers = ArrayList<User>() for (index in 1..10) { newUsers.add(User(0, "bob${++idNum}")) } return newUsers } }
2)PagedListAdapter
使用Recycleview必要要用到adatper,所以这里需要绑定一个继承自PagedListAdapter
的adapter:
class UserAdapter : PagedListAdapter<User, UserAdapter.UserViewHolder>(diffCallback) { override fun onBindViewHolder(holder: UserViewHolder, position: Int) { holder.bindTo(getItem(position)) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder = UserViewHolder(parent) companion object { private val diffCallback = object : DiffUtil.ItemCallback<User>() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: User, newItem: User): Boolean = oldItem == newItem } } class UserViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder( LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)) { private val tv1 = itemView.findViewById<TextView>(R.id.name) var user: User? = null fun bindTo(user: User?) { this.user = user tv1.text = user?.name } } }
这里还用到了DiffUtil.ItemCallback
类,用于比较数据,进行数据更新用。
ok,数据源,adapter都设置好了,接下来就是监听数据,刷新数据就可以了
// 监听users数据,数据改变调用submitList方法 viewModel.users.observe(this, Observer(adapter::submitList))
对,就是这么一句,监听PagedList
,并且在它改变的时候调用PagedListAdapter的submitList
方法。这分层够爽吧,其实这也就是paging或者说jetpack给我们项目带来的优势,层层解耦,adapter都不用维护list数据源了。
官方文档
Demo代码地址
ViewModel
“ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
”
终于说到ViewModel
了,其实之前的demo都用了好多遍了,ViewModel
主要是从界面控制器逻辑中分离出视图数据,为什么要这么做呢?主要为了解决两大问题:
- 以前Activity中如果被系统销毁或者需要重新创建的时候,页面临时性数据都会丢失,需要通过
onSaveInstanceState()
方法保存,onCreate方法中读取。而且数据量一大就更加不方便了。 - 在Activity中,难免有些异步调用,所以就会容易导致界面销毁时候,这些调用还存在。那就会发生内存泄漏或者直接崩溃。
所以ViewModel
诞生了,还是解耦,我把数据单独拿出来管理,还加上生命周期,那不就可以解决这些问题了吗。而且当所有者 Activity 完全销毁之后,ViewModel
会调用其onCleared()
方法,以便清理资源。
接下来举个🌰,看看ViewModel具体是怎么使用的:
def lifecycle_version = "2.2.0" // ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" class SharedViewModel : ViewModel() { var userData = MutableLiveData<User>() fun select(item: User) { userData.value = item } override fun onCleared() { super.onCleared() } } class MyFragment1 : Fragment() { private lateinit var btn: Button override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val model=activity?.let { ViewModelProvider(it).get(SharedViewModel::class.java) } btn.setOnClickListener{ model?.select(User(0,"bob")) } } } class MyFragment2 : Fragment() { private lateinit var btn: Button override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val model=activity?.let { ViewModelProvider(it).get(SharedViewModel::class.java) } model?.userData?.observe(viewLifecycleOwner, Observer<User> { item -> // Update the UI }) } }
Fragment中,获取到viewmodel
的实例,然后进行数据监听等操作。等等,你能发现什么不?对了,数据通信。不同的 Fragment 可以使用其父Activity共享ViewModel
来进行数据的通信,厉害吧。还有很多其他的用法,去项目中慢慢发现吧!
官方文档
Demo代码地址
WorkManager
“使用 WorkManager API 可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务。
”
听听这个介绍就很神奇了,应用退出和设备重启都能自动运行?通过广播?那数据又是怎么保存的呢?听说还可以执行周期性异步任务,顺序链式调用哦!接下来一一解密
- 关于应用退出和设备重启
如果APP正在运行,WorkManager
会在APP进程中起一个新线程来运行任务;如果APP没有运行,WorkManager
会选择一个合适的方式来调度后台任务--根据系统级别和APP状态,WorkManager可能会使用JobScheduler,FireBase JobDispatcher
或者AlarmManager
。
- 关于数据保存
WorkManager
创建的任务数据都会保存到数据库,用的是Room
框架。然后重启等时间段都会去数据库寻找需要安排执行的任务,然后判断约束条件
,满足即可执行。
一般这个API应用到什么场景呢?想想,可靠运行,还可以周期异步。对了,发送日志。可以通过WorkManager
设定周期任务,每天执行一次发送日志的任务。而且能够保证你的任务可靠运行,一定可以上传到,当然也是支持监听任务结果等。🌰:
1)导入库
dependencies { def work_version = "2.3.4" // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:$work_version" // optional - RxJava2 support implementation "androidx.work:work-rxjava2:$work_version" // optional - GCMNetworkManager support implementation "androidx.work:work-gcm:$work_version" }
2) 新建任务类,继承Worker
,重写doWork
方法,返回任务结果。
class UploadLogcatWork(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { if (isUploadLogcatSuc()) { return Result.success() } else if (isNeedRetry()){ return Result.retry() } return Result.failure() } fun isUploadLogcatSuc(): Boolean { var isSuc: Boolean = false return isSuc } fun isNeedRetry(): Boolean { var isSuc: Boolean = false return isSuc } }
3)最后就是设定约束(是否需要网络,是否支持低电量,是否支持充电执行,延迟等等),执行任务(单次任务或者循环周期任务)
//设定约束 val constraints = Constraints.Builder() //网络链接的时候使用 .setRequiredNetworkType(NetworkType.CONNECTED) //是否在设备空闲的时候执行 .setRequiresDeviceIdle(false) //是否在低电量的时候执行 .setRequiresBatteryNotLow(true) //是否在内存不足的时候执行 .setRequiresStorageNotLow(true) //是否时充电的时候执行 .setRequiresCharging(true) //延迟执行 .setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS) .build() //设定循环任务 val uploadRequest = PeriodicWorkRequestBuilder<UploadLogcatWork>(1, TimeUnit.HOURS) .setConstraints(constraints) .addTag("uploadTag") .build() //执行 WorkManager.getInstance(applicationContext).enqueue(uploadRequest) //监听执行结果 WorkManager.getInstance(this) // .getWorkInfosByTagLiveData("uploadTag") //通过tag拿到work .getWorkInfoByIdLiveData(uploadRequest.id) //通过id拿到work .observe(this, Observer { it?.apply { when (this.state) { WorkInfo.State.BLOCKED -> println("BLOCKED") WorkInfo.State.CANCELLED -> println("CANCELLED") WorkInfo.State.RUNNING -> println("RUNNING") WorkInfo.State.ENQUEUED -> println("ENQUEUED") WorkInfo.State.FAILED -> println("FAILED") WorkInfo.State.SUCCEEDED -> println("SUCCEEDED") else -> println("else status ${this.state}") } } })
4)另外还支持任务取消,任务链式顺序调用等
//取消 fun cancelWork(){ WorkManager.getInstance(applicationContext).cancelAllWorkByTag("uploadTag") } fun startLineWork(){ //图片滤镜1 val filter1 = OneTimeWorkRequestBuilder<UploadLogcatWork>() .build() //图片滤镜2 val filter2 = OneTimeWorkRequestBuilder<UploadLogcatWork>() .build() //图片压缩 val compress = OneTimeWorkRequestBuilder<UploadLogcatWork>() .build() //图片上传 val upload = OneTimeWorkRequestBuilder<UploadLogcatWork>() .build() WorkManager.getInstance(applicationContext) .beginWith(listOf(filter1, filter2)) .then(compress) .then(upload) .enqueue() }
官方文档
Demo代码地址
总结
Jetpack-架构组件讲完了,大家吃🌰应该吃饱了吧哈哈。希望这篇文章能让不怎么熟悉Jetpack
的同学熟悉熟悉。
当然,这还远远不够,在我看来,本文更像是一个科普文
,只是告诉了大家Jetpack-架构组件有哪些成员,有什么用处。实际项目中,我们还需要建立MVVM
的思想,深刻了解每个组件的设计意义,灵活运用组件。(附件有个项目是官方的Jetpack实践项目
,可以看看)
最后希望大家都能通过jetpack
构建高质量,简易并优质的项目架构,从而解放生产力,成为效率达人
。
附件
https://github.com/android/sunflowerhttps://github.com/JiMuzz/jimu-Jetpack-Demo
谢谢你的阅读,如果你觉得写的还行,就点个赞支持下吧!感谢!
你的一个👍,就是我分享的动力❤️。