增加realm 在离线地图中的使用

This commit is contained in:
squallzhjch 2023-04-06 10:46:19 +08:00
parent dbb1572688
commit fa4ad254a5
29 changed files with 422 additions and 384 deletions

View File

@ -8,8 +8,20 @@ class Constant {
* sd卡根目录
*/
lateinit var ROOT_PATH: String
/**
* 地图目录
*/
lateinit var MAP_PATH: String
/**
* 数据目录
*/
lateinit var DATA_PATH: String
/**
* 离线地图目录
*/
lateinit var OFFLINE_MAP_PATH: String
/**

View File

@ -1,13 +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()
Realm.init(this)
}
}

View File

@ -1,45 +0,0 @@
package com.navinfo.omqs.bean
import io.realm.RealmObject
enum class StatusEnum(val status: Int) {
NONE(0), WAITING(1), LOADING(2), PAUSE(3),
ERROR(4), DONE(5), UPDATE(6)
}
open class OfflineMapCityBean{
var id: String = ""
var fileName: String = ""
var name: String = ""
var url: String = ""
var version: Long = 0L
var fileSize: Long = 0L
var currentSize:Long = 0L
var status: Int = StatusEnum.NONE.status
// status的转换对象
var statusEnum:StatusEnum
get() {
return try {
StatusEnum.values().find { it.status == status }!!
} catch (e: IllegalArgumentException) {
StatusEnum.NONE
}
}
set(value) {
status = value.status
}
constructor() : super()
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)
}
}

View File

@ -1,17 +0,0 @@
package com.navinfo.omqs.bean
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class OfflineMapCityRealmObject(){
@PrimaryKey
var id: String = ""
var fileName: String=""
var name: String = ""
var url: String = ""
var version: Long = 0
var fileSize: Long = 0
var currentSize:Long = 0
var status:Int = 0
}

View File

@ -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 对象
@ -92,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)
}
}

View File

@ -1,14 +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)
@ -28,9 +27,10 @@ class MainActivityModule {
@ActivityRetainedScoped
@Provides
fun providesOfflineMapDownloadManager(
networkServiceAPI: RetrofitNetworkServiceAPI
networkServiceAPI: RetrofitNetworkServiceAPI,
realmManager: RealmCoroutineScope
): OfflineMapDownloadManager =
OfflineMapDownloadManager( networkServiceAPI)
OfflineMapDownloadManager(networkServiceAPI, realmManager)
/**
* 实验失败这样创建viewmodel不会在activity销毁的时候同时销毁

View File

@ -1,6 +1,7 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.collect.library.data.entity.OfflineMapCityBean
/**
* 网络访问 业务接口

View File

@ -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

View File

@ -1,14 +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 网络请求接口

View File

@ -1,23 +1,17 @@
package com.navinfo.omqs.http.offlinemapdownload
import android.content.Context
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.collect.library.data.entity.OfflineMapCityBean
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import dagger.hilt.android.qualifiers.ActivityContext
import kotlinx.coroutines.cancel
import com.navinfo.omqs.tools.RealmCoroutineScope
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/**
* 管理离线地图下载
*/
class OfflineMapDownloadManager @Inject constructor(
private val netApi: RetrofitNetworkServiceAPI
class OfflineMapDownloadManager(
val netApi: RetrofitNetworkServiceAPI, val realmManager: RealmCoroutineScope
) {
/**
* 最多同时下载数量
@ -94,33 +88,27 @@ class OfflineMapDownloadManager @Inject constructor(
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)
if (!scopeMap.containsKey(cityBean.id)) {
scopeMap[cityBean.id] = OfflineMapDownloadScope(this, cityBean)
}
}
fun observer(
id: String,
lifecycleOwner: LifecycleOwner,
observer: Observer<OfflineMapCityBean>
id: String, lifecycleOwner: LifecycleOwner, observer: Observer<OfflineMapCityBean>
) {
if (scopeMap.containsKey(id)) {
val downloadScope = scopeMap[id]
downloadScope?.let {
downloadScope.observer(lifecycleOwner, observer)
}
scopeMap[id]!!.observer(lifecycleOwner, observer)
}
}
fun removeObserver(id: String) {
if (scopeMap.containsKey(id)) {
scopeMap[id]!!.removeObserver()
}
}
}

View File

@ -4,14 +4,13 @@ import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.navinfo.collect.library.data.entity.OfflineMapCityBean
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.RandomAccessFile
import kotlin.coroutines.EmptyCoroutineContext
/**
* 代表一个下载任务
@ -22,11 +21,9 @@ import kotlin.coroutines.EmptyCoroutineContext
*/
class OfflineMapDownloadScope(
private val downloadManager: OfflineMapDownloadManager,
private val netApi: RetrofitNetworkServiceAPI,
val cityBean: OfflineMapCityBean
val cityBean: OfflineMapCityBean,
) :
CoroutineScope by CoroutineScope(EmptyCoroutineContext) {
CoroutineScope by CoroutineScope(Dispatchers.IO) {
/**
*下载任务用来取消的
*/
@ -35,10 +32,11 @@ class OfflineMapDownloadScope(
/**
* 管理观察者同时只有一个就行了
*/
private var observer: Observer<OfflineMapCityBean>? = null
// private var observer: Observer<OfflineMapCityBean>? = null
private var lifecycleOwner: LifecycleOwner? = null
/**
*
*通知UI更新
*/
private val downloadData = MutableLiveData<OfflineMapCityBean>()
@ -46,39 +44,6 @@ class OfflineMapDownloadScope(
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)
@ -91,6 +56,7 @@ class OfflineMapDownloadScope(
*/
fun pause() {
downloadJob?.cancel("pause")
change(OfflineMapCityBean.PAUSE)
}
/**
@ -98,20 +64,12 @@ class OfflineMapDownloadScope(
* 请不要尝试在外部调用此方法,那样会脱离[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)
}
downloadJob = launch() {
Log.e("jingo", "启动下载1")
download()
Log.e("jingo", "启动下载2")
downloadManager.launchNext(cityBean.id)
Log.e("jingo", "启动下载3")
}
}
@ -120,9 +78,7 @@ class OfflineMapDownloadScope(
* 是否是等待任务
*/
fun isWaiting(): Boolean {
val downloadInfo = downloadData.value
downloadInfo ?: return false
return downloadInfo.status == OfflineMapCityBean.WAITING
return cityBean.status == OfflineMapCityBean.WAITING
}
/**
@ -130,126 +86,91 @@ class OfflineMapDownloadScope(
* @param status [OfflineMapCityBean.Status]
*/
private fun change(status: Int) {
downloadData.value?.let {
it.status = status
downloadData.postValue(it)
if (cityBean.status != status || status == OfflineMapCityBean.LOADING) {
cityBean.status = status
downloadData.postValue(cityBean)
downloadManager.realmManager.launch {
downloadManager.realmManager.insertOrUpdate(cityBean)
}
}
}
/**
* 添加下载任务观察者
*/
fun observer(lifecycleOwner: LifecycleOwner, ob: Observer<OfflineMapCityBean>) {
if (observer != null) {
downloadData.removeObserver(observer!!)
}
this.observer = ob
downloadData.observe(lifecycleOwner, observer!!)
fun observer(owner: LifecycleOwner, ob: Observer<OfflineMapCityBean>) {
removeObserver()
this.lifecycleOwner = owner
downloadData.observe(owner, ob)
}
/**
* 下载文件
*/
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)
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 (isActive) {
while (downloadJob?.isActive == true) {
readLength = inputStream.read(buffer)
if (readLength != -1) {
randomAccessFile.write(buffer, 0, readLength)
downloadInfo.currentSize += 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()
inputStream?.close()
randomAccessFile?.close()
}
})
/**
*
*/
private fun checkFile(){
}
fun removeObserver() {
lifecycleOwner?.let {
downloadData.removeObservers(it)
null
}
}
}

View File

@ -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
}
}
}
}
}
}

View File

@ -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<OfflineMapCityBean> {
var list: List<OfflineMapCityBean> = mutableListOf()
realm.executeTransaction {
val objects = realm.where<OfflineMapCityBean>().findAll().sort("id", Sort.ASCENDING)
list = realm.copyFromRealm(objects)
}
return list
}
suspend fun insertOrUpdate(objects: Collection<RealmModel?>?) {
realm.executeTransaction {
realm.insertOrUpdate(objects)
}
}
suspend fun insertOrUpdate(realmModel: RealmModel?) {
realm.executeTransaction {
realm.insertOrUpdate(realmModel)
}
}
}

View File

@ -3,15 +3,9 @@ package com.navinfo.omqs.ui
import android.content.Intent
import android.os.Bundle
import androidx.core.view.WindowCompat
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import com.github.k1rakishou.fsaf.FileChooser
import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityMainBinding
import com.navinfo.omqs.ui.activity.PermissionsActivity

View File

@ -13,10 +13,13 @@ 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
@ -65,6 +68,9 @@ class LoginActivity : PermissionsActivity() {
loginDialog?.dismiss()
loginDialog = null
}
LoginStatus.LOGIN_STATUS_NET_OFFLINE_MAP -> {
loginDialog("检查离线地图...")
}
}
}

View File

@ -5,17 +5,17 @@ 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
import com.navinfo.omqs.bean.LoginUserBean
import io.realm.Realm
import io.realm.RealmConfiguration
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 java.math.BigInteger
import javax.inject.Inject
enum class LoginStatus {
/**
@ -23,6 +23,11 @@ enum class LoginStatus {
*/
LOGIN_STATUS_NET_LOADING,
/**
* 访问离线地图列表
*/
LOGIN_STATUS_NET_OFFLINE_MAP,
/**
* 初始化文件夹
*/
@ -49,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<LoginUserBean> = MutableLiveData()
@ -63,17 +71,6 @@ class LoginViewModel(
loginUser.value = LoginUserBean(username = "admin", password = "123456")
}
private fun initRealm() {
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()
Constant.realm = Realm.getInstance(config)
}
/**
* 处理注册按钮
@ -113,46 +110,56 @@ class LoginViewModel(
//文件夹初始化
try {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT)
createRootFolder(context)
createUserFolder(context)
// 初始化Realm
initRealm()
} catch (e: IOException) {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_FAILURE)
}
//假装解压文件等
delay(1000)
loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS)
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/"
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()
}
}
private fun createUserFolder(context: Context) {
// 在SD卡创建用户目录解压资源等
}
/**
* 取消登录
*/
fun cancelLogin() {
Log.e("jingo", "取消了?${Thread.currentThread().name}")
jobLogin?.let {
it.cancel()
loginStatus.value = LoginStatus.LOGIN_STATUS_CANCEL
@ -163,6 +170,4 @@ class LoginViewModel(
super.onCleared()
cancelLogin()
}
}

View File

@ -69,7 +69,6 @@ class MainActivity : BaseActivity() {
super.onDestroy()
mapController.mMapView.onDestroy()
mapController.locationLayerHandler.stopLocation()
Log.e("jingo", "MainActivity 销毁")
}
override fun onResume() {

View File

@ -25,7 +25,6 @@ class MainViewModel @Inject constructor(
}
override fun onCleared() {
Log.e("jingo","MainViewModel 被释放了")
super.onCleared()
}
}

View File

@ -6,8 +6,8 @@ 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.bean.OfflineMapCityBean
import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding
import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter
@ -32,14 +32,16 @@ class OfflineMapCityListAdapter @Inject constructor(
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)
}
// OfflineMapCityBean.WAITING->{
// downloadManager.cancel(cityBean.id)
// }
else -> {
Log.e("jingo", "暂停 ${cityBean.status}")
}
}
}
}
@ -50,24 +52,38 @@ class OfflineMapCityListAdapter @Inject constructor(
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()
downloadManager.addTask(cityBean)
changeViews(binding, cityBean)
downloadManager.observer(cityBean.id, holder) {
if (cityBean.id == it.id)
changeViews(binding, it)
}
inner class DownloadObserver(val id: String, val binding: AdapterOfflineMapCityBinding) :
Observer<OfflineMapCityBean> {
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()

View File

@ -57,6 +57,5 @@ class OfflineMapCityListFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
_binding = null
Log.e("jingo", "OfflineMapCityListFragment onDestroyView")
}
}

View File

@ -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<List<OfflineMapCityBean>>()
@ -29,21 +30,15 @@ class OfflineMapCityListViewModel @Inject constructor(
* 去获取离线地图列表
*/
fun getCityList() {
viewModelScope.launch {
when (val result = networkService.getOfflineMapCityList()) {
is NetResult.Success -> {
cityListLiveData.postValue(result.data?.sortedBy { bean -> bean.id })
}
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<OfflineMapCityBean>().findAll().sort("id", Sort.ASCENDING)
val list = realm.copyFromRealm(objects)
realm.close()
for (item in list) {
FileManager.checkOfflineMapFileInfo(item)
}
cityListLiveData.postValue(list)
}
}
}

View File

@ -61,6 +61,5 @@ class OfflineMapFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
_binding = null
Log.e("jingo","OfflineMapFragment onDestroyView")
}
}

View File

@ -33,6 +33,5 @@ class OfflineMapStateListFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
_binding = null
Log.e("jingo","OfflineMapStateListFragment onDestroyView")
}
}

View File

@ -3,21 +3,16 @@ 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.activity.viewModels
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.get
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.google.android.material.snackbar.Snackbar
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentPersonalCenterBinding
@ -42,7 +37,6 @@ class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
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 ->

View File

@ -14,6 +14,7 @@ import androidx.viewbinding.ViewBinding
open class BaseViewHolder(val viewBinding: ViewBinding) :
RecyclerView.ViewHolder(viewBinding.root), LifecycleOwner {
private val lifecycleRegistry = LifecycleRegistry(this)
var tag = ""
init {
// dataBinding.lifecycleOwner = this

View File

@ -62,9 +62,10 @@ 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
}
mPaint.textSize = 24f
val y: Int = 10 - rect.top

View File

@ -3,31 +3,44 @@ package com.navinfo.collect.library.data.entity
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
enum class StatusEnum(val status: Int) {
NONE(0), WAITING(1), LOADING(2), PAUSE(3),
ERROR(4), DONE(5), UPDATE(6)
}
//enum class StatusEnum(val status: Int) {
// NONE(0), WAITING(1), LOADING(2), PAUSE(3),
// ERROR(4), DONE(5), UPDATE(6)
//}
open class OfflineMapCityBean @JvmOverloads constructor(@PrimaryKey var id: String = "",
var fileName: String = "",
var name: String = "",
var url: String = "",
var version: Long = 0L,
var fileSize: Long = 0L,
var currentSize: Long = 0L,
var status: Int =0) : RealmObject(){
// status的转换对象
var statusEnum:StatusEnum
get() {
return try {
StatusEnum.values().find { it.status == status }!!
} catch (e: IllegalArgumentException) {
StatusEnum.NONE
}
}
set(value) {
status = value.status
}
open class OfflineMapCityBean @JvmOverloads constructor(
@PrimaryKey var id: String = "",
var fileName: String = "",
var name: String = "",
var url: String = "",
var version: Long = 0L,
var fileSize: Long = 0L,
var currentSize: Long = 0L,
var status: Int = NONE
) : RealmObject() {
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 //有新版本要更新
}
// // status的转换对象
// var statusEnum: StatusEnum
// get() {
// return try {
// StatusEnum.values().find { it.status == status }!!
// } catch (e: IllegalArgumentException) {
// StatusEnum.NONE
// }
// }
// set(value) {
// status = value.status
// }
fun getFileSizeText(): String {
return if (fileSize < 1024.0)
@ -40,9 +53,5 @@ open class OfflineMapCityBean @JvmOverloads constructor(@PrimaryKey var id: Stri
"%.2f M".format(fileSize / 1073741824.0)
}
// constructor(){
//
// }
//
}

View File

@ -91,7 +91,6 @@ DataNiLocationHandler(context: Context, dataBase: MapLifeDataBase) :
)
mDataBase.niLocationDao.delete(niLocation);
} catch (e: Throwable) {
Log.e("jingo", "删除数据报错 ${e.message}");
Handler(Looper.getMainLooper()).post {
callback.invoke(false, "${e.message}")
}

View File

@ -37,8 +37,5 @@ class NIMapController {
mapView.setOptions(options)
}
fun print() {
Log.e("jingo", "NIMapController 哈希code ${hashCode()}")
}
}