From dcc5f581fb3a29a6f03a313becc97aa6b34cf482 Mon Sep 17 00:00:00 2001
From: squallzhjch <zhangjingchao@navinfo.com>
Date: Mon, 3 Apr 2023 10:40:53 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A6=BB=E7=BA=BF=E5=9C=B0?=
 =?UTF-8?q?=E5=9B=BE=E4=B8=8B=E8=BD=BD=E6=B5=81=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/src/main/AndroidManifest.xml              |   2 +
 .../main/java/com/navinfo/omqs/Constant.kt    |   2 +-
 .../navinfo/omqs/bean/OfflineMapCityBean.kt   |   8 +-
 .../com/navinfo/omqs/hilt/GlobalModule.kt     |   3 +-
 .../navinfo/omqs/hilt/MainActivityModule.kt   |   8 +-
 .../omqs/http/RetrofitNetworkServiceAPI.kt    |   3 +-
 .../OfflineMapDownloadManager.kt              | 110 ++++++--
 .../OfflineMapDownloadScope.kt                | 239 +++++++++++++++++-
 .../omqs/ui/activity/login/LoginActivity.kt   |  77 +++---
 .../omqs/ui/activity/login/LoginViewModel.kt  |  14 +-
 .../omqs/ui/activity/map/MainActivity.kt      |   3 +
 .../fragment/offlinemap/OfflineMapAdapter.kt  |   1 +
 .../offlinemap/OfflineMapCityListAdapter.kt   | 107 +++++++-
 .../offlinemap/OfflineMapCityListFragment.kt  |  21 +-
 .../offlinemap/OfflineMapCityListViewModel.kt |   2 +-
 .../offlinemap/OfflineMapViewHolder.kt        |  10 +
 .../omqs/ui/other/BaseRecyclerViewAdapter.kt  |  51 +++-
 .../navinfo/omqs/ui/other/BaseViewHolder.kt   |  49 +++-
 .../navinfo/omqs/ui/widget/MyProgressBar.kt   |  20 +-
 app/src/main/res/drawable/progress_bg.xml     |  21 ++
 app/src/main/res/layout/activity_login.xml    |   3 +-
 .../res/layout/adapter_offline_map_city.xml   | 127 ++++------
 .../collect/library/utils/GeometryTools.java  |   2 -
 23 files changed, 705 insertions(+), 178 deletions(-)
 create mode 100644 app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapViewHolder.kt
 create mode 100644 app/src/main/res/drawable/progress_bg.xml

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 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <!-- 读取缓存数据 -->
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!--android:largeHeap="true" 大内存 128M -->
     <application
         android:name=".OMQSApplication"
         android:allowBackup="true"
@@ -35,6 +36,7 @@
         android:label="@string/app_name"
         android:requestLegacyExternalStorage="true"
         android:supportsRtl="true"
+        android:largeHeap="true"
         android:theme="@style/Theme.OMQualityInspection"
         tools:targetApi="31">
         <activity
diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt
index 92d50246..bc53b658 100644
--- a/app/src/main/java/com/navinfo/omqs/Constant.kt
+++ b/app/src/main/java/com/navinfo/omqs/Constant.kt
@@ -7,7 +7,7 @@ class Constant {
          */
         lateinit var ROOT_PATH: String
         lateinit var MAP_PATH: String
-
+        lateinit var OFFLINE_MAP_PATH: String
         /**
          * 服务器地址
          */
diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
index ea294e98..860173a2 100644
--- a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
+++ b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
@@ -2,11 +2,17 @@ package com.navinfo.omqs.bean
 
 data class OfflineMapCityBean(
     val id: String,
+    /**
+     * 文件名称
+     */
     val fileName: String,
+    /**
+     * 城市名称
+     */
     val name: String,
     val url: String,
     val version: Long,
-    val fileSize: Long,
+    var fileSize: Long,
     var currentSize:Long = 0,
     var status:Int = NONE
 ) {
diff --git a/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt b/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt
index 90cac2bb..ccfa89dc 100644
--- a/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt
+++ b/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt
@@ -54,7 +54,8 @@ class GlobalModule {
             }
         }.apply {
             level = if (Constant.DEBUG) {
-                HttpLoggingInterceptor.Level.BODY
+                //坑 !!!! 下载文件时打印log 内存不足
+                HttpLoggingInterceptor.Level.HEADERS
             } else {
                 HttpLoggingInterceptor.Level.NONE
             }
diff --git a/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt b/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt
index 92dd5207..5587be3e 100644
--- a/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt
+++ b/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt
@@ -2,6 +2,7 @@ package com.navinfo.omqs.hilt
 
 import android.content.Context
 import com.navinfo.collect.library.map.NIMapController
+import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
 import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
 import dagger.Module
 import dagger.Provides
@@ -26,8 +27,10 @@ class MainActivityModule {
      */
     @ActivityRetainedScoped
     @Provides
-    fun providesOfflineMapDownloadManager(@ActivityContext context: Context): OfflineMapDownloadManager =
-        OfflineMapDownloadManager(context)
+    fun providesOfflineMapDownloadManager(
+        networkServiceAPI: RetrofitNetworkServiceAPI
+    ): OfflineMapDownloadManager =
+        OfflineMapDownloadManager( networkServiceAPI)
 
     /**
      * 实验失败,这样创建,viewmodel不会在activity销毁的时候同时销毁
@@ -35,7 +38,6 @@ class MainActivityModule {
 //    @ActivityRetainedScoped
 //    @Provides
 //    fun providesMainViewModel(mapController: NIMapController): MainViewModel {
-//        Log.e("jingo", "MainViewModel 被创建")
 //        return MainViewModel(mapController)
 //    }
 
diff --git a/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt b/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt
index 5267cc8c..34fa2cdf 100644
--- a/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt
+++ b/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt
@@ -5,6 +5,7 @@ import com.navinfo.omqs.bean.OfflineMapCityBean
 import okhttp3.ResponseBody
 import retrofit2.Response
 import retrofit2.http.GET
+import retrofit2.http.Header
 import retrofit2.http.Streaming
 import retrofit2.http.Url
 import java.util.concurrent.Flow
@@ -47,7 +48,7 @@ interface RetrofitNetworkServiceAPI {
      */
     @Streaming
     @GET
-    suspend fun retrofitDownLoadFile(@Url url: String):Response<ResponseBody>
+    suspend fun retrofitDownLoadFile(@Header("RANGE") start: String? = "0", @Url url: String):Response<ResponseBody>
 
 
     /**
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<String, OfflineMapDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
         ConcurrentHashMap<String, OfflineMapDownloadScope>()
     }
 
-    val downloadFolder: String? by lazy {
-        Constant.MAP_PATH + "/offline/"
+    /**
+     * 存储正在下载的城市队列
+     */
+    private val taskScopeMap: ConcurrentHashMap<String, OfflineMapDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
+        ConcurrentHashMap<String, OfflineMapDownloadScope>()
     }
 
 
     /**
+     * 启动下载任务
+     * 请不要直接使用此方法启动下载任务,它是交由[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<OfflineMapCityBean>
+    ) {
+        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<OfflineMapCityBean>? = null
+
     /**
      *
      */
     private val downloadData = MutableLiveData<OfflineMapCityBean>()
+
+    init {
+        downloadData.value = cityBean
+    }
+
+    /**
+     * 开始任务的下载
+     * [OfflineMapCityBean]是在协程中进行创建的,它的创建会优先从数据库和本地文件获取,但这种操作是异步的,详情请看init代码块
+     * 我们需要通过观察者观察[OfflineMapCityBean]来得知它是否已经创建完成,只有当他创建完成且不为空(如果创建完成,它一定不为空)
+     * 才可以交由[OfflineMapDownloadManager]进行下载任务的启动
+     * 任务的开始可能并不是立即的,任务会受到[OfflineMapDownloadManager]的管理
+     *
+     * 这段原来代码没看懂:要触发 Observer 得观察的对象[OfflineMapCityBean]发生变化才行,原demo里没找到livedata的变化也触发了onChange,这里根本触发不了
+     *
+     * 找到原因了:是[cityBean]根本没有设置到liveData中,但是还是不用这样了,因为cityBean是一定创建好了的
+     */
+    //原代码
+//    fun start() {
+//        var observer: Observer<OfflineMapCityBean>? = 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<OfflineMapCityBean>) {
+        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<LoginStatus> {
+        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<OfflineMapCityBean>() {
 
-class OfflineMapCityListAdapter @Inject constructor() :
-    BaseRecyclerViewAdapter<OfflineMapCityBean>() {
-    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<OfflineMapCityListViewModel>()
     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<T>(var data: List<T> = listOf()) :
     RecyclerView.Adapter<BaseViewHolder>() {
-    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<T>){
+    fun refreshData(newData: List<T>) {
         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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!--设置ProgressBar背景色-->
+    <item android:id="@android:id/background">
+        <shape>
+            <!--设置ProgressBar进度条圆角半径-->
+            <corners android:radius="4dp" />
+            <solid android:color="@color/cv_gray_153" />
+        </shape>
+    </item>
+
+    <!--设置ProgressBar进度条颜色-->
+    <item android:id="@android:id/progress">
+        <clip android:clipOrientation="horizontal">
+            <shape>
+                <corners android:radius="4dp" />
+                <solid android:color="@color/default_blue" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>
\ 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 @@
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        >
+        android:layout_height="match_parent">
 
         <ImageView
             android:id="@+id/login_fragment_logo"
diff --git a/app/src/main/res/layout/adapter_offline_map_city.xml b/app/src/main/res/layout/adapter_offline_map_city.xml
index 3bad8ddd..3f9f5700 100644
--- a/app/src/main/res/layout/adapter_offline_map_city.xml
+++ b/app/src/main/res/layout/adapter_offline_map_city.xml
@@ -1,82 +1,67 @@
 <?xml version="1.0" encoding="utf-8"?>
-<layout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/cv_bg_color"
+    android:paddingLeft="10dp"
+    android:paddingRight="10dp"
+    android:paddingTop="5dp"
     tools:context="com.navinfo.omqs.ui.fragment.offlinemap.OfflineMapCityListAdapter">
 
-    <data>
-
-        <import type="com.navinfo.omqs.R" />
-
-        <variable
-            name="cityBean"
-            type="com.navinfo.omqs.bean.OfflineMapCityBean" />
-    </data>
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
+    <TextView
+        android:id="@+id/offline_map_city_name"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:padding="10dp"
-        android:background="@color/cv_bg_color">
+        android:text="省市名称"
+        android:textColor="@color/white"
+        android:textSize="@dimen/default_font_size" />
 
-        <TextView
-            android:id="@+id/offline_map_city_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@{cityBean.name}"
-            android:textColor="@color/white"
-            android:textSize="@dimen/default_font_size"
-            app:layout_constraintLeft_toLeftOf="@id/offline_map_city_size"
-            app:layout_constraintRight_toRightOf="@id/offline_map_city_size"
-            app:layout_constraintTop_toTopOf="parent" />
+    <TextView
+        android:id="@+id/offline_map_city_size"
+        style="@style/map_size_font_style"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/offline_map_city_name"
+        android:drawableLeft="@mipmap/point_blue"
+        android:layout_marginTop="5dp"
+        android:text="文件大小"
+        android:textSize="@dimen/card_title_font_3size" />
 
-        <TextView
-            android:id="@+id/offline_map_city_size"
-            style="@style/map_size_font_style"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:drawableLeft="@mipmap/point_blue"
-            android:textSize="@dimen/card_title_font_3size"
-            android:text="@{cityBean.getFileSizeText()}"
-            app:layout_constraintLeft_toLeftOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/offline_map_city_name" />
 
-        <TextView
-            android:id="@+id/tv_city_list_status"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:clickable="true"
-            android:focusable="false"
-            android:shadowColor="@android:color/transparent"
-            android:textColor="@color/white"
-            android:textSize="@dimen/card_title_font_2size"
-            app:layout_constraintBottom_toBottomOf="@id/offline_map_download_btn"
-            app:layout_constraintRight_toLeftOf="@id/offline_map_download_btn"
-            app:layout_constraintTop_toTopOf="@id/offline_map_download_btn" />
+    <TextView
+        android:id="@+id/offline_map_download_btn"
+        style="@style/map_download_style_btn"
+        android:layout_width="60dp"
+        android:layout_alignTop="@id/offline_map_city_name"
+        android:layout_alignBottom="@id/offline_map_city_size"
+        android:layout_alignParentRight="true"
+        android:shadowColor="@android:color/transparent"
+        android:text="下载"
+        android:textColor="@color/btn_blue_solid"
+        android:textSize="@dimen/card_title_font_2size" />
 
-        <TextView
-            android:id="@+id/offline_map_download_btn"
-            style="@style/map_download_style_btn"
-            android:layout_alignParentRight="true"
-            android:layout_centerVertical="true"
-            android:shadowColor="@android:color/transparent"
-            android:text="下载"
-            android:textColor="@color/btn_blue_solid"
-            android:textSize="@dimen/card_title_font_2size"
-            app:layout_constraintRight_toRightOf="parent"
-            app:layout_constraintBottom_toTopOf="@id/offline_map_progress"
-            app:layout_constraintTop_toTopOf="parent" />
+    <TextView
+        android:id="@+id/tv_city_list_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginBottom="10dp"
+        android:layout_toLeftOf="@id/offline_map_download_btn"
+        android:clickable="true"
+        android:focusable="false"
+        android:shadowColor="@android:color/transparent"
+        android:textColor="@color/white"
+        android:textSize="@dimen/card_title_font_2size" />
 
-        <com.navinfo.omqs.ui.widget.MyProgressBar
-            android:layout_marginTop="5dp"
-            android:visibility="gone"
-            android:id="@+id/offline_map_progress"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:max="100"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintLeft_toLeftOf="parent" />
-    </androidx.constraintlayout.widget.ConstraintLayout>
-</layout>
\ No newline at end of file
+    <com.navinfo.omqs.ui.widget.MyProgressBar
+        android:id="@+id/offline_map_progress"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="16dp"
+        android:layout_below="@id/offline_map_download_btn"
+        android:progressDrawable="@drawable/progress_bg"
+        android:paddingTop="10dp"
+        android:visibility="invisible" />
+</RelativeLayout>
\ 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);