增加任务列表和下载功能

This commit is contained in:
squallzhjch
2023-04-23 16:35:45 +08:00
parent ccfd30228e
commit a1170db7a9
35 changed files with 1083 additions and 137 deletions

View File

@@ -0,0 +1,7 @@
package com.navinfo.omqs.http
class DefaultTaskResponse<T> {
var success: Boolean = false
var msg: String = ""
var obj: T? = null
}

View File

@@ -1,6 +1,7 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.bean.TaskBean
/**
@@ -11,4 +12,8 @@ interface NetworkService {
* 获取离线地图城市列表
*/
suspend fun getOfflineMapCityList():NetResult<List<OfflineMapCityBean>>
/**
* 获取任务列表
*/
suspend fun getTaskList(evaluatorNo:String): NetResult<DefaultTaskResponse<List<TaskBean>>>
}

View File

@@ -1,6 +1,7 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.bean.TaskBean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
@@ -32,4 +33,23 @@ class NetworkServiceImpl @Inject constructor(
NetResult.Error(e)
}
}
override suspend fun getTaskList(evaluatorNo: String): NetResult<DefaultTaskResponse<List<TaskBean>>> =
//在IO线程中运行
withContext(Dispatchers.IO) {
return@withContext try {
val result = netApi.retrofitGetTaskList(evaluatorNo)
if (result.isSuccessful) {
if (result.code() == 200) {
NetResult.Success(result.body())
} else {
NetResult.Failure(result.code(), result.message())
}
} else {
NetResult.Failure(result.code(), result.message())
}
} catch (e: Exception) {
NetResult.Error(e)
}
}
}

View File

@@ -1,10 +1,12 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.bean.TaskBean
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
import retrofit2.http.Streaming
import retrofit2.http.Url
@@ -46,8 +48,15 @@ interface RetrofitNetworkServiceAPI {
*/
@Streaming
@GET
suspend fun retrofitDownLoadFile(@Header("RANGE") start: String? = "0", @Url url: String):Response<ResponseBody>
suspend fun retrofitDownLoadFile(
@Header("RANGE") start: String? = "0",
@Url url: String
): Response<ResponseBody>
@GET("/devcp/task?evaluatType=2")
suspend fun retrofitGetTaskList(
@Query("evaluatorNo") evaluatorNo: String,
): Response<DefaultTaskResponse<List<TaskBean>>>
/**
* @FormUrlEncoded 请求格式注解请求实体是一个From表单每个键值对需要使用@Field注解

View File

@@ -112,6 +112,4 @@ class OfflineMapDownloadManager(
scopeMap[id]!!.removeObserver()
}
}
}

View File

@@ -6,6 +6,8 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.tools.FileManager
import com.navinfo.omqs.tools.FileManager.Companion.FileDownloadStatus
import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
@@ -23,7 +25,7 @@ class OfflineMapDownloadScope(
private val downloadManager: OfflineMapDownloadManager,
val cityBean: OfflineMapCityBean,
) :
CoroutineScope by CoroutineScope(Dispatchers.IO) {
CoroutineScope by CoroutineScope(Dispatchers.IO + CoroutineName("OfflineMapDownLoad")) {
/**
*下载任务,用来取消的
*/
@@ -46,7 +48,7 @@ class OfflineMapDownloadScope(
//改进的代码
fun start() {
change(OfflineMapCityBean.WAITING)
change(FileDownloadStatus.WAITING)
downloadManager.launchScope(this@OfflineMapDownloadScope)
}
@@ -56,7 +58,7 @@ class OfflineMapDownloadScope(
*/
fun pause() {
downloadJob?.cancel("pause")
change(OfflineMapCityBean.PAUSE)
change(FileDownloadStatus.PAUSE)
}
/**
@@ -65,11 +67,8 @@ class OfflineMapDownloadScope(
*/
fun launch() {
downloadJob = launch() {
Log.e("jingo", "启动下载1")
download()
Log.e("jingo", "启动下载2")
downloadManager.launchNext(cityBean.id)
Log.e("jingo", "启动下载3")
}
}
@@ -78,7 +77,7 @@ class OfflineMapDownloadScope(
* 是否是等待任务
*/
fun isWaiting(): Boolean {
return cityBean.status == OfflineMapCityBean.WAITING
return cityBean.status == FileDownloadStatus.WAITING
}
/**
@@ -86,7 +85,7 @@ class OfflineMapDownloadScope(
* @param status [OfflineMapCityBean.Status]
*/
private fun change(status: Int) {
if (cityBean.status != status || status == OfflineMapCityBean.LOADING) {
if (cityBean.status != status || status == FileDownloadStatus.LOADING) {
cityBean.status = status
downloadData.postValue(cityBean)
launch(Dispatchers.IO) {
@@ -128,7 +127,7 @@ class OfflineMapDownloadScope(
url = cityBean.url
)
val responseBody = response.body()
change(OfflineMapCityBean.LOADING)
change(FileDownloadStatus.LOADING)
responseBody ?: throw IOException("jingo ResponseBody is null")
//写入文件
randomAccessFile = RandomAccessFile(fileTemp, "rwd")
@@ -144,7 +143,7 @@ class OfflineMapDownloadScope(
if (readLength != -1) {
randomAccessFile.write(buffer, 0, readLength)
cityBean.currentSize += readLength
change(OfflineMapCityBean.LOADING)
change(FileDownloadStatus.LOADING)
} else {
break
}
@@ -155,15 +154,15 @@ class OfflineMapDownloadScope(
val res =
fileTemp.renameTo(File("${Constant.OFFLINE_MAP_PATH}${cityBean.fileName}"))
Log.e("jingo", "文件下载完成 修改文件 $res")
change(OfflineMapCityBean.DONE)
change(FileDownloadStatus.DONE)
withContext(Dispatchers.Main) {
downloadManager.mapController.layerManagerHandler.loadBaseMap()
}
} else {
change(OfflineMapCityBean.PAUSE)
change(FileDownloadStatus.PAUSE)
}
} catch (e: Throwable) {
change(OfflineMapCityBean.ERROR)
change(FileDownloadStatus.ERROR)
} finally {
inputStream?.close()
randomAccessFile?.close()

View File

@@ -0,0 +1,113 @@
package com.navinfo.omqs.http.taskdownload
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import com.navinfo.omqs.bean.TaskBean
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import java.util.concurrent.ConcurrentHashMap
/**
* 管理任务数据下载
*/
class TaskDownloadManager(
val netApi: RetrofitNetworkServiceAPI,
) {
/**
* 最多同时下载数量
*/
private val MAX_SCOPE = 3
/**
* 存储有哪些城市需要下载的队列
*/
private val scopeMap: ConcurrentHashMap<Int, TaskDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
ConcurrentHashMap<Int, TaskDownloadScope>()
}
/**
* 存储正在下载的城市队列
*/
private val taskScopeMap: ConcurrentHashMap<Int, TaskDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
ConcurrentHashMap<Int, TaskDownloadScope>()
}
/**
* 启动下载任务
* 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用
*/
fun launchScope(scope: TaskDownloadScope) {
if (taskScopeMap.size >= MAX_SCOPE) {
return
}
if (taskScopeMap.contains(scope.taskBean.id)) {
return
}
taskScopeMap[scope.taskBean.id] = scope
scope.launch()
}
/**
* 启动下一个任务,如果有正在等待中的任务的话
* 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用
* @param previousUrl 上一个下载任务的下载连接
*/
fun launchNext(id: Int) {
taskScopeMap.remove(id)
for (entrySet in scopeMap) {
val downloadScope = entrySet.value
if (downloadScope.isWaiting()) {
launchScope(downloadScope)
break
}
}
}
/**
* 暂停任务
* 只有等待中的任务和正在下载中的任务才可以进行暂停操作
*/
fun pause(id: Int) {
if (taskScopeMap.containsKey(id)) {
val downloadScope = taskScopeMap[id]
downloadScope?.let {
downloadScope.pause()
}
launchNext(id)
}
}
/**
* 将下载任务加入到协程作用域的下载队列里
* 请求一个下载任务[OfflineMapDownloadScope]
* 这是创建[OfflineMapDownloadScope]的唯一途径,请不要通过其他方式创建[OfflineMapDownloadScope]
*/
fun start(id: Int) {
scopeMap[id]?.start()
}
fun addTask(taskBean: TaskBean) {
if (!scopeMap.containsKey(taskBean.id)) {
scopeMap[taskBean.id] = TaskDownloadScope(this, taskBean)
}
}
fun observer(
id: Int, lifecycleOwner: LifecycleOwner, observer: Observer<TaskBean>
) {
if (scopeMap.containsKey(id)) {
scopeMap[id]!!.observer(lifecycleOwner, observer)
}
}
fun removeObserver(id: Int) {
if (scopeMap.containsKey(id)) {
scopeMap[id]!!.removeObserver()
}
}
}

View File

@@ -0,0 +1,168 @@
package com.navinfo.omqs.http.taskdownload
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.TaskBean
import com.navinfo.omqs.tools.FileManager.Companion.FileDownloadStatus
import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.RandomAccessFile
class TaskDownloadScope(
private val downloadManager: TaskDownloadManager,
val taskBean: TaskBean,
) :
CoroutineScope by CoroutineScope(Dispatchers.IO + CoroutineName("OfflineMapDownLoad")) {
/**
*下载任务,用来取消的
*/
private var downloadJob: Job? = null
/**
* 管理观察者,同时只有一个就行了
*/
private val observer = Observer<Any> {}
// private var lifecycleOwner: LifecycleOwner? = null
/**
*通知UI更新
*/
private val downloadData = MutableLiveData<TaskBean>()
init {
downloadData.value = taskBean
}
//改进的代码
fun start() {
change(FileDownloadStatus.WAITING)
downloadManager.launchScope(this@TaskDownloadScope)
}
/**
* 暂停任务
* 其实就是取消任务,移除监听
*/
fun pause() {
downloadJob?.cancel("pause")
change(FileDownloadStatus.PAUSE)
}
/**
* 启动协程进行下载
* 请不要尝试在外部调用此方法,那样会脱离[OfflineMapDownloadManager]的管理
*/
fun launch() {
downloadJob = launch() {
download()
downloadManager.launchNext(taskBean.id)
}
}
/**
* 是否是等待任务
*/
fun isWaiting(): Boolean {
return taskBean.status == FileDownloadStatus.WAITING
}
/**
* 更新任务
* @param status [OfflineMapCityBean.Status]
*/
private fun change(status: Int) {
if (taskBean.status != status || status == FileDownloadStatus.LOADING) {
taskBean.status = status
downloadData.postValue(taskBean)
launch(Dispatchers.IO) {
// downloadManager.roomDatabase.getOfflineMapDao().update(taskBean)
}
}
}
/**
* 添加下载任务观察者
*/
fun observer(owner: LifecycleOwner, ob: Observer<TaskBean>) {
removeObserver()
// this.lifecycleOwner = owner
downloadData.observe(owner, ob)
}
/**
* 下载文件
*/
private suspend fun download() {
var inputStream: InputStream? = null
var randomAccessFile: RandomAccessFile? = null
try {
//创建离线地图 下载文件夹,.map文件夹的下一级
val fileDir = File("${Constant.OFFLINE_MAP_PATH}download")
if (!fileDir.exists()) {
fileDir.mkdirs()
}
val fileTemp =
File("${Constant.OFFLINE_MAP_PATH}download/${taskBean.id}_${taskBean.dataVersion}")
val startPosition = taskBean.currentSize
//验证断点有效性
if (startPosition < 0) throw IOException("jingo Start position less than zero")
val response = downloadManager.netApi.retrofitDownLoadFile(
start = "bytes=$startPosition-",
url = taskBean.getDownLoadUrl()
)
val responseBody = response.body()
change(FileDownloadStatus.LOADING)
responseBody ?: throw IOException("jingo ResponseBody is null")
//写入文件
randomAccessFile = RandomAccessFile(fileTemp, "rwd")
randomAccessFile.seek(startPosition)
taskBean.currentSize = startPosition
inputStream = responseBody.byteStream()
val bufferSize = 1024 * 2
val buffer = ByteArray(bufferSize)
var readLength = 0
while (downloadJob?.isActive == true) {
readLength = inputStream.read(buffer)
if (readLength != -1) {
randomAccessFile.write(buffer, 0, readLength)
taskBean.currentSize += readLength
change(FileDownloadStatus.LOADING)
} else {
break
}
}
Log.e("jingo", "文件下载完成 ${taskBean.currentSize} == ${taskBean.fileSize}")
if (taskBean.currentSize == taskBean.fileSize) {
val res =
fileTemp.renameTo(File("${Constant.OFFLINE_MAP_PATH}${taskBean.evaluationTaskName}.zip"))
Log.e("jingo", "文件下载完成 修改文件 $res")
change(FileDownloadStatus.DONE)
} else {
change(FileDownloadStatus.PAUSE)
}
} catch (e: Throwable) {
change(FileDownloadStatus.ERROR)
} finally {
inputStream?.close()
randomAccessFile?.close()
}
}
fun removeObserver() {
downloadData.observeForever(observer)
// lifecycleOwner?.let {
downloadData.removeObserver(observer)
// null
// }
}
}