Kotlin 结合数据库小实战
2021-06-22 本文已影响0人
zcwfeng
最近在做一个离线下载的业务,考虑简单一点直接下载缓存,然后保存通过url 编码查询。但是后来发现有剧集的分组关系,那么不如直接用数据库。但是复杂的逻辑关系,懒得实现,那么直接拿Jetpack 的room来用,查询插入时候,有不像用复杂线程切换,那么,直接用Kotlin携程来做。
关于下载器,网上找一款就行,我临时用的FileDownloader 一个比较成熟老的下载器。
- 定义分层
MVVM 模式,利用viewmodel的观察者模式的特点。
- data 数据仓库Repository 管理dao操作
- model 定义数据库表,我目前定义一张表,实际可能是两张表。
- Dao 和 DataBase
- 具体实现
model/DownloadMedia
@Entity(tableName = "download_media")
data class DownloadMedia (
@PrimaryKey(autoGenerate = false)
var mid:String,
@ColumnInfo(name = "coverurl")
val coverurl: String= "",
@ColumnInfo(name = "url")
var url: String = "",
@ColumnInfo(name = "path")
var path: String="",
@ColumnInfo(name = "size")
var size: Long = 0,
@ColumnInfo(name = "status")
var status:Int = 0
)
TaskDao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.amfun.db.model.DownloadMedia
@Dao
interface TaskDao {
@Insert
fun insert(vararg media: DownloadMedia?)
@Update
fun update(media: DownloadMedia?)
@Query("select * from download_media")
fun getAll(): LiveData<List<DownloadMedia>>
@Query("select * from download_media where url like :url")
fun findByUrl(url: String): DownloadMedia
@Query("select * from download_media where mid in (:mids)")
fun getAllId(mids: IntArray?): LiveData<List<DownloadMedia>>
}
TaskRoomDatabase
import android.content.Context
import androidx.room.Database
import androidx.room.Room.databaseBuilder
import androidx.room.RoomDatabase
import com.amfun.AmFunApp
import com.amfun.db.model.DownloadMedia
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.internal.synchronized
@Database(entities = [DownloadMedia::class], version = 1)
abstract class TaskRoomDatabase :RoomDatabase(){
abstract fun taskDao():TaskDao
companion object {
@Volatile private var INSTANCE: TaskRoomDatabase? = null
@OptIn(InternalCoroutinesApi::class)
fun getDatabase(
context: Context,
scope: CoroutineScope
): TaskRoomDatabase {
// 如果INSTANCE为null,返回此INSTANCE,否则,创建database
return INSTANCE ?: synchronized<TaskRoomDatabase>(this) {
val instance = databaseBuilder<TaskRoomDatabase>(
AmFunApp.getInstance().applicationContext,
TaskRoomDatabase::class.java,
"task_database"
)
// 如果没有迁移数据库,则擦除并重建而不是迁移。
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
instance
}
}
}
}
数据库这里,还是有点语法技巧,版本和语法兼容问题,由于ViewModelFactory的升级,所以谢了两个工具类处理
utils/工具类方便操作
object ViewModelFactory {
/**
* @Description: 创建 FragmentActivity 页面的 ViewModel
* @Date: 2020-05-09 14:41:04
*/
fun <T : ViewModel?> createViewModel(
activity: FragmentActivity,
cls: Class<T>?
): T {
return ViewModelProvider(activity).get(cls!!)
}
/**
* @Description: 创建 Fragment 页面的 ViewModel
* @Date: 2020-05-09 14:40:36
*/
fun <T : ViewModel?> createViewModel(
fragment: Fragment,
cls: Class<T>?
): T {
return ViewModelProvider(fragment).get(cls!!)
}
/**
* @Description:创建 FragmentActivity 页面的 AndroidViewModel
* @Date: 2020-05-09 14:36:39
*/
fun <T : ViewModel?> createViewModel(
activity: FragmentActivity,
application: Application,
cls: Class<T>?
): T {
return ViewModelProvider(
activity,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
).get(cls!!)
}
/**
* @Description:创建 Fragment 页面的 AndroidViewModel
* @Date: 2020-05-09 14:36:39
*/
fun <T : ViewModel?> createViewModel(
fragment: Fragment,
application: Application,
cls: Class<T>?
): T {
return ViewModelProvider(
fragment,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
).get(cls!!)
}
/**
* @Description: 创建带参数的 AndroidViewModel
* @Date: 2020-05-09 14:36:24
*/
fun <T : ViewModel?> createViewModel(
activity: FragmentActivity,
application: Application,
name: String,
cls: Class<T>?
): T {
return ViewModelProvider(
activity,
AndroidViewModelFactory.getInstance(application, name)
).get(cls!!)
}
}
全局AndroidViewModel
class AndroidViewModelFactory private constructor(
private val mApplication: Application,
private val mName: String
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
try {
modelClass.getConstructor(
Application::class.java, String::class.java
).newInstance(mApplication, mName)
} catch (e: Exception) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
} else super.create(modelClass)
}
companion object {
private var sInstance: AndroidViewModelFactory? = null
fun getInstance(
application: Application,
name: String
): AndroidViewModelFactory {
if (sInstance == null) {
sInstance = AndroidViewModelFactory(application, name)
}
return sInstance as AndroidViewModelFactory
}
}
}
data/TaskReposity
import androidx.lifecycle.LiveData
import com.amfun.db.TaskDao
import com.amfun.db.model.DownloadMedia
class TaskRepository(private val taskDao: TaskDao) {
// Room在单独的线程上执行所有查询
// 观察到的LiveData将在数据更改时通知观察者。
val allMedias: LiveData<List<DownloadMedia>> = taskDao.getAll()
fun insert(media: DownloadMedia) {
taskDao.insert(media)
}
fun update(media: DownloadMedia){
taskDao.update(media)
}
}
viewmodel/TaskViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.amfun.db.TaskRoomDatabase
import com.amfun.db.data.TaskRepository
import com.amfun.db.model.DownloadMedia
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TaskViewModel(application: Application,name:String) : AndroidViewModel(application) {
private val repository: TaskRepository
// 使用LiveData并缓存getAllTask返回的内容有几个好处:
// - 每当Room数据库有更新的时候通知观察者,而不是轮询更新
// 数据变化适时更新UI。
// - 存储库通过ViewModel与UI完全隔离。
val allMedias: LiveData<List<DownloadMedia>>
init {
val taskDao = TaskRoomDatabase.getDatabase(application, viewModelScope).taskDao()
repository = TaskRepository(taskDao)
allMedias = repository.allMedias
}
/**
* 启动新的协程以非阻塞方式插入数据
*/
fun insert(media: DownloadMedia) = viewModelScope.launch(Dispatchers.IO) {
try {
repository.insert(media)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun update(media: DownloadMedia) = viewModelScope.launch(Dispatchers.IO) {
try {
repository.update(media)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
简单借助携程,非阻塞方式插入和查询
- 测试。
设计到业务逻辑,这块我是在Adapter里面查询展示,在对应的视频下面按钮点击下载。查询下载进度,下载路径等。大致的逻辑是这样。
查询
// TODO query
mTaskViewModel.getAllMedias().observe((LifecycleOwner) context.get(), new Observer<List<DownloadMedia>>() {
@Override
public void onChanged(List<DownloadMedia> downloadMedia) {
KLog.d("zcw:::","查询数据库表数据,查询条数:" + downloadMedia.size());
}
});
下载器下载,插入和更新,当然插入等条件判断还没写,先看怎么用吧
DownloadUtils.getInstance().downloadSingleFile(url, path, false, tag, new FileDownloadSampleListener() {
@Override
protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {
super.pending(task, soFarBytes, totalBytes);
((ViewHolder) task.getTag()).updatePending(task);
mediaTask.setSize(totalBytes);
mTaskViewModel.insert(mediaTask);
KLog.d("zcw:::","pending test download into DB ");
}
@Override
protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {
super.progress(task, soFarBytes, totalBytes);
((ViewHolder) task.getTag()).updateProgress(soFarBytes, totalBytes,
task.getSpeed());
}
@Override
protected void error(BaseDownloadTask task, Throwable e) {
super.error(task, e);
((ViewHolder) task.getTag()).updateError(e, task.getSpeed());
}
@Override
protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {
super.connected(task, etag, isContinue, soFarBytes, totalBytes);
((ViewHolder) task.getTag()).updateConnected(etag, task.getFilename());
}
@Override
protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {
super.paused(task, soFarBytes, totalBytes);
((ViewHolder) task.getTag()).updatePaused(task.getSpeed());
}
@Override
protected void completed(BaseDownloadTask task) {
super.completed(task);
((ViewHolder) task.getTag()).updateCompleted(task);
mediaTask.setStatus(2);
mTaskViewModel.update(mediaTask);
KLog.d("zcw:::","completed test update info to DB ");
}
@Override
protected void warn(BaseDownloadTask task) {
super.warn(task);
((ViewHolder) task.getTag()).updateWarn();
}
});
好久不谢技术文章,不知道是否写的简单明了。
疫情改变世界太多了,快三年的疫情,改变了交流方式,赶走了一大批健身爱好者,破坏了很多好习惯,夺走了我们很多快乐。
坚持技术更新学习,坚持写文章,继续前进。