feat:完成礼物资源预缓存功能,修改礼物播放下载方案

This commit is contained in:
max
2024-05-16 16:58:11 +08:00
parent 58f1002b4e
commit 89b381bfbc
20 changed files with 3067 additions and 252 deletions

View File

@@ -0,0 +1,26 @@
package com.chwl.library.download
class DownloadException : Exception {
private var task: DownloadTask? = null
private var request: DownloadRequest? = null
constructor(message: String) : super(message)
constructor(task: DownloadTask, throwable: Throwable) : super(
throwable
) {
this.task = task
this.request = task.getRequest()
}
constructor(request: DownloadRequest, throwable: Throwable) : super(
throwable
) {
this.request = request
}
fun task() = task
}

View File

@@ -0,0 +1,22 @@
package com.chwl.library.download
interface DownloadListener {
/**
* 下载完成
*/
fun onDownloadCompleted(task: DownloadTask) {}
/**
* 下载进度
* @param soFarBytes 已下载
* @param totalBytes 总
*/
fun onDownloadProgress(task: DownloadTask, soFarBytes: Int, totalBytes: Int) {}
/**
* 下载异常
*/
fun onDownloadError(exception: DownloadException){}
}

View File

@@ -0,0 +1,231 @@
package com.chwl.library.download
import com.example.lib_utils.log.ILog
import java.util.concurrent.ConcurrentHashMap
object DownloadManager : ILog {
/**
* 任务列表 <URL,任务>
*/
private val tasks: ConcurrentHashMap<String, DownloadTask> by lazy {
ConcurrentHashMap()
}
/**
* 标签列表 <TAG,URL>
*/
private val tags: ConcurrentHashMap<String, HashSet<String>> by lazy {
ConcurrentHashMap()
}
/**
* 停止所有任务
*/
fun stopAll() {
logD("stopAll()")
tasks.keys.map {
it
}.forEach {
stop(it)
}
tasks.clear()
tags.clear()
}
/**
* 停止某一个URL下载任务有多组下载该URL都会被停止
*/
fun stop(url: String) {
logD("stop() url:${url}")
getTask(url)?.stop()
}
/**
* 停止某一组内的某个URL下载任务其他组正在下载该URL的任务不受影响
*/
fun stop(url: String, tag: String) {
logD("stop() url:$url ,tag:$tag")
getTask(url)?.removeListener(tag)
}
/**
* 停止某一标签的所有任务
*/
fun stopTag(tag: String) {
logD("stopTag()")
if (tags.containsKey(tag)) {
tags.remove(tag)?.let {
it.forEach { url ->
getTask(url)?.removeListener(tag)
}
}
}
}
/**
* 停止某个下载请求
*/
fun stop(request: DownloadRequest) {
stop(request.getUrl(), request.getTag())
}
/**
* 下载文件 (简单下载,也可通过自己构造DownloadRequest下载)
* @param url 下载地址
* @param tag 标签
* @param listener 监听器
*/
fun download(url: String, path: String, listener: DownloadListener, tag: String? = null) {
val request = DownloadRequest.build(url, path, tag, null)
download(request, listener)
}
/**
* 下载文件
* @param request 下载请求体
* @param listener 监听器
*/
fun download(request: DownloadRequest, listener: DownloadListener) {
download(request, request.getTag(), listener)
}
/**
* 下载文件
* @param request 下载请求体
* @param listenerTag 监听器标签(有重复任务时,内部会复用任务追加监听器,监听器以标签区分。)
* @param listener 监听器
*/
fun download(request: DownloadRequest, listenerTag: String, listener: DownloadListener) {
logD("download() url:${request.getUrl()} ,path:${request.getPath()} ,listenerTag:$listenerTag")
// 记录标签与地址关系
addTag(request.getTag(), request.getUrl())
synchronized(this) {
var task = getTask(request)
// 判断是否有相同目标的任务
if (task != null) {
logD("download() 有重复任务->关联复用")
task.addListener(listenerTag, listener)
task.start()
} else {
logD("download() 创建新任务")
task = request.createTask().apply {
addListener(listenerTag, listener)
}
addTask(task)
task.start()
}
}
}
/**
* 添加监听
* @param url 下载地址
* @param listener 监听器
*/
fun addListener(url: String, listener: DownloadListener) {
getTask(url)?.addListener(listener = listener)
}
/**
* 移除监听
* @param url 下载地址
* @param listener 监听器
*/
fun removeListener(url: String, listener: DownloadListener) {
getTask(url)?.removeListener(listener = listener)
}
/**
* 获取任务(具有相同目标的任务)
* @param request
*/
fun getTask(request: DownloadRequest): DownloadTask? {
tasks.values.forEach {
if (it.getRequest().equalsTarget(request)) {
return it
}
}
return null
}
/**
* 添加任务
*/
private fun addTask(task: DownloadTask) {
logD("addTask()")
tasks[task.getRequest().getUrl()] = task
}
/**
* 移除任务
*/
fun removeTask(task: DownloadTask) {
removeTask(task.getRequest().getUrl())
}
/**
* 移除任务
*/
@Synchronized
private fun removeTask(url: String) {
logD("removeTask() url:$url")
tasks.remove(url)?.apply {
// 移除该URL与标签关系
tags.iterator().let {
while (it.hasNext()) {
it.next().let { entry ->
entry.value.remove(url)
if (entry.value.isEmpty()) {
it.remove()
}
}
}
}
}
}
/**
* 获取任务-根据下载地址
* @param url 下载地址
*/
fun getTask(url: String): DownloadTask? {
if (hasTask(url)) {
return tasks[url]
}
return null
}
/**
* 是否存在相同任务
* @param url 下载地址
*/
private fun hasTask(url: String): Boolean {
return tasks.containsKey(url)
}
/**
* 是否存在相同任务
*/
private fun hasTask(request: DownloadRequest): Boolean {
return getTask(request) != null
}
/**
* 添加标签记录(建立关系)
*/
private fun addTag(tag: String, url: String) {
logD("addTag() tag:$tag ,url:$url")
if (tag.isEmpty() || url.isEmpty()) {
return
}
synchronized(this) {
tags[tag]?.add(url) ?: let {
tags[tag] = HashSet<String>().apply {
add(url)
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
package com.chwl.library.download
interface DownloadRequest {
companion object {
/**
* 构建请求
* @param url 下载地址
* @param path 存储路径(完整)
* @param tag 标记
* @param header 请求头
* @param timeout 下载超时时间
*/
fun build(
url: String,
path: String,
tag: String? = null,
header: Map<String, String>? = null,
timeout: Long? = null,
): DownloadRequest {
return FileDownloadRequest(url, path, tag, header, timeout)
}
}
/**
* 下载地址
*/
fun getUrl(): String
/**
* 本地文件路径
*/
fun getPath(): String
/**
* 标签
*/
fun getTag(): String
/**
* 请求头
*/
fun getHeader(): Map<String, String>?
/**
* 获取下载任务
*/
fun createTask(): DownloadTask
/**
* 获取下载超时时间(毫秒)
*/
fun getTimeout(): Long?
/**
* 是否是相同的目标
*/
fun equalsTarget(request: DownloadRequest): Boolean
}

View File

@@ -0,0 +1,36 @@
package com.chwl.library.download
object DownloadState {
/**
* 空闲
*/
const val IDLE: Int = -1
/**
* 错误
*/
const val ERROR: Int = 0
/**
* 成功
*/
const val SUCCESS: Int = 1
/**
* 已启动
*/
const val STARTED: Int = 2
/**
* 进行中
*/
const val PROGRESS: Int = 3
/**
* 暂停
*/
const val PAUSED: Int = 4
}

View File

@@ -0,0 +1,64 @@
package com.chwl.library.download
import com.example.lib_utils.ICleared
interface DownloadTask : ICleared {
/**
* 请求体
*/
fun getRequest(): DownloadRequest
/**
* 获取当前状态
*/
fun getState(): Int
/**
* 任务ID(唯一标识)
*/
fun getId(): String
/**
* 文件路径(只有成功时才能拿到有效的文件路径)
*/
fun getFilePath(): String?
/**
* 开始/恢复
*/
fun start()
/**
* 停止/暂停
*/
fun stop()
/**
* 是否暂停状态
*/
fun isPause(): Boolean {
return getState() == DownloadState.PAUSED
}
/**
* 添加回调监听
* @param tag 标签 (重复标签时,监听器会发生覆盖操作)
* @param listener 监听器
* @return 是否添加成功
*/
fun addListener(tag: String? = null, listener: DownloadListener)
/**
* 移除回调监听
* @param tag 标签
*/
fun removeListener(tag: String)
/**
* 移除回调监听
* @param listener 监听器
*/
fun removeListener(listener: DownloadListener)
}

View File

@@ -0,0 +1,4 @@
package com.chwl.library.download
open class FileDownloadListener : DownloadListener

View File

@@ -0,0 +1,47 @@
package com.chwl.library.download
open class FileDownloadRequest(
// 下载地址
private val url: String,
// 保存路径(完整)
private val path: String,
// 别名
private val tag: String? = null,
// 请求头
private val header: Map<String, String>? = null,
// 下载超时时间
private val timeout: Long? = null,
) : DownloadRequest {
override fun getUrl(): String {
return url
}
override fun getPath(): String {
return path
}
override fun getTag(): String {
return tag ?: "DEFAULT_TAG"
}
override fun getHeader(): Map<String, String>? {
return header
}
override fun createTask(): DownloadTask {
return FileDownloadTask(this)
}
override fun getTimeout(): Long? {
return timeout
}
override fun equalsTarget(request: DownloadRequest): Boolean {
// 暂时只判断目标地址是否一致
return request.getUrl() == getUrl()
}
}

View File

@@ -0,0 +1,276 @@
package com.chwl.library.download
import android.os.Handler
import android.os.Looper
import com.example.lib_utils.FileUtils2
import com.example.lib_utils.log.ILog
import com.liulishuo.filedownloader.BaseDownloadTask
import com.liulishuo.filedownloader.FileDownloadListener
import com.liulishuo.filedownloader.FileDownloader
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeoutException
open class FileDownloadTask(
private val request: DownloadRequest,
) : DownloadTask, ILog {
// 真正执行下载工作的任务第三方组件SDK
private var task: BaseDownloadTask? = null
// 工作的任务监听器
private var taskListener: ListenerBridge? = null
// 监听器列表 <标签,监听器>
private val listeners: ConcurrentHashMap<String, DownloadListener> by lazy {
ConcurrentHashMap()
}
// 任务ID
private val taskId: String by lazy {
getRequest().getUrl() + getRequest().getPath()
}
// 状态
@Volatile
private var state: Int = DownloadState.IDLE
private var handler: Handler? = null
// 是否释放了(临时记录,只是为了协助排除问题)
private var isCleared: Boolean? = null
override fun getRequest(): DownloadRequest {
return request
}
override fun getState(): Int {
return state
}
override fun getId(): String {
return taskId
}
override fun getFilePath(): String {
return getWorkTask().targetFilePath
}
override fun start() {
logI(
"start()" +
"\nurl:${getRequest().getUrl()}" +
"\npath:${getRequest().getPath()}" +
"\ntimeout:${getRequest().getTimeout()}" +
"\nstate:${getState()}" +
"\nworkTask-status:${getWorkTask().status}", filePrinter = true
)
if (FileUtils2.isFileExists(getRequest().getPath())) {
logD("该目标文件已存在本地")
downloadSuccess()
return
}
if (isCleared == true) {
logE("当前任务已释放 url:${request.getUrl()}", filePrinter = true)
return
}
if (getState() == DownloadState.IDLE || getState() == DownloadState.ERROR) {
state = DownloadState.STARTED
startTimeout()
// -------start
// TODO 线上部分异常调用start时内部抛正在进行的异常(This task is dirty to restart, If you want to reuse this task, please invoke #reuse method manually and retry to restart again)
// TODO 临时预先判断记录下
if (getWorkTask().isUsing) {
logE("下载正在进行无需重复启动url:${request.getUrl()}", filePrinter = true)
return
}
// -------end
try {
getWorkTask().start()
} catch (e: Exception) {
downloadError(e)
}
} else {
logI("start() 已经启动")
}
}
override fun stop() {
logI("stop()", filePrinter = true)
if (getState() != DownloadState.PAUSED && getState() != DownloadState.IDLE) {
getWorkTask().pause()
}
onCleared()
}
override fun addListener(tag: String?, listener: DownloadListener) {
listeners[tag ?: getRequest().getTag()] = listener
}
override fun removeListener(tag: String) {
listeners.remove(tag)
if (listeners.isEmpty()) {
stop()
}
}
override fun removeListener(listener: DownloadListener) {
listeners.forEach {
if (it == listener) {
listeners.remove(it.key)
if (listeners.isEmpty()) {
stop()
}
return
}
}
}
/**
* (真正)工作的任务
*/
protected open fun getWorkTask(): BaseDownloadTask {
if (task == null) {
task = createTask()
}
return task!!
}
/**
* 创建任务
*/
protected open fun createTask(): BaseDownloadTask {
taskListener = ListenerBridge()
return FileDownloader.getImpl().create(getRequest().getUrl()).apply {
setPath(getRequest().getPath(), false)
this.listener = taskListener
}
}
/**
* 释放工作任务相关资源
*/
private fun releaseWorkTask() {
task?.listener = null
taskListener = null
task = null
}
/**
* 释放
*/
override fun onCleared() {
super.onCleared()
logD("onCleared() url:${getRequest().getUrl()}", filePrinter = true)
this.isCleared = true
// 从任务管理中移除自己
DownloadManager.removeTask(this)
handler?.removeCallbacksAndMessages(null)
handler = null
// 释放工作任务
releaseWorkTask()
// 移除所有监听
listeners.clear()
}
/**
* 下载成功处理
*/
private fun downloadSuccess() {
logI("downloadSuccess() url:${getRequest().getUrl()}", filePrinter = true)
state = DownloadState.SUCCESS
listeners.values.forEach {
it.onDownloadCompleted(this)
}
onCleared()
}
/**
* 下载失败
*/
private fun downloadError(error: Throwable) {
logE("downloadError() url:${getRequest().getUrl()}", error, filePrinter = true)
state = DownloadState.ERROR
listeners.values.forEach {
it.onDownloadError(DownloadException(this@FileDownloadTask, error))
}
onCleared()
}
/**
* 开始超时检测
*/
private fun startTimeout() {
val timeout = request.getTimeout()
if (timeout == null || timeout < 500) {
// 时间太小就过滤掉
return
}
logD("startTimeout() timeout:$timeout")
if (handler == null) {
handler = Handler(Looper.getMainLooper())
} else {
handler?.removeCallbacksAndMessages(null)
}
handler?.postDelayed({
logD("startTimeout() postDelayed state:$state")
if (state != DownloadState.IDLE && state != DownloadState.SUCCESS && state != DownloadState.ERROR) {
logI(
"startTimeout() postDelayed state:$state url:${getRequest().getUrl()}",
filePrinter = true
)
if (getState() != DownloadState.PAUSED) {
getWorkTask().pause()
}
downloadError(TimeoutException())
}
}, timeout)
}
/**
* 监听器转发处理
*/
private inner class ListenerBridge : FileDownloadListener() {
override fun pending(task: BaseDownloadTask, soFarBytes: Int, totalBytes: Int) {
state = DownloadState.STARTED
logI(
"pending() url:${getRequest().getUrl()} soFarBytes:$soFarBytes,totalBytes:$totalBytes",
filePrinter = true
)
}
override fun progress(task: BaseDownloadTask, soFarBytes: Int, totalBytes: Int) {
state = DownloadState.PROGRESS
// logD("progress() soFarBytes:$soFarBytes,totalBytes:$totalBytes")
listeners.values.forEach {
it.onDownloadProgress(this@FileDownloadTask, soFarBytes, totalBytes)
}
}
/**
* 完成时
*/
override fun completed(task: BaseDownloadTask) {
downloadSuccess()
}
override fun paused(task: BaseDownloadTask, soFarBytes: Int, totalBytes: Int) {
logI("paused() url:${getRequest().getUrl()}", filePrinter = true)
state = DownloadState.PAUSED
}
override fun error(task: BaseDownloadTask, e: Throwable) {
downloadError(e)
}
/**
* 在下载队列中(正在等待/正在下载)已经存在相同下载连接与相同存储路径的任务
*/
override fun warn(task: BaseDownloadTask) {
logI("warn() 已经存在相同下载连接与相同存储路径的任务 url:${getRequest().getUrl()}", filePrinter = true)
}
}
}