Android 下载任务队列实现-Kotlin

3,155 阅读3分钟

下载队列在Android中可以说是非常常见了,常见的做法是将下载任务放到服务中,创建线程任务队列,对其进行处理,这样子问题也有很多,比如常见的线程安全之类的。并且考虑到线程创建和关闭对资源的消耗,我们还要维护一个线程池。同时实现动态像队列中添加任务,采用生产者-消费者模式。非常复杂。

今天采用kotlin来实现一个可以随时添加下载任务的下载队列。

知识点

  • 协程

  • 通道

  • Android service

如果对于协程和通道不明白的可以参考kotlin官方文档

思路

首先创建一个Android Service,onBind返回null,因为不需要绑定任何activity。重写onCreate函数,onStartCommand函数。

class DownloadService : Service(), CoroutineScope by MainScope() {
	override fun onBind(intent: Intent): IBinder? {
        return null
    }
    override fun onCreate() {
        super.onCreate()
    }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    }    
}

简单说明onCreate和onStartCommand函数:

  • onCreate : 在服务首次开启时调用一次,之后不再调用
  • onStartCommand :每次调用startService()都会调用一次

这样就非常清晰了,在onCreate方法中实现下载队列,在onStartCommand 方法中添加下载队列。

/**
 * 声明下载通道
 */
private val downLoadChannel = Channel<String>()

/**
 * 添加下载任务
 */
private suspend fun addDownloadTask(down: String) {
    count++
    Log.d(TAG, "增加:$down,当前任务数:$count")
    downLoadChannel.send(down)
}

我们只要在onStartCommand中调用添加下载任务就行了

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    launch {
        c++
        Log.d(TAG, "准备增加")
        addDownloadTask("下载任务$c")
    }
    return super.onStartCommand(intent, flags, startId)
}

在onCreate中,我们需要创建一个在IO线程中的协程来处理下载任务。每当任务处理完毕之后调用销毁程序关闭任务,销毁程序需要先等待11s检查是否没有任务添加了,如果是在等待5s中确认真的没有任务添加之后,关闭通道和服务进程。

@ExperimentalCoroutinesApi
suspend fun processingDownloadTasks() {
    while (!downLoadChannel.isEmpty) {
        val down = downLoadChannel.receive()
        Log.d(TAG, "正在下载:$down")
        // 模拟下载
        delay(1000)
        count--
    }
    waitingToStop()
}

@ExperimentalCoroutinesApi
suspend fun waitingToStop() {
    Log.d(TAG, "等待...${downLoadChannel.isEmpty}")
    delay(11000)
    Log.d(TAG, "每11s检查是否有下载任务,当前任务:${downLoadChannel.isEmpty}")
    if (downLoadChannel.isEmpty) {
        Log.d(TAG, "没有下载任务,最后5s到底有没有,当前任务:${downLoadChannel.isEmpty}")
        delay(5000)
        Log.d(TAG, "延迟,当前任务:${downLoadChannel.isEmpty}")
        if (downLoadChannel.isEmpty) {
            Log.d(TAG, "真的没有下载任务,我要关闭了")
            downLoadChannel.cancel()
            // 停止
            stopSelf()
            return
        }
    }
    processingDownloadTasks()
}

override fun onBind(intent: Intent): IBinder? {
    return null
}

@ExperimentalCoroutinesApi
override fun onCreate() {
    super.onCreate()
    Log.d(TAG, "启动服务")

    launch(Dispatchers.IO) {
        processingDownloadTasks()
    }
}

在activity中随时都可以调用startService来开启下载进程。

startService<DownloadService>()

运行效果

启动服务
等待...true
准备增加
增加:下载任务1,当前任务数:1
准备增加
增加:下载任务2,当前任务数:2
准备增加
增加:下载任务3,当前任务数:3
准备增加
增加:下载任务4,当前任务数:4
每11s检查是否有下载任务,当前任务:false
正在下载:下载任务1
准备增加
增加:下载任务5,当前任务数:5
正在下载:下载任务2
正在下载:下载任务3
正在下载:下载任务4
正在下载:下载任务5
等待...true
准备增加
增加:下载任务6,当前任务数:1
每11s检查是否有下载任务,当前任务:false
正在下载:下载任务6
等待...true
每11s检查是否有下载任务,当前任务:true
没有下载任务,最后5s到底有没有,当前任务:true
延迟,当前任务:true
真的没有下载任务,我要关闭了
关闭服务