diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8d175c01..4e0cee42 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -26,6 +26,7 @@
+
+ suspend fun retrofitDownLoadFile(@Header("RANGE") start: String? = "0", @Url url: String):Response
/**
diff --git a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt
index cc720498..d38896f0 100644
--- a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt
+++ b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt
@@ -1,48 +1,126 @@
package com.navinfo.omqs.http.offlinemapdownload
import android.content.Context
-import android.os.Environment
import android.text.TextUtils
+import android.util.Log
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.Observer
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.OfflineMapCityBean
+import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import dagger.hilt.android.qualifiers.ActivityContext
-import java.io.Serializable
+import kotlinx.coroutines.cancel
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/**
* 管理离线地图下载
*/
-class OfflineMapDownloadManager @Inject constructor(@ActivityContext context: Context) {
+class OfflineMapDownloadManager @Inject constructor(
+ private val netApi: RetrofitNetworkServiceAPI
+) {
/**
* 最多同时下载数量
*/
- private val MAX_SCOPE = 5
+ private val MAX_SCOPE = 3
/**
- * 存储有哪些城市需要下载
+ * 存储有哪些城市需要下载的队列
*/
private val scopeMap: ConcurrentHashMap by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
ConcurrentHashMap()
}
- val downloadFolder: String? by lazy {
- Constant.MAP_PATH + "/offline/"
+ /**
+ * 存储正在下载的城市队列
+ */
+ private val taskScopeMap: ConcurrentHashMap by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
+ ConcurrentHashMap()
}
/**
+ * 启动下载任务
+ * 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用
+ */
+ fun launchScope(scope: OfflineMapDownloadScope) {
+ if (taskScopeMap.size >= MAX_SCOPE) {
+ return
+ }
+ if (taskScopeMap.contains(scope.cityBean.id)) {
+ return
+ }
+ taskScopeMap[scope.cityBean.id] = scope
+ scope.launch()
+ }
+
+ /**
+ * 启动下一个任务,如果有正在等待中的任务的话
+ * 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用
+ * @param previousUrl 上一个下载任务的下载连接
+ */
+ fun launchNext(previousUrl: String) {
+ taskScopeMap.remove(previousUrl)
+ for (entrySet in scopeMap) {
+ val downloadScope = entrySet.value
+ if (downloadScope.isWaiting()) {
+ launchScope(downloadScope)
+ break
+ }
+ }
+ }
+
+ /**
+ * 暂停任务
+ * 只有等待中的任务和正在下载中的任务才可以进行暂停操作
+ */
+ fun pause(id: String) {
+ if (taskScopeMap.containsKey(id)) {
+ val downloadScope = taskScopeMap[id]
+ downloadScope?.let {
+ downloadScope.pause()
+ }
+ launchNext(id)
+ }
+
+ }
+
+ /**
+ * 将下载任务加入到协程作用域的下载队列里
* 请求一个下载任务[OfflineMapDownloadScope]
* 这是创建[OfflineMapDownloadScope]的唯一途径,请不要通过其他方式创建[OfflineMapDownloadScope]
*/
- fun request(cityBean: OfflineMapCityBean): OfflineMapDownloadScope? {
- //没有下载连接的不能下载
- if (TextUtils.isEmpty(cityBean.url)) return null
-// if(scopeMap.containsKey())
- var downloadScope = scopeMap[cityBean.id]
- if (downloadScope == null) {
- scopeMap[cityBean.id] = OfflineMapDownloadScope(cityBean)
- }
- return downloadScope
+ fun start(id: String) {
+ scopeMap[id]?.start()
}
+
+ fun cancel(id: String) {
+ taskScopeMap.remove(id)
+ scopeMap[id]?.cancelTask()
+ }
+
+ fun addTask(cityBean: OfflineMapCityBean) {
+ if (scopeMap.containsKey(cityBean.id)) {
+ return
+ } else {
+ scopeMap[cityBean.id] = OfflineMapDownloadScope(this, netApi, cityBean)
+ }
+ }
+
+
+ fun observer(
+ id: String,
+ lifecycleOwner: LifecycleOwner,
+ observer: Observer
+ ) {
+ if (scopeMap.containsKey(id)) {
+ val downloadScope = scopeMap[id]
+ downloadScope?.let {
+ downloadScope.observer(lifecycleOwner, observer)
+ }
+ }
+ }
+
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt
index a51f93d5..c518b504 100644
--- a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt
+++ b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt
@@ -1,26 +1,255 @@
package com.navinfo.omqs.http.offlinemapdownload
+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.OfflineMapCityBean
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
+import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
+import kotlinx.coroutines.*
+import java.io.File
+import java.io.IOException
+import java.io.RandomAccessFile
import kotlin.coroutines.EmptyCoroutineContext
/**
* 代表一个下载任务
* [OfflineMapCityBean.id]将做为下载任务的唯一标识
- * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.download]获取此对象
+ * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.request]获取此对象
* 这是一个协程作用域,
* EmptyCoroutineContext 表示一个不包含任何元素的协程上下文,它通常用于创建新的协程上下文,或者作为协程上下文的基础。
*/
-class OfflineMapDownloadScope(cityBean: OfflineMapCityBean) : CoroutineScope by CoroutineScope(EmptyCoroutineContext) {
+class OfflineMapDownloadScope(
+ private val downloadManager: OfflineMapDownloadManager,
+ private val netApi: RetrofitNetworkServiceAPI,
+ val cityBean: OfflineMapCityBean
+
+) :
+ CoroutineScope by CoroutineScope(EmptyCoroutineContext) {
/**
- *
+ *下载任务,用来取消的
*/
private var downloadJob: Job? = null
+ /**
+ * 管理观察者,同时只有一个就行了
+ */
+ private var observer: Observer? = null
+
/**
*
*/
private val downloadData = MutableLiveData()
+
+ init {
+ downloadData.value = cityBean
+ }
+
+ /**
+ * 开始任务的下载
+ * [OfflineMapCityBean]是在协程中进行创建的,它的创建会优先从数据库和本地文件获取,但这种操作是异步的,详情请看init代码块
+ * 我们需要通过观察者观察[OfflineMapCityBean]来得知它是否已经创建完成,只有当他创建完成且不为空(如果创建完成,它一定不为空)
+ * 才可以交由[OfflineMapDownloadManager]进行下载任务的启动
+ * 任务的开始可能并不是立即的,任务会受到[OfflineMapDownloadManager]的管理
+ *
+ * 这段原来代码没看懂:要触发 Observer 得观察的对象[OfflineMapCityBean]发生变化才行,原demo里没找到livedata的变化也触发了onChange,这里根本触发不了
+ *
+ * 找到原因了:是[cityBean]根本没有设置到liveData中,但是还是不用这样了,因为cityBean是一定创建好了的
+ */
+ //原代码
+// fun start() {
+// var observer: Observer? = null
+// observer = Observer { cityBean ->
+// Log.e("jingo","Observer 创建了,bean 为null吗?$cityBean")
+// cityBean?.let {
+// observer?.let {
+// Log.e("jingo","Observer 这里为什么要解除观察?")
+// downloadData.removeObserver(it)
+// }
+// Log.e("jingo","Observer 状态 ${cityBean.status} ")
+// when (cityBean.status) {
+//
+// OfflineMapCityBean.PAUSE, OfflineMapCityBean.ERROR, OfflineMapCityBean.NONE -> {
+// change(OfflineMapCityBean.WAITING)
+// downloadManager.launchScope(this@OfflineMapDownloadScope)
+// }
+// }
+// }
+// }
+// downloadData.observeForever(observer)
+// }
+ //改进的代码
+ fun start() {
+ change(OfflineMapCityBean.WAITING)
+ downloadManager.launchScope(this@OfflineMapDownloadScope)
+ }
+
+ /**
+ * 暂停任务
+ * 其实就是取消任务,移除监听
+ */
+ fun pause() {
+ downloadJob?.cancel("pause")
+ }
+
+ /**
+ * 启动协程进行下载
+ * 请不要尝试在外部调用此方法,那样会脱离[OfflineMapDownloadManager]的管理
+ */
+ fun launch() {
+ downloadJob = launch {
+ try {
+ download()
+ change(OfflineMapCityBean.DONE)
+ } catch (e: Throwable) {
+ Log.e("jingo DownloadScope", "error:${e.message}")
+ if (e.message == "pause") {
+ change(OfflineMapCityBean.PAUSE)
+ } else {
+ change(OfflineMapCityBean.ERROR)
+ }
+ } finally {
+ downloadManager.launchNext(cityBean.id)
+ }
+ }
+ }
+
+
+ /**
+ * 是否是等待任务
+ */
+ fun isWaiting(): Boolean {
+ val downloadInfo = downloadData.value
+ downloadInfo ?: return false
+ return downloadInfo.status == OfflineMapCityBean.WAITING
+ }
+
+ /**
+ * 更新任务
+ * @param status [OfflineMapCityBean.Status]
+ */
+ private fun change(status: Int) {
+ downloadData.value?.let {
+ it.status = status
+ downloadData.postValue(it)
+ }
+ }
+
+ /**
+ * 添加下载任务观察者
+ */
+ fun observer(lifecycleOwner: LifecycleOwner, ob: Observer) {
+ if (observer != null) {
+ downloadData.removeObserver(observer!!)
+ }
+ this.observer = ob
+ downloadData.observe(lifecycleOwner, observer!!)
+ }
+
+ /**
+ * 下载文件
+ */
+ private suspend fun download() = withContext(context = Dispatchers.IO, block = {
+
+ val downloadInfo = downloadData.value ?: throw IOException("jingo Download info is null")
+ //创建离线地图 下载文件夹,.map文件夹的下一级
+ val fileDir = File("${Constant.OFFLINE_MAP_PATH}download")
+ if (!fileDir.exists()) {
+ fileDir.mkdirs()
+ }
+ //遍历文件夹,找到对应的省市.map文件
+ val files = fileDir.listFiles()
+ for (item in files) {
+ //用id找到对应的文件
+ if (item.isFile && item.name.startsWith(downloadInfo.id)) {
+ //判断文件的版本号是否一致
+ if (item.name.contains("_${downloadInfo.version}.map")) {
+ //都一致,说明文件已经下载完成,不用再次下载
+ change(OfflineMapCityBean.DONE)
+ return@withContext
+ }else{
+
+ }
+ break
+ }
+ }
+
+ //查看下.map文件夹在不在
+ val fileMap = File("${Constant.OFFLINE_MAP_PATH}${downloadInfo.fileName}")
+ val fileTemp =
+ File("${Constant.OFFLINE_MAP_PATH}download/${downloadInfo.id}_${downloadInfo.version}")
+
+
+ if (fileTemp.exists()) {
+
+ }
+
+ if (!fileMap.exists()) {
+ }
+
+ change(OfflineMapCityBean.LOADING)
+
+
+ val startPosition = downloadInfo.currentSize
+ //验证断点有效性
+ if (startPosition < 0) throw IOException("jingo Start position less than zero")
+ //下载的文件是否已经被删除
+// if (startPosition > 0 && !TextUtils.isEmpty(downloadInfo.path))
+// if (!File(downloadInfo.path).exists()) throw IOException("File does not exist")
+ val response = netApi.retrofitDownLoadFile(
+ start = "bytes=$startPosition-",
+ url = downloadInfo.url
+ )
+ val responseBody = response.body()
+
+ responseBody ?: throw IOException("jingo ResponseBody is null")
+ //文件长度
+ downloadInfo.fileSize = responseBody.contentLength()
+ //保存的文件名称
+// if (TextUtils.isEmpty(downloadInfo.fileName))
+// downloadInfo.fileName = UrlUtils.getUrlFileName(downloadInfo.url)
+
+// //验证下载完成的任务与实际文件的匹配度
+// if (startPosition == downloadInfo.fileSize && startPosition > 0) {
+// if (file.exists() && startPosition == file.length()) {
+// change(OfflineMapCityBean.DONE)
+// return@withContext
+// } else throw IOException("jingo The content length is not the same as the file length")
+// }
+ //写入文件
+ val randomAccessFile = RandomAccessFile(fileTemp, "rwd")
+ randomAccessFile.seek(startPosition)
+// if (downloadInfo.currentSize == 0L) {
+// randomAccessFile.setLength(downloadInfo.fileSize)
+// }
+ downloadInfo.currentSize = startPosition
+ val inputStream = responseBody.byteStream()
+ val bufferSize = 1024 * 2
+ val buffer = ByteArray(bufferSize)
+ try {
+ var readLength = 0
+ while (isActive) {
+ readLength = inputStream.read(buffer)
+ if (readLength != -1) {
+ randomAccessFile.write(buffer, 0, readLength)
+ downloadInfo.currentSize += readLength
+ change(OfflineMapCityBean.LOADING)
+ } else {
+ break
+ }
+ }
+ } finally {
+ inputStream.close()
+ randomAccessFile.close()
+ }
+ })
+
+ /**
+ *
+ */
+ private fun checkFile(){
+
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt
index f4fc2611..36c50b0b 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt
@@ -7,6 +7,7 @@ import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.Observer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityLoginBinding
@@ -30,41 +31,49 @@ class LoginActivity : PermissionsActivity() {
initView()
}
- private fun initView() {
- //登录校验,初始化成功
- viewModel.loginStatus.observe(this) {
- when (it) {
- LoginStatus.LOGIN_STATUS_NET_LOADING -> {
- loginDialog("验证用户信息...")
- }
- LoginStatus.LOGIN_STATUS_FOLDER_INIT -> {
- loginDialog("检查本地数据...")
- }
- LoginStatus.LOGIN_STATUS_FOLDER_FAILURE -> {
- Toast.makeText(this, "文件夹初始化失败", Toast.LENGTH_SHORT).show()
- loginDialog?.dismiss()
- loginDialog = null
- }
- LoginStatus.LOGIN_STATUS_NET_FAILURE -> {
- Toast.makeText(this, "网络访问失败", Toast.LENGTH_SHORT).show()
- loginDialog?.dismiss()
- loginDialog = null
- }
- LoginStatus.LOGIN_STATUS_SUCCESS -> {
- val intent = Intent(this@LoginActivity, MainActivity::class.java)
- startActivity(intent)
+ /**
+ * 观察登录状态,把Observer提出来是为了防止每次数据变化都会有新的observer创建
+ * 还有为了方便释放(需不需要手动释放?不清楚)不需要释放,当viewmodel观察到activity/fragment 的生命周期时会自动释放
+ * PS:不要在 observer 中修改 LiveData 的值的数据,会影响其他 observer
+ */
+ private val loginObserve = Observer {
+ when (it) {
+ LoginStatus.LOGIN_STATUS_NET_LOADING -> {
+ loginDialog("验证用户信息...")
+ }
+ LoginStatus.LOGIN_STATUS_FOLDER_INIT -> {
+ loginDialog("检查本地数据...")
+ }
+ LoginStatus.LOGIN_STATUS_FOLDER_FAILURE -> {
+ Toast.makeText(this, "文件夹初始化失败", Toast.LENGTH_SHORT).show()
+ loginDialog?.dismiss()
+ loginDialog = null
+ }
+ LoginStatus.LOGIN_STATUS_NET_FAILURE -> {
+ Toast.makeText(this, "网络访问失败", Toast.LENGTH_SHORT).show()
+ loginDialog?.dismiss()
+ loginDialog = null
+ }
+ LoginStatus.LOGIN_STATUS_SUCCESS -> {
+ val intent = Intent(this@LoginActivity, MainActivity::class.java)
+ startActivity(intent)
// finish()
- loginDialog?.dismiss()
- loginDialog = null
- }
- LoginStatus.LOGIN_STATUS_CANCEL -> {
- loginDialog?.dismiss()
- loginDialog = null
- }
+ loginDialog?.dismiss()
+ loginDialog = null
+ }
+ LoginStatus.LOGIN_STATUS_CANCEL -> {
+ loginDialog?.dismiss()
+ loginDialog = null
}
}
}
+ private fun initView() {
+ //登录校验,初始化成功
+ viewModel.loginStatus.observe(this, loginObserve)
+
+ }
+
/**
* 登录dialog
*/
@@ -73,7 +82,7 @@ class LoginActivity : PermissionsActivity() {
loginDialog = MaterialAlertDialogBuilder(
this, com.google.android.material.R.style.MaterialAlertDialog_Material3
).setTitle("登录").setMessage(message).show()
- loginDialog!!.setCanceledOnTouchOutside(true)
+ loginDialog!!.setCanceledOnTouchOutside(false)
loginDialog!!.setOnCancelListener {
viewModel.cancelLogin()
}
@@ -84,13 +93,17 @@ class LoginActivity : PermissionsActivity() {
//进应用根本不调用,待查
override fun onPermissionsGranted() {
- Log.e("jingo","调用了吗")
+ Log.e("jingo", "调用了吗")
}
override fun onPermissionsDenied() {
}
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+
/**
* 处理登录按钮
*/
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
index 2d6f29b6..ad02cc83 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
@@ -5,6 +5,7 @@ import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.navinfo.omqs.Constant
@@ -65,7 +66,7 @@ class LoginViewModel(
*/
fun onClick(view: View) {
loginUser.value!!.username = "admin2"
- loginUser.postValue(loginUser.value)
+ loginUser.value = loginUser.value
}
/**
@@ -81,7 +82,6 @@ class LoginViewModel(
//不指定IO,会在主线程里运行
jobLogin = viewModelScope.launch(Dispatchers.IO) {
loginCheck(context, userName, password)
- Log.e("jingo", "运行完了1?${Thread.currentThread().name}")
}
}
@@ -90,14 +90,12 @@ class LoginViewModel(
*/
private suspend fun loginCheck(context: Context, userName: String, password: String) {
- Log.e("jingo", "我在哪个线程里?${Thread.currentThread().name}")
//上面调用了线程切换,这里不用调用,即使调用了还是在同一个线程中,除非自定义协程域?(待验证)
// withContext(Dispatchers.IO) {
- Log.e("jingo", "delay之前?${Thread.currentThread().name}")
//网络访问
loginStatus.postValue(LoginStatus.LOGIN_STATUS_NET_LOADING)
- //假装网络访问,等待3秒
- delay(3000)
+ //假装网络访问,等待2秒
+ delay(1000)
//文件夹初始化
try {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT)
@@ -108,7 +106,6 @@ class LoginViewModel(
//假装解压文件等
delay(1000)
loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS)
- Log.e("jingo", "delay之后?${Thread.currentThread().name}")
// }
}
@@ -121,6 +118,7 @@ class LoginViewModel(
sdCardPath?.let {
Constant.ROOT_PATH = sdCardPath.absolutePath
Constant.MAP_PATH = Constant.ROOT_PATH + "/map/"
+ Constant.OFFLINE_MAP_PATH = Constant.MAP_PATH + "offline/"
val file = File(Constant.MAP_PATH)
if (!file.exists()) {
file.mkdirs()
@@ -135,7 +133,7 @@ class LoginViewModel(
Log.e("jingo", "取消了?${Thread.currentThread().name}")
jobLogin?.let {
it.cancel()
- loginStatus.postValue(LoginStatus.LOGIN_STATUS_CANCEL)
+ loginStatus.value = LoginStatus.LOGIN_STATUS_CANCEL
}
}
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt
index cb03179e..0ec5416c 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt
@@ -11,6 +11,7 @@ import com.navinfo.collect.library.map.NIMapController
import com.navinfo.omqs.Constant
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityMainBinding
+import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import com.navinfo.omqs.ui.activity.BaseActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@@ -27,6 +28,8 @@ class MainActivity : BaseActivity() {
//注入地图控制器
@Inject
lateinit var mapController: NIMapController
+ @Inject
+ lateinit var offlineMapDownloadManager: OfflineMapDownloadManager
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt
index 4c1e6786..f3c863a4 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt
@@ -3,6 +3,7 @@ package com.navinfo.omqs.ui.fragment.offlinemap
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
+import dagger.hilt.EntryPoint
/**
* 离线地图主页面,viewpage适配器
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt
index 8f94464c..8894d104 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt
@@ -1,33 +1,118 @@
package com.navinfo.omqs.ui.fragment.offlinemap
-import androidx.databinding.ViewDataBinding
+import android.content.Context
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.Observer
import com.navinfo.omqs.R
-import com.navinfo.omqs.BR
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding
+import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter
import com.navinfo.omqs.ui.other.BaseViewHolder
import javax.inject.Inject
/**
* 离线地图城市列表 RecyclerView 适配器
+ *
+ * 在 RecycleView 的 ViewHolder 中监听 ViewModel 的 LiveData,然后此时传递的 lifecycleOwner 是对应的 Fragment。由于 ViewHolder 的生命周期是比 Fragment 短的,所以当 ViewHolder 销毁时,由于 Fragment 的 Lifecycle 还没有结束,此时 ViewHolder 会发生内存泄露(监听的 LiveData 没有解绑)
+ * 这种场景下有两种解决办法:
+ *使用 LiveData 的 observeForever 然后在 ViewHolder 销毁前手动调用 removeObserver
+ *使用 LifecycleRegistry 给 ViewHolder 分发生命周期(这里使用了这个)
*/
+class OfflineMapCityListAdapter @Inject constructor(
+ private val downloadManager: OfflineMapDownloadManager, private val context: Context
+) : BaseRecyclerViewAdapter() {
-class OfflineMapCityListAdapter @Inject constructor() :
- BaseRecyclerViewAdapter() {
- override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
- var binding: ViewDataBinding = holder.dataBinding
- //立刻刷新UI,解决闪烁
-// binding.executePendingBindings()
- binding.setVariable(BR.cityBean, data[position])
- (binding as AdapterOfflineMapCityBinding).offlineMapDownloadBtn.setOnClickListener {
+ private val downloadBtnClick = View.OnClickListener() {
+ if (it.tag != null) {
+ val cityBean = data[it.tag as Int]
+ when (cityBean.status) {
+ OfflineMapCityBean.NONE, OfflineMapCityBean.UPDATE, OfflineMapCityBean.PAUSE, OfflineMapCityBean.ERROR -> {
+ downloadManager.start(cityBean.id)
+ }
+ OfflineMapCityBean.LOADING, OfflineMapCityBean.WAITING -> {
+ downloadManager.pause(cityBean.id)
+ }
+// OfflineMapCityBean.WAITING->{
+// downloadManager.cancel(cityBean.id)
+// }
+ }
}
+ }
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
+ val viewBinding =
+ AdapterOfflineMapCityBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return BaseViewHolder(viewBinding)
+ }
+
+ override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
+ val binding: AdapterOfflineMapCityBinding =
+ holder.viewBinding as AdapterOfflineMapCityBinding
+ //牺牲性能立刻刷新UI,解决闪烁 这里不用
+// binding.executePendingBindings()
+ val cityBean = data[position]
+ binding.offlineMapDownloadBtn.tag = position
+ binding.offlineMapDownloadBtn.setOnClickListener(downloadBtnClick)
+ binding.offlineMapCityName.text = cityBean.name
+ binding.offlineMapCitySize.text = cityBean.getFileSizeText()
+ downloadManager.addTask(cityBean)
+ changeViews(binding, cityBean)
+ downloadManager.observer(cityBean.id, holder) {
+ if (cityBean.id == it.id)
+ changeViews(binding, it)
+ }
+ }
+
+ private fun changeViews(binding: AdapterOfflineMapCityBinding, cityBean: OfflineMapCityBean) {
+ binding.offlineMapProgress.progress =
+ (cityBean.currentSize * 100 / cityBean.fileSize).toInt()
+ when (cityBean.status) {
+ OfflineMapCityBean.NONE -> {
+ if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
+ View.INVISIBLE
+ binding.offlineMapDownloadBtn.text = "下载"
+ }
+ OfflineMapCityBean.WAITING -> {
+ if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
+ View.VISIBLE
+ binding.offlineMapDownloadBtn.text = "等待中"
+ }
+ OfflineMapCityBean.LOADING -> {
+ if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
+ View.VISIBLE
+ binding.offlineMapDownloadBtn.text = "暂停"
+ }
+ OfflineMapCityBean.PAUSE -> {
+ if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
+ View.VISIBLE
+ binding.offlineMapDownloadBtn.text = "继续"
+ }
+ OfflineMapCityBean.ERROR -> {
+ if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
+ View.VISIBLE
+ binding.offlineMapDownloadBtn.text = "重试"
+ }
+ OfflineMapCityBean.DONE -> {
+ if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
+ View.INVISIBLE
+ binding.offlineMapDownloadBtn.text = "已完成"
+ }
+ OfflineMapCityBean.UPDATE -> {
+ if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
+ View.INVISIBLE
+ binding.offlineMapDownloadBtn.text = "更新"
+ }
+ }
}
override fun getItemViewType(position: Int): Int {
return R.layout.adapter_offline_map_city
}
+}
+
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt
index 23b74da5..1baa5823 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt
@@ -10,17 +10,28 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.navinfo.omqs.databinding.FragmentOfflineMapCityListBinding
+import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
+import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
/**
* 离线地图城市列表
*/
@AndroidEntryPoint
class OfflineMapCityListFragment : Fragment() {
+ @Inject
+ lateinit var downloadManager: OfflineMapDownloadManager
private var _binding: FragmentOfflineMapCityListBinding? = null
private val viewModel by viewModels()
private val binding get() = _binding!!
- private val adapter: OfflineMapCityListAdapter by lazy { OfflineMapCityListAdapter() }
+ private val adapter: OfflineMapCityListAdapter by lazy {
+ OfflineMapCityListAdapter(
+ downloadManager,
+ requireContext()
+ )
+ }
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -33,8 +44,10 @@ class OfflineMapCityListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val layoutManager = LinearLayoutManager(context)
- _binding!!.offlineMapCityListRecyclerview.layoutManager = layoutManager
- _binding!!.offlineMapCityListRecyclerview.adapter = adapter
+ //// 设置 RecyclerView 的固定大小,避免在滚动时重新计算视图大小和布局,提高性能
+ binding.offlineMapCityListRecyclerview.setHasFixedSize(true)
+ binding.offlineMapCityListRecyclerview.layoutManager = layoutManager
+ binding.offlineMapCityListRecyclerview.adapter = adapter
viewModel.cityListLiveData.observe(viewLifecycleOwner) {
adapter.refreshData(it)
}
@@ -44,6 +57,6 @@ class OfflineMapCityListFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
_binding = null
- Log.e("jingo","OfflineMapCityListFragment onDestroyView")
+ Log.e("jingo", "OfflineMapCityListFragment onDestroyView")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt
index 4eba17ae..93ddba13 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt
@@ -32,7 +32,7 @@ class OfflineMapCityListViewModel @Inject constructor(
viewModelScope.launch {
when (val result = networkService.getOfflineMapCityList()) {
is NetResult.Success -> {
- cityListLiveData.postValue(result.data!!)
+ cityListLiveData.postValue(result.data?.sortedBy { bean -> bean.id })
}
is NetResult.Error -> {
Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT)
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapViewHolder.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapViewHolder.kt
new file mode 100644
index 00000000..02a03583
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapViewHolder.kt
@@ -0,0 +1,10 @@
+package com.navinfo.omqs.ui.fragment.offlinemap
+
+import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding
+import com.navinfo.omqs.ui.other.BaseViewHolder
+
+class OfflineMapViewHolder(dataBinding: AdapterOfflineMapCityBinding) : BaseViewHolder(dataBinding) {
+ init{
+ dataBinding.offlineMapDownloadBtn
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt
index 33747c57..fa964626 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt
@@ -1,6 +1,7 @@
package com.navinfo.omqs.ui.other
import android.view.LayoutInflater
+import android.view.View.OnClickListener
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
@@ -10,23 +11,51 @@ import androidx.recyclerview.widget.RecyclerView
*/
abstract class BaseRecyclerViewAdapter(var data: List = listOf()) :
RecyclerView.Adapter() {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
- return BaseViewHolder(
- DataBindingUtil.inflate(
- LayoutInflater.from(parent.context),
- viewType,
- parent,
- false
- )
- )
- }
+ // private var recyclerView: RecyclerView? = null
+// override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
+//
+//
+//
+// return BaseViewHolder(
+// DataBindingUtil.inflate(
+// LayoutInflater.from(parent.context),
+// viewType,
+// parent,
+// false
+// )
+// )
+// }
+
override fun getItemCount(): Int {
return data.size
}
- fun refreshData(newData:List){
+ fun refreshData(newData: List) {
this.data = newData
this.notifyDataSetChanged()
}
+
+
+ override fun onViewAttachedToWindow(holder: BaseViewHolder) {
+ super.onViewAttachedToWindow(holder)
+ holder.onStart()
+ }
+
+ override fun onViewDetachedFromWindow(holder: BaseViewHolder) {
+ super.onViewDetachedFromWindow(holder)
+ holder.apply {
+ onStop()
+ }
+ }
+//
+// override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
+// super.onAttachedToRecyclerView(recyclerView)
+// this.recyclerView = recyclerView
+// }
+//
+// override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
+// super.onDetachedFromRecyclerView(recyclerView)
+// this.recyclerView = null
+// }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt b/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt
index a06b61ca..e6f4d62c 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt
@@ -1,11 +1,54 @@
package com.navinfo.omqs.ui.other
-import androidx.databinding.ViewDataBinding
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
/**
* dataBinding viewHolder 基类
+ * LifecycleRegistry 这是一个生命周期注册器,继承自 Lifecycle,LifecycleOwner 通过这个类来分发生命周期事件,并在 getLifecycle() 中返回
*/
-open class BaseViewHolder(var dataBinding: ViewDataBinding) :
- RecyclerView.ViewHolder(dataBinding.root) {
+open class BaseViewHolder(val viewBinding: ViewBinding) :
+ RecyclerView.ViewHolder(viewBinding.root), LifecycleOwner {
+ private val lifecycleRegistry = LifecycleRegistry(this)
+
+ init {
+// dataBinding.lifecycleOwner = this
+ lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
+ lifecycleRegistry.currentState = Lifecycle.State.CREATED
+ itemView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+ // View onDetached 的时候回调 onDestroy()
+ override fun onViewDetachedFromWindow(v: View) {
+ itemView.removeOnAttachStateChangeListener(this)
+ onDestroy()
+ }
+
+ // View onAttached 的时候回调 onCreate()
+ override fun onViewAttachedToWindow(v: View) {
+ onStart()
+ }
+ })
+ }
+
+ fun onStart() {
+ lifecycleRegistry.currentState = Lifecycle.State.STARTED //
+ lifecycleRegistry.currentState = Lifecycle.State.RESUMED // ON_RESUME EVENT
+ }
+
+ fun onStop() {
+ lifecycleRegistry.currentState = Lifecycle.State.STARTED //
+ lifecycleRegistry.currentState = Lifecycle.State.CREATED // ON_STOP EVENT
+ }
+
+ fun onDestroy() {
+ lifecycleRegistry.currentState = Lifecycle.State.DESTROYED /// ON_DESTROY EVENT
+ }
+
+
+ override fun getLifecycle(): Lifecycle {
+ return lifecycleRegistry
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt b/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt
index 0f1180a6..aab03188 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt
@@ -5,10 +5,13 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
-import android.opengl.ETC1.getHeight
-import android.opengl.ETC1.getWidth
import android.util.AttributeSet
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
import android.widget.ProgressBar
+import com.navinfo.omqs.R
/**
@@ -18,8 +21,13 @@ class MyProgressBar : ProgressBar {
private lateinit var mPaint: Paint
private var text: String = ""
private var rate = 0f
+ private lateinit var bar: ProgressBar
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
+// LayoutInflater.from(context).inflate(
+// R.layout.my_projressbar, this,
+// true
+// );
initView()
}
@@ -33,6 +41,7 @@ class MyProgressBar : ProgressBar {
mPaint.color = Color.BLUE
}
+
@Synchronized
override fun setProgress(progress: Int) {
setText(progress)
@@ -40,7 +49,7 @@ class MyProgressBar : ProgressBar {
}
private fun setText(progress: Int) {
- rate = progress * 1.0f / this.getMax()
+ rate = progress * 1.0f / this.max
val i = (rate * 100).toInt()
text = "$i%"
}
@@ -57,8 +66,9 @@ class MyProgressBar : ProgressBar {
// 如果为百分之百则在左边绘制。
x = width - rect.right
}
- val y: Int = 0 - rect.top
- mPaint.textSize = 22f
+ mPaint.textSize = 24f
+ val y: Int = 10 - rect.top
+
canvas.drawText(text, x.toFloat(), y.toFloat(), mPaint)
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/progress_bg.xml b/app/src/main/res/drawable/progress_bg.xml
new file mode 100644
index 00000000..244083fc
--- /dev/null
+++ b/app/src/main/res/drawable/progress_bg.xml
@@ -0,0 +1,21 @@
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index d1405c56..1d88e9f5 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -17,8 +17,7 @@
+ android:layout_height="match_parent">
-
-
-
-
-
-
-
-
-
+ android:text="省市名称"
+ android:textColor="@color/white"
+ android:textSize="@dimen/default_font_size" />
-
+
-
-
+
-
+
-
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/utils/GeometryTools.java b/collect-library/src/main/java/com/navinfo/collect/library/utils/GeometryTools.java
index 4c6d0519..bf7066d2 100644
--- a/collect-library/src/main/java/com/navinfo/collect/library/utils/GeometryTools.java
+++ b/collect-library/src/main/java/com/navinfo/collect/library/utils/GeometryTools.java
@@ -429,7 +429,6 @@ public class GeometryTools {
dList.add(lt + dis);
total += dis;
}
- Log.e("jingo", "line lengh =" + total);
total = total / 2;
for (int i = 0; i < dList.size(); i++) {
double a = dList.get(i);
@@ -495,7 +494,6 @@ public class GeometryTools {
dList.add(lt + dis);
total += dis;
}
- Log.e("jingo", "line lengh =" + total);
total = total / 2;
for (int i = 0; i < dList.size(); i++) {
double a = dList.get(i);