Android 知识每天进步一点点想法

Kotlin 结合数据库小实战

2021-06-22  本文已影响0人  zcwfeng

最近在做一个离线下载的业务,考虑简单一点直接下载缓存,然后保存通过url 编码查询。但是后来发现有剧集的分组关系,那么不如直接用数据库。但是复杂的逻辑关系,懒得实现,那么直接拿Jetpack 的room来用,查询插入时候,有不像用复杂线程切换,那么,直接用Kotlin携程来做。
关于下载器,网上找一款就行,我临时用的FileDownloader 一个比较成熟老的下载器。

  1. 定义分层
    MVVM 模式,利用viewmodel的观察者模式的特点。
  1. 具体实现

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()
        }
    }
}

简单借助携程,非阻塞方式插入和查询

  1. 测试。
    设计到业务逻辑,这块我是在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();
            }
        });

好久不谢技术文章,不知道是否写的简单明了。
疫情改变世界太多了,快三年的疫情,改变了交流方式,赶走了一大批健身爱好者,破坏了很多好习惯,夺走了我们很多快乐。
坚持技术更新学习,坚持写文章,继续前进。

上一篇下一篇

猜你喜欢

热点阅读