diff --git a/app/build.gradle b/app/build.gradle
index 69bf5cd0..a6212aa7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,8 +3,8 @@ plugins {
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
+ id 'realm-android'
}
-
android {
namespace 'com.navinfo.omqs'
compileSdk 33
@@ -26,8 +26,8 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '1.8'
@@ -50,8 +50,12 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- //权限管理
- implementation 'com.github.getActivity:XXPermissions:16.5'
+ //权限管理 https://github.com/getActivity/XXPermissions
+ implementation 'com.github.getActivity:XXPermissions:16.8'
+ // 文件管理 https://github.com/K1rakishou/Fuck-Storage-Access-Framework
+ implementation 'com.github.K1rakishou:Fuck-Storage-Access-Framework:v1.1.3'
+ // Android工具类库 https://blankj.com/2016/07/31/android-utils-code/
+ implementation 'com.blankj:utilcodex:1.30.1'
//依赖注入
//hilt
implementation "com.google.dagger:hilt-android:2.44"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index db80cb12..99b60c14 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,6 +28,7 @@
+
+ android:requestLegacyExternalStorage="true">
+
\ No newline at end of file
diff --git a/app/src/main/assets/MergeOMDB.py b/app/src/main/assets/MergeOMDB.py
new file mode 100644
index 00000000..401524b1
--- /dev/null
+++ b/app/src/main/assets/MergeOMDB.py
@@ -0,0 +1,95 @@
+# coding:utf-8
+# 合并指定目录下的omdb(sqlite)数据
+
+import os
+import sys
+import json
+import sqlite3
+
+
+# 定义遍历目录的函数
+def traverse_dir(path):
+ fileList = list()
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ if str(file).endswith(".omdb"):
+ # 文件的完整路径
+ file_path = os.path.join(root, file)
+ # 处理文件,例如读取文件内容等
+ print(file_path)
+ fileList.append(file_path)
+ return fileList
+
+
+# 打开配置文件,读取用户配置的
+def openConfigJson(path):
+ # 读取json配置,获取要抽取的表名
+ with open(path, "r") as f:
+ configMap = json.load(f)
+ return configMap
+
+
+# 按照tableList中指定的表名合并多个源数据库到指定目标数据库中
+def mergeSqliteData(originSqliteList, destSqlite, tableList):
+ destConn = sqlite3.connect(destSqlite)
+ destCursor = destConn.cursor()
+
+ for originSqlite in originSqliteList:
+ originConn = sqlite3.connect(originSqlite)
+ originCursor = originConn.cursor()
+ # 从源数据库中遍历取出表list中的数据
+ for table in tableList:
+ # 检查目标数据库中是否存在指定的表
+ containsTable = destCursor.execute(
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'" % (table)).fetchall()
+ if not containsTable or len(containsTable) <= 0:
+ # 复制表结构
+ originCursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'" % (table))
+ createTableSql = originCursor.fetchone()[0]
+ destCursor.execute(createTableSql)
+ destConn.commit()
+
+ originCursor.execute("Select * From " + table)
+ # 获取到源数据库中该表的所有数据
+ originData = originCursor.fetchall()
+ # 获取一行数据中包含多少列,以此动态设置sql语句中的?个数
+ if originData and len(originData)>0:
+ num_cols = len(originData[0])
+ placeholders = ",".join(["?"] * num_cols)
+ for row in originData:
+ destCursor.execute("INSERT INTO "+table+" VALUES ({})".format(placeholders), row)
+
+ print("{}数据已导入!".format(originSqlite))
+ originCursor.close()
+ originConn.close()
+ destConn.commit()
+ destCursor.close()
+ destConn.close()
+
+
+if __name__ == '__main__':
+ params = sys.argv[1:] # 截取参数
+ if params:
+ if not params[0]:
+ print("请输入要合并的omdb数据的文件夹")
+ raise AttributeError("请输入要合并的omdb数据的文件夹")
+ # 获取导出文件的表配置
+ jsonPath = params[0] + "/config.json"
+ if not os.path.exists(jsonPath):
+ raise AttributeError("指定目录下缺少config.json配置文件")
+ omdbDir = params[0]
+ originSqliteList = traverse_dir(omdbDir) # 获取到所有的omdb数据库的路径
+
+ tableNameList = list()
+ configMap = openConfigJson(jsonPath)
+ if configMap["tables"] and len(configMap["tables"]) > 0:
+ for tableName in set(configMap["tables"]):
+ tableNameList.append(tableName)
+ print(tableNameList)
+ else:
+ raise AttributeError("config.json文件中没有配置抽取数据的表名")
+
+ # 开始分别连接Sqlite数据库,按照指定表名合并数据
+ mergeSqliteData(originSqliteList, params[0]+"/output.sqlite", tableNameList)
+ else:
+ raise AttributeError("缺少参数:请输入要合并的omdb数据的文件夹")
diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt
index 92d50246..306ff4e3 100644
--- a/app/src/main/java/com/navinfo/omqs/Constant.kt
+++ b/app/src/main/java/com/navinfo/omqs/Constant.kt
@@ -1,13 +1,29 @@
package com.navinfo.omqs
+import io.realm.Realm
+
class Constant {
companion object {
/**
* sd卡根目录
*/
lateinit var ROOT_PATH: String
+
+ /**
+ * 地图目录
+ */
lateinit var MAP_PATH: String
+ /**
+ * 数据目录
+ */
+ lateinit var DATA_PATH: String
+
+ /**
+ * 离线地图目录
+ */
+ lateinit var OFFLINE_MAP_PATH: String
+
/**
* 服务器地址
*/
@@ -20,7 +36,7 @@ class Constant {
const val message_version_right_off = "1" //立即发送
const val MESSAGE_PAGE_SIZE = 30 //消息列表一页最多数量
-
+ lateinit var realm: Realm
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
index 7e85a078..27e26fa3 100644
--- a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
+++ b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
@@ -1,8 +1,18 @@
package com.navinfo.omqs
import android.app.Application
+import android.util.Log
+import com.navinfo.omqs.tools.FileManager
import dagger.hilt.android.HiltAndroidApp
+import io.realm.Realm
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import java.io.File
@HiltAndroidApp
class OMQSApplication : Application() {
+ override fun onCreate() {
+ FileManager.initRootDir(this)
+ super.onCreate()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
deleted file mode 100644
index ea294e98..00000000
--- a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-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 currentSize:Long = 0,
- var status:Int = NONE
-) {
- companion object Status{
- const val NONE = 0 //无状态
- const val WAITING = 1 //等待中
- const val LOADING = 2 //下载中
- const val PAUSE = 3 //暂停
- const val ERROR = 4 //错误
- const val DONE = 5 //完成
- const val UPDATE = 6 //有新版本要更新
- }
- fun getFileSizeText(): String {
- return if (fileSize < 1024.0)
- "$fileSize B"
- else if (fileSize < 1048576.0)
- "%.2f K".format(fileSize / 1024.0)
- else if (fileSize < 1073741824.0)
- "%.2f M".format(fileSize / 1048576.0)
- else
- "%.2f M".format(fileSize / 1073741824.0)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt b/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt
new file mode 100644
index 00000000..581343c3
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt
@@ -0,0 +1,8 @@
+package com.navinfo.omqs.data.process
+
+/**
+ * 数据处理引擎,数据导入、导出及转换处理
+ * */
+class DataEngine {
+
+}
\ No newline at end of file
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..4ace2b64 100644
--- a/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt
+++ b/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt
@@ -6,11 +6,13 @@ import com.google.gson.Gson
import com.navinfo.omqs.Constant
import com.navinfo.omqs.OMQSApplication
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
+import com.navinfo.omqs.tools.RealmCoroutineScope
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.*
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
@@ -25,11 +27,11 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
class GlobalModule {
-// @Singleton
-// @Provides
-// fun provideApplication(application: Application): OMQSApplication {
-// return application as OMQSApplication
-// }
+ @Singleton
+ @Provides
+ fun provideApplication(application: Application): OMQSApplication {
+ return application as OMQSApplication
+ }
/**
* 注入 网络OKHttp 对象
@@ -54,7 +56,8 @@ class GlobalModule {
}
}.apply {
level = if (Constant.DEBUG) {
- HttpLoggingInterceptor.Level.BODY
+ //坑 !!!! 下载文件时打印log 内存不足
+ HttpLoggingInterceptor.Level.HEADERS
} else {
HttpLoggingInterceptor.Level.NONE
}
@@ -91,4 +94,13 @@ class GlobalModule {
fun provideNetworkService(retrofit: Retrofit): RetrofitNetworkServiceAPI {
return retrofit.create(RetrofitNetworkServiceAPI::class.java)
}
+
+ /**
+ * realm 注册
+ */
+ @Provides
+ @Singleton
+ fun provideRealmService(context: Application): RealmCoroutineScope {
+ return RealmCoroutineScope(context)
+ }
}
\ No newline at end of file
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..8560c38b 100644
--- a/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt
+++ b/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt
@@ -1,13 +1,13 @@
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 com.navinfo.omqs.tools.RealmCoroutineScope
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent
-import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityRetainedScoped
@InstallIn(ActivityRetainedComponent::class)
@@ -26,8 +26,11 @@ class MainActivityModule {
*/
@ActivityRetainedScoped
@Provides
- fun providesOfflineMapDownloadManager(@ActivityContext context: Context): OfflineMapDownloadManager =
- OfflineMapDownloadManager(context)
+ fun providesOfflineMapDownloadManager(
+ networkServiceAPI: RetrofitNetworkServiceAPI,
+ realmManager: RealmCoroutineScope
+ ): OfflineMapDownloadManager =
+ OfflineMapDownloadManager(networkServiceAPI, realmManager)
/**
* 实验失败,这样创建,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/NetworkService.kt b/app/src/main/java/com/navinfo/omqs/http/NetworkService.kt
index 8ed34846..eb4a6103 100644
--- a/app/src/main/java/com/navinfo/omqs/http/NetworkService.kt
+++ b/app/src/main/java/com/navinfo/omqs/http/NetworkService.kt
@@ -1,6 +1,7 @@
package com.navinfo.omqs.http
-import com.navinfo.omqs.bean.OfflineMapCityBean
+import com.navinfo.collect.library.data.entity.OfflineMapCityBean
+
/**
* 网络访问 业务接口
diff --git a/app/src/main/java/com/navinfo/omqs/http/NetworkServiceImpl.kt b/app/src/main/java/com/navinfo/omqs/http/NetworkServiceImpl.kt
index 4a67bf83..bbcafc3e 100644
--- a/app/src/main/java/com/navinfo/omqs/http/NetworkServiceImpl.kt
+++ b/app/src/main/java/com/navinfo/omqs/http/NetworkServiceImpl.kt
@@ -1,6 +1,6 @@
package com.navinfo.omqs.http
-import com.navinfo.omqs.bean.OfflineMapCityBean
+import com.navinfo.collect.library.data.entity.OfflineMapCityBean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
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..4bea1858 100644
--- a/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt
+++ b/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt
@@ -1,13 +1,12 @@
package com.navinfo.omqs.http
-import androidx.lifecycle.LiveData
-import com.navinfo.omqs.bean.OfflineMapCityBean
+import com.navinfo.collect.library.data.entity.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
/**
* retrofit2 网络请求接口
@@ -47,7 +46,7 @@ interface RetrofitNetworkServiceAPI {
*/
@Streaming
@GET
- suspend fun retrofitDownLoadFile(@Url url: String):Response
+ 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..e4aa57e1 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,114 @@
package com.navinfo.omqs.http.offlinemapdownload
-import android.content.Context
-import android.os.Environment
-import android.text.TextUtils
-import com.navinfo.omqs.Constant
-import com.navinfo.omqs.bean.OfflineMapCityBean
-import dagger.hilt.android.qualifiers.ActivityContext
-import java.io.Serializable
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.Observer
+import com.navinfo.collect.library.data.entity.OfflineMapCityBean
+import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
+import com.navinfo.omqs.tools.RealmCoroutineScope
import java.util.concurrent.ConcurrentHashMap
-import javax.inject.Inject
/**
* 管理离线地图下载
*/
-class OfflineMapDownloadManager @Inject constructor(@ActivityContext context: Context) {
+class OfflineMapDownloadManager(
+ val netApi: RetrofitNetworkServiceAPI, val realmManager: RealmCoroutineScope
+) {
/**
* 最多同时下载数量
*/
- 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 addTask(cityBean: OfflineMapCityBean) {
+ if (!scopeMap.containsKey(cityBean.id)) {
+ scopeMap[cityBean.id] = OfflineMapDownloadScope(this, cityBean)
+ }
+ }
+
+
+ fun observer(
+ id: String, lifecycleOwner: LifecycleOwner, observer: Observer
+ ) {
+ if (scopeMap.containsKey(id)) {
+ scopeMap[id]!!.observer(lifecycleOwner, observer)
+ }
+ }
+
+ fun removeObserver(id: String) {
+ if (scopeMap.containsKey(id)) {
+ scopeMap[id]!!.removeObserver()
+ }
+ }
+
+
}
\ 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..0821f8c0 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,176 @@
package com.navinfo.omqs.http.offlinemapdownload
+import android.util.Log
+import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
-import com.navinfo.omqs.bean.OfflineMapCityBean
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlin.coroutines.EmptyCoroutineContext
+import androidx.lifecycle.Observer
+import com.navinfo.collect.library.data.entity.OfflineMapCityBean
+import com.navinfo.omqs.Constant
+import kotlinx.coroutines.*
+import java.io.File
+import java.io.IOException
+import java.io.InputStream
+import java.io.RandomAccessFile
/**
* 代表一个下载任务
* [OfflineMapCityBean.id]将做为下载任务的唯一标识
- * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.download]获取此对象
+ * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.request]获取此对象
* 这是一个协程作用域,
* EmptyCoroutineContext 表示一个不包含任何元素的协程上下文,它通常用于创建新的协程上下文,或者作为协程上下文的基础。
*/
-class OfflineMapDownloadScope(cityBean: OfflineMapCityBean) : CoroutineScope by CoroutineScope(EmptyCoroutineContext) {
+class OfflineMapDownloadScope(
+ private val downloadManager: OfflineMapDownloadManager,
+ val cityBean: OfflineMapCityBean,
+) :
+ CoroutineScope by CoroutineScope(Dispatchers.IO) {
/**
- *
+ *下载任务,用来取消的
*/
private var downloadJob: Job? = null
/**
- *
+ * 管理观察者,同时只有一个就行了
+ */
+// private var observer: Observer? = null
+ private var lifecycleOwner: LifecycleOwner? = null
+
+ /**
+ *通知UI更新
*/
private val downloadData = MutableLiveData()
+
+ init {
+ downloadData.value = cityBean
+ }
+
+ //改进的代码
+ fun start() {
+ change(OfflineMapCityBean.WAITING)
+ downloadManager.launchScope(this@OfflineMapDownloadScope)
+ }
+
+ /**
+ * 暂停任务
+ * 其实就是取消任务,移除监听
+ */
+ fun pause() {
+ downloadJob?.cancel("pause")
+ change(OfflineMapCityBean.PAUSE)
+ }
+
+ /**
+ * 启动协程进行下载
+ * 请不要尝试在外部调用此方法,那样会脱离[OfflineMapDownloadManager]的管理
+ */
+ fun launch() {
+ downloadJob = launch() {
+ Log.e("jingo", "启动下载1")
+ download()
+ Log.e("jingo", "启动下载2")
+ downloadManager.launchNext(cityBean.id)
+ Log.e("jingo", "启动下载3")
+ }
+ }
+
+
+ /**
+ * 是否是等待任务
+ */
+ fun isWaiting(): Boolean {
+ return cityBean.status == OfflineMapCityBean.WAITING
+ }
+
+ /**
+ * 更新任务
+ * @param status [OfflineMapCityBean.Status]
+ */
+ private fun change(status: Int) {
+ if (cityBean.status != status || status == OfflineMapCityBean.LOADING) {
+ cityBean.status = status
+ downloadData.postValue(cityBean)
+
+ downloadManager.realmManager.launch {
+ downloadManager.realmManager.insertOrUpdate(cityBean)
+ }
+ }
+ }
+
+ /**
+ * 添加下载任务观察者
+ */
+ fun observer(owner: LifecycleOwner, ob: Observer) {
+ 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/${cityBean.id}_${cityBean.version}")
+ val startPosition = cityBean.currentSize
+ //验证断点有效性
+ if (startPosition < 0) throw IOException("jingo Start position less than zero")
+ val response = downloadManager.netApi.retrofitDownLoadFile(
+ start = "bytes=$startPosition-",
+ url = cityBean.url
+ )
+ val responseBody = response.body()
+ change(OfflineMapCityBean.LOADING)
+ responseBody ?: throw IOException("jingo ResponseBody is null")
+ //写入文件
+ randomAccessFile = RandomAccessFile(fileTemp, "rwd")
+ randomAccessFile.seek(startPosition)
+ cityBean.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)
+ cityBean.currentSize += readLength
+ change(OfflineMapCityBean.LOADING)
+ } else {
+ break
+ }
+ }
+
+ Log.e("jingo", "文件下载完成 ${cityBean.currentSize} == ${cityBean.fileSize}")
+ if (cityBean.currentSize == cityBean.fileSize) {
+ val res =
+ fileTemp.renameTo(File("${Constant.OFFLINE_MAP_PATH}${cityBean.fileName}"))
+ Log.e("jingo", "文件下载完成 修改文件 $res")
+ change(OfflineMapCityBean.DONE)
+ } else {
+ change(OfflineMapCityBean.PAUSE)
+ }
+ } catch (e: Throwable) {
+ change(OfflineMapCityBean.ERROR)
+ } finally {
+ inputStream?.close()
+ randomAccessFile?.close()
+ }
+ }
+
+ fun removeObserver() {
+ lifecycleOwner?.let {
+ downloadData.removeObservers(it)
+ null
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/tools/FileManager.kt b/app/src/main/java/com/navinfo/omqs/tools/FileManager.kt
new file mode 100644
index 00000000..e40972ee
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/tools/FileManager.kt
@@ -0,0 +1,95 @@
+package com.navinfo.omqs.tools
+
+import android.content.Context
+import android.util.Log
+import com.navinfo.collect.library.data.entity.OfflineMapCityBean
+import com.navinfo.omqs.Constant
+import java.io.File
+
+class FileManager {
+ companion object {
+ //初始化数据文件夹
+ fun initRootDir(context:Context){
+ // 在SD卡创建项目目录
+ val sdCardPath = context.getExternalFilesDir(null)
+ 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()
+ Constant.DATA_PATH = Constant.ROOT_PATH + "/data/"
+ with(File(Constant.MAP_PATH)) {
+ if (!this.exists()) this.mkdirs()
+ }
+ with(File(Constant.DATA_PATH)) {
+ if (!this.exists()) this.mkdirs()
+ }
+ }else{
+ Constant.DATA_PATH = Constant.ROOT_PATH + "/data/"
+ }
+ }
+ }
+
+ /**
+ * 检查离线地图文件
+ */
+ suspend fun checkOfflineMapFileInfo(cityBean: OfflineMapCityBean) {
+ //访问离线地图文件夹
+ val fileDir = File("${Constant.OFFLINE_MAP_PATH}")
+ //如果连本地文件夹还没有,就不用修改任何数据了
+ if (!fileDir.exists()) {
+ return
+ }
+ //访问离线地图临时下载文件夹
+ val fileTempDir = File("${Constant.OFFLINE_MAP_PATH}download/")
+ //是否有一份.map文件了
+ var mapFile: File? = null
+ //文件夹里文件挨个访问
+ for (item in fileDir.listFiles()) {
+ //先找到对应的省市文件,例如:540000_西藏自治区_20230401195018.map",以id开头
+ if (item.isFile && item.name.startsWith(cityBean.id)) {
+ //如果本地文件与从网络获取到版本号一致,表示这个文件已经下载完毕,不用处理了
+ if (item.name.contains("_${cityBean.version}.map")) {
+ cityBean.status = OfflineMapCityBean.DONE
+ return
+ }
+ //文件存在,版本号不对应,留给下面流程处理
+ mapFile = item
+ break
+ }
+ }
+ //临时下载文件夹
+ if (fileTempDir.exists()) {
+ for (item in fileTempDir.listFiles()) {
+ //先找到对应的省市文件,例如:540000_20230401195018",以id开头
+ if (item.isFile && item.name.startsWith(cityBean.id)) {
+ //如果本地文件与从网络获取到版本号一致,表示这个文件已经在下载列表中
+ if (item.name == "${cityBean.id}_${cityBean.version}") {
+ //如果这个临时文件的大小和下载大小是一致的,说明已经下载完了,但是在下载环节没有更名移动成功,需要重命名和移动文件夹
+ if (item.length() == cityBean.fileSize) {
+ //移动更名文件后删除旧数据,修改状态
+ if (item.renameTo(File("${Constant.OFFLINE_MAP_PATH}${cityBean.fileName}"))) {
+ //删除旧版本数据
+ mapFile?.delete()
+ cityBean.status = OfflineMapCityBean.DONE
+ return
+ }
+ } else { // 临时文件大小和目标不一致,说明下载了一半
+ cityBean.status = OfflineMapCityBean.PAUSE
+ cityBean.currentSize = item.length()
+ return
+ }
+ } else { //虽然省市id开头一致,但是版本号不一致,说明之前版本下载了一部分,现在要更新了,原来下载的文件直接删除
+ cityBean.status = OfflineMapCityBean.UPDATE
+ item.delete()
+ return
+ }
+ break
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/tools/RealmCoroutineScope.kt b/app/src/main/java/com/navinfo/omqs/tools/RealmCoroutineScope.kt
new file mode 100644
index 00000000..70880896
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/tools/RealmCoroutineScope.kt
@@ -0,0 +1,57 @@
+package com.navinfo.omqs.tools
+
+import android.app.Application
+import com.navinfo.collect.library.data.entity.OfflineMapCityBean
+import com.navinfo.omqs.Constant
+import io.realm.Realm
+import io.realm.RealmConfiguration
+import io.realm.RealmModel
+import io.realm.Sort
+import io.realm.kotlin.where
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.newSingleThreadContext
+import java.io.File
+
+class RealmCoroutineScope(context: Application) :
+ CoroutineScope by CoroutineScope(newSingleThreadContext("RealmThread")) {
+ lateinit var realm: Realm
+
+ init {
+ launch {
+ Realm.init(context)
+ val password = "password".encodeToByteArray().copyInto(ByteArray(64))
+ // 1110000011000010111001101110011011101110110111101110010011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+// Log.d("", "密码是: ${BigInteger(1, password).toString(2).padStart(64, '0')}")
+ val config = RealmConfiguration.Builder()
+ .directory(File(Constant.DATA_PATH))
+ .name("HDData")
+// .encryptionKey(password)
+ .build()
+ Realm.setDefaultConfiguration(config)
+ realm = Realm.getDefaultInstance()
+ }
+ }
+
+ suspend fun getOfflineCityList(): List {
+ var list: List = mutableListOf()
+ realm.executeTransaction {
+ val objects = realm.where().findAll().sort("id", Sort.ASCENDING)
+ list = realm.copyFromRealm(objects)
+ }
+ return list
+ }
+
+ suspend fun insertOrUpdate(objects: Collection?) {
+ realm.executeTransaction {
+ realm.insertOrUpdate(objects)
+ }
+ }
+
+ suspend fun insertOrUpdate(realmModel: RealmModel?) {
+ realm.executeTransaction {
+ realm.insertOrUpdate(realmModel)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt
new file mode 100644
index 00000000..6bad71e2
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt
@@ -0,0 +1,84 @@
+package com.navinfo.omqs.ui
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.core.view.WindowCompat
+import androidx.navigation.ui.AppBarConfiguration
+import com.github.k1rakishou.fsaf.FileChooser
+import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks
+import com.navinfo.omqs.databinding.ActivityMainBinding
+import com.navinfo.omqs.ui.activity.PermissionsActivity
+
+class MainActivity : PermissionsActivity(), FSAFActivityCallbacks {
+
+ private lateinit var appBarConfiguration: AppBarConfiguration
+ private lateinit var binding: ActivityMainBinding
+ private val fileChooser by lazy { FileChooser(this@MainActivity) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+// val navController = findNavController(R.id.nav_host_fragment_content_main)
+// appBarConfiguration = AppBarConfiguration(navController.graph)
+// setupActionBarWithNavController(navController, appBarConfiguration)
+
+ fileChooser.setCallbacks(this@MainActivity)
+// binding.fab.setOnClickListener { view ->
+// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+// .setAnchorView(R.id.fab)
+// .setAction("Action", null).show()
+// // 开始数据导入功能
+// fileChooser.openChooseFileDialog(object: FileChooserCallback() {
+// override fun onCancel(reason: String) {
+// }
+//
+// override fun onResult(uri: Uri) {
+// val file = UriUtils.uri2File(uri)
+// Snackbar.make(view, "文件大小为:${file.length()}", Snackbar.LENGTH_LONG)
+// .show()
+// }
+// })
+// }
+ }
+
+ override fun onPermissionsGranted() {
+ }
+
+ override fun onPermissionsDenied() {
+ }
+
+// override fun onCreateOptionsMenu(menu: Menu): Boolean {
+// // Inflate the menu; this adds items to the action bar if it is present.
+// menuInflater.inflate(R.menu.menu_main, menu)
+// return true
+// }
+
+// override fun onOptionsItemSelected(item: MenuItem): Boolean {
+// // Handle action bar item clicks here. The action bar will
+// // automatically handle clicks on the Home/Up button, so long
+// // as you specify a parent activity in AndroidManifest.xml.
+// return when (item.itemId) {
+// R.id.action_settings -> true
+// else -> super.onOptionsItemSelected(item)
+// }
+// }
+//
+// override fun onSupportNavigateUp(): Boolean {
+// val navController = findNavController(R.id.nav_host_fragment_content_main)
+// return navController.navigateUp(appBarConfiguration)
+// || super.onSupportNavigateUp()
+// }
+
+ override fun fsafStartActivityForResult(intent: Intent, requestCode: Int) {
+ startActivityForResult(intent, requestCode)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ fileChooser.onActivityResult(requestCode, resultCode, data)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
index 94b8ab90..cb8bb99c 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
@@ -16,9 +16,10 @@ open class PermissionsActivity : BaseActivity() {
val permissionList = mutableListOf()
if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU) {
//文件读写
- permissionList.add(Permission.READ_MEDIA_IMAGES)
- permissionList.add(Permission.READ_MEDIA_AUDIO)
- permissionList.add(Permission.READ_MEDIA_VIDEO)
+// permissionList.add(Permission.READ_MEDIA_IMAGES)
+// permissionList.add(Permission.READ_MEDIA_AUDIO)
+// permissionList.add(Permission.READ_MEDIA_VIDEO)
+ permissionList.add(Permission.MANAGE_EXTERNAL_STORAGE)
} else {
//文件读写
permissionList.add(Permission.WRITE_EXTERNAL_STORAGE)
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..061fe8dc 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,15 +7,19 @@ 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
import com.navinfo.omqs.ui.activity.PermissionsActivity
import com.navinfo.omqs.ui.activity.map.MainActivity
+import dagger.hilt.android.AndroidEntryPoint
/**
* 登陆页面
*/
+
+@AndroidEntryPoint
class LoginActivity : PermissionsActivity() {
private lateinit var binding: ActivityLoginBinding
@@ -30,41 +34,52 @@ 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
+ }
+ LoginStatus.LOGIN_STATUS_NET_OFFLINE_MAP -> {
+ loginDialog("检查离线地图...")
}
}
}
+ private fun initView() {
+ //登录校验,初始化成功
+ viewModel.loginStatus.observe(this, loginObserve)
+
+ }
+
/**
* 登录dialog
*/
@@ -73,7 +88,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 +99,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..76496b0d 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
@@ -7,11 +7,15 @@ import android.widget.Toast
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.LoginUserBean
+import com.navinfo.omqs.http.NetResult
+import com.navinfo.omqs.http.NetworkService
+import com.navinfo.omqs.tools.FileManager
+import com.navinfo.omqs.tools.RealmCoroutineScope
+import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import okio.IOException
-import java.io.File
+import javax.inject.Inject
enum class LoginStatus {
/**
@@ -19,6 +23,11 @@ enum class LoginStatus {
*/
LOGIN_STATUS_NET_LOADING,
+ /**
+ * 访问离线地图列表
+ */
+ LOGIN_STATUS_NET_OFFLINE_MAP,
+
/**
* 初始化文件夹
*/
@@ -45,7 +54,10 @@ enum class LoginStatus {
LOGIN_STATUS_CANCEL,
}
-class LoginViewModel(
+@HiltViewModel
+class LoginViewModel @Inject constructor(
+ private val networkService: NetworkService,
+ private val realmManager: RealmCoroutineScope
) : ViewModel() {
//用户信息
val loginUser: MutableLiveData = MutableLiveData()
@@ -65,7 +77,7 @@ class LoginViewModel(
*/
fun onClick(view: View) {
loginUser.value!!.username = "admin2"
- loginUser.postValue(loginUser.value)
+ loginUser.value = loginUser.value
}
/**
@@ -81,7 +93,6 @@ class LoginViewModel(
//不指定IO,会在主线程里运行
jobLogin = viewModelScope.launch(Dispatchers.IO) {
loginCheck(context, userName, password)
- Log.e("jingo", "运行完了1?${Thread.currentThread().name}")
}
}
@@ -90,52 +101,68 @@ 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)
- createRootFolder(context)
+ createUserFolder(context)
+ // 初始化Realm
} catch (e: IOException) {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_FAILURE)
}
+
//假装解压文件等
delay(1000)
- loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS)
- Log.e("jingo", "delay之后?${Thread.currentThread().name}")
+ loginStatus.postValue(LoginStatus.LOGIN_STATUS_NET_OFFLINE_MAP)
+ when (val result = networkService.getOfflineMapCityList()) {
+ is NetResult.Success -> {
-// }
+ if (result.data != null) {
+ for (cityBean in result.data) {
+ FileManager.checkOfflineMapFileInfo(cityBean)
+ }
+ realmManager.launch {
+ realmManager.insertOrUpdate(result.data)
+ }
+ }
+ }
+ is NetResult.Error -> {
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+ is NetResult.Failure -> {
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, "${result.code}:${result.msg}", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+ NetResult.Loading -> {}
+ }
+ loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS)
}
-
+ /**
+ * 创建用户目录
+ */
@Throws(IOException::class)
- private fun createRootFolder(context: Context) {
- // 在SD卡创建项目目录
- val sdCardPath = context.getExternalFilesDir(null)
- sdCardPath?.let {
- Constant.ROOT_PATH = sdCardPath.absolutePath
- Constant.MAP_PATH = Constant.ROOT_PATH + "/map/"
- val file = File(Constant.MAP_PATH)
- if (!file.exists()) {
- file.mkdirs()
- }
- }
+ private fun createUserFolder(context: Context) {
+ // 在SD卡创建用户目录,解压资源等
}
/**
* 取消登录
*/
fun cancelLogin() {
- Log.e("jingo", "取消了?${Thread.currentThread().name}")
jobLogin?.let {
it.cancel()
- loginStatus.postValue(LoginStatus.LOGIN_STATUS_CANCEL)
+ loginStatus.value = LoginStatus.LOGIN_STATUS_CANCEL
}
}
@@ -143,6 +170,4 @@ class LoginViewModel(
super.onCleared()
cancelLogin()
}
-
-
}
\ No newline at end of file
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..4126123b 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)
@@ -66,7 +69,6 @@ class MainActivity : BaseActivity() {
super.onDestroy()
mapController.mMapView.onDestroy()
mapController.locationLayerHandler.stopLocation()
- Log.e("jingo", "MainActivity 销毁")
}
override fun onResume() {
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt
index 722baf7a..c052a0f7 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt
@@ -25,7 +25,6 @@ class MainViewModel @Inject constructor(
}
override fun onCleared() {
- Log.e("jingo","MainViewModel 被释放了")
super.onCleared()
}
}
\ No newline at end of file
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..d27594bd 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,134 @@
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.collect.library.data.entity.OfflineMapCityBean
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 -> {
+ Log.e("jingo", "开始下载 ${cityBean.status}")
+ downloadManager.start(cityBean.id)
+ }
+ OfflineMapCityBean.LOADING, OfflineMapCityBean.WAITING -> {
+ Log.e("jingo", "暂停 ${cityBean.status}")
+ downloadManager.pause(cityBean.id)
+ }
+ else -> {
+ Log.e("jingo", "暂停 ${cityBean.status}")
+ }
+ }
}
+ }
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
+ val viewBinding =
+ AdapterOfflineMapCityBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return BaseViewHolder(viewBinding)
+ }
+
+ override fun onViewRecycled(holder: BaseViewHolder) {
+ super.onViewRecycled(holder)
+ //页面滑动时会用holder重构页面,但是对进度条的监听回调会一直返回,扰乱UI,所以当当前holder去重构的时候,移除监听
+ downloadManager.removeObserver(holder.tag)
+ }
+
+ override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
+ val binding: AdapterOfflineMapCityBinding =
+ holder.viewBinding as AdapterOfflineMapCityBinding
+ //牺牲性能立刻刷新UI,解决闪烁 这里不用
+// binding.executePendingBindings()
+ val cityBean = data[position]
+ //tag 方便onclick里拿到数据
+ holder.tag = cityBean.id
+ changeViews(binding, cityBean)
+ downloadManager.addTask(cityBean)
+ downloadManager.observer(cityBean.id, holder, DownloadObserver(cityBean.id, binding))
+ binding.offlineMapDownloadBtn.tag = position
+ binding.offlineMapDownloadBtn.setOnClickListener(downloadBtnClick)
+ binding.offlineMapCityName.text = cityBean.name
+ binding.offlineMapCitySize.text = cityBean.getFileSizeText()
+ }
+
+ inner class DownloadObserver(val id: String, val binding: AdapterOfflineMapCityBinding) :
+ Observer {
+ override fun onChanged(t: OfflineMapCityBean?) {
+ if (id == t?.id)
+ changeViews(binding, t)
+ }
+ }
+
+
+ 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..5d64468d 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,5 @@ class OfflineMapCityListFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
_binding = null
- 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..1dbd04f0 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
@@ -1,16 +1,18 @@
package com.navinfo.omqs.ui.fragment.offlinemap
-import android.app.Application
import android.content.Context
-import android.widget.Toast
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.navinfo.omqs.http.NetResult
-import com.navinfo.omqs.http.NetworkService
-import com.navinfo.omqs.bean.OfflineMapCityBean
+import com.navinfo.collect.library.data.entity.OfflineMapCityBean
+import com.navinfo.omqs.tools.FileManager
+import com.navinfo.omqs.tools.RealmCoroutineScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
+import io.realm.Realm
+import io.realm.Sort
+import io.realm.kotlin.where
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -19,8 +21,7 @@ import javax.inject.Inject
*/
@HiltViewModel
class OfflineMapCityListViewModel @Inject constructor(
- private val networkService: NetworkService,
- @ApplicationContext val context: Context
+ @ApplicationContext val context: Context,
) : ViewModel() {
val cityListLiveData = MutableLiveData>()
@@ -29,21 +30,15 @@ class OfflineMapCityListViewModel @Inject constructor(
* 去获取离线地图列表
*/
fun getCityList() {
- viewModelScope.launch {
- when (val result = networkService.getOfflineMapCityList()) {
- is NetResult.Success -> {
- cityListLiveData.postValue(result.data!!)
- }
- is NetResult.Error -> {
- Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT)
- .show()
- }
- is NetResult.Failure -> {
- Toast.makeText(context, "${result.code}:${result.msg}", Toast.LENGTH_SHORT)
- .show()
- }
- NetResult.Loading -> {}
+ viewModelScope.launch(Dispatchers.IO) {
+ val realm = Realm.getDefaultInstance()
+ val objects = realm.where().findAll().sort("id", Sort.ASCENDING)
+ val list = realm.copyFromRealm(objects)
+ realm.close()
+ for (item in list) {
+ FileManager.checkOfflineMapFileInfo(item)
}
+ cityListLiveData.postValue(list)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapFragment.kt
index ee60b969..8f9da1e1 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapFragment.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapFragment.kt
@@ -61,6 +61,5 @@ class OfflineMapFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
_binding = null
- Log.e("jingo","OfflineMapFragment onDestroyView")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapStateListFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapStateListFragment.kt
index 59285dcf..b4ae37f7 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapStateListFragment.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapStateListFragment.kt
@@ -33,6 +33,5 @@ class OfflineMapStateListFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
_binding = null
- Log.e("jingo","OfflineMapStateListFragment onDestroyView")
}
}
\ No newline at end of file
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/fragment/personalcenter/PersonalCenterFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
index b9b9992d..e0d21450 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
@@ -1,22 +1,30 @@
package com.navinfo.omqs.ui.fragment.personalcenter
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
+import com.blankj.utilcode.util.UriUtils
+import com.github.k1rakishou.fsaf.FileChooser
+import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks
+import com.github.k1rakishou.fsaf.callback.FileChooserCallback
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentPersonalCenterBinding
/**
* 个人中心
*/
-class PersonalCenterFragment : Fragment() {
+class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
private var _binding: FragmentPersonalCenterBinding? = null
private val binding get() = _binding!!
+ private val fileChooser by lazy { FileChooser(requireContext()) }
+ private val viewModel by lazy { viewModels().value }
override fun onCreateView(
@@ -25,23 +33,45 @@ class PersonalCenterFragment : Fragment() {
): View {
_binding = FragmentPersonalCenterBinding.inflate(inflater, container, false)
return binding.root
-
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- Log.e("jingo", "NIMapController PersonalCenterFragment onViewCreated")
binding.root.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.personal_center_menu_offline_map ->
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
+ R.id.personal_center_menu_import_data -> {
+ // 用户选中导入数据,打开文件选择器,用户选择导入的数据文件目录
+ fileChooser.openChooseFileDialog(object: FileChooserCallback() {
+ override fun onCancel(reason: String) {
+ }
+
+ override fun onResult(uri: Uri) {
+ val file = UriUtils.uri2File(uri)
+ // 开始导入数据
+ viewModel.importOmdbData(file)
+ }
+ })
+ }
}
true
}
+
+ fileChooser.setCallbacks(this@PersonalCenterFragment)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
+
+ override fun fsafStartActivityForResult(intent: Intent, requestCode: Int) {
+ startActivityForResult(intent, requestCode)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ fileChooser.onActivityResult(requestCode, resultCode, data)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt
new file mode 100644
index 00000000..a0217e7d
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt
@@ -0,0 +1,16 @@
+package com.navinfo.omqs.ui.fragment.personalcenter
+
+import androidx.lifecycle.ViewModel
+import java.io.File
+
+class PersonalCenterViewModel: ViewModel() {
+ fun importOmdbData(omdbFile: File) {
+ // 检查File是否为sqlite数据库
+ if (omdbFile == null || omdbFile.exists()) {
+ throw Exception("文件不存在")
+ }
+ if (!omdbFile.name.endsWith(".sqlite") and !omdbFile.name.endsWith("db")) {
+ throw Exception("文件不存在")
+ }
+ }
+}
\ 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..2544a7c0 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,55 @@
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)
+ var tag = ""
+
+ 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..b4a9fd7f 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%"
}
@@ -53,12 +62,14 @@ class MyProgressBar : ProgressBar {
// int x = (getWidth()/2) - rect.centerX();
// int y = (getHeight()/2) - rect.centerY();
var x = (width * rate).toInt()
- if (x == width) {
+ val dx = width - rect.right
+ if (x > dx) {
// 如果为百分之百则在左边绘制。
- x = width - rect.right
+ x = dx
}
- 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/ic_baseline_import_export_24.xml b/app/src/main/res/drawable/ic_baseline_import_export_24.xml
new file mode 100644
index 00000000..0c6ecd72
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_import_export_24.xml
@@ -0,0 +1,5 @@
+
+
+
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">
+
+
+
\ No newline at end of file
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 @@
-
-
-
-
-
-
-
-
-
+ 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/app/src/main/res/menu/personal_center_menu.xml b/app/src/main/res/menu/personal_center_menu.xml
index e66e3705..4e3502df 100644
--- a/app/src/main/res/menu/personal_center_menu.xml
+++ b/app/src/main/res/menu/personal_center_menu.xml
@@ -11,9 +11,9 @@
android:icon="@drawable/baseline_map_24"
android:title="离线地图" />
+ android:id="@+id/personal_center_menu_import_data"
+ android:icon="@drawable/ic_baseline_import_export_24"
+ android:title="导入数据" />