From 0b5fe7468bb1c69917e2056fdbc29681003c3801 Mon Sep 17 00:00:00 2001 From: xiaoyan Date: Fri, 24 Mar 2023 16:23:32 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=AE=A1=E7=90=86=E5=92=8C=E6=96=87=E4=BB=B6=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 8 +++- app/src/main/AndroidManifest.xml | 2 +- .../navinfo/omqs/data/process/DataEngine.kt | 8 ++++ .../java/com/navinfo/omqs/ui/MainActivity.kt | 45 ++++++++++++++++++- .../navinfo/omqs/ui/PermissionsActivity.kt | 12 +++-- 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt diff --git a/app/build.gradle b/app/build.gradle index 353bb4e8..3f963fba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,6 +48,10 @@ 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' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7720dc64..d55acb3d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.OMQualityInspection" - tools:targetApi="31"> + android:requestLegacyExternalStorage="true"> 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) @@ -59,4 +93,13 @@ class MainActivity : AppCompatActivity() { 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/PermissionsActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/PermissionsActivity.kt index 77508544..a175c633 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/PermissionsActivity.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/PermissionsActivity.kt @@ -12,13 +12,17 @@ import com.hjq.permissions.XXPermissions * 权限申请Activity */ abstract class PermissionsActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { - super.onCreate(savedInstanceState, persistentState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) XXPermissions.with(this) // 申请单个权限 - .permission(Permission.WRITE_EXTERNAL_STORAGE) - .permission(Permission.READ_EXTERNAL_STORAGE) +// .permission(Permission.WRITE_EXTERNAL_STORAGE) +// .permission(Permission.READ_EXTERNAL_STORAGE) +// .permission(Permission.READ_MEDIA_IMAGES) +// .permission(Permission.READ_MEDIA_AUDIO) +// .permission(Permission.READ_MEDIA_VIDEO) + .permission(Permission.MANAGE_EXTERNAL_STORAGE) // 设置权限请求拦截器(局部设置) //.interceptor(new PermissionInterceptor()) // 设置不触发错误检测机制(局部设置) From 56c5badcf04b80aa133d7a62bb34c7bd551adb5f Mon Sep 17 00:00:00 2001 From: xiaoyan Date: Mon, 3 Apr 2023 10:26:38 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0Realm=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 +- .../navinfo/omqs/bean/OfflineMapCityBean.kt | 2 +- .../omqs/bean/OfflineMapCityRealmObject.kt | 2 +- .../omqs/ui/activity/login/LoginViewModel.kt | 4 +- .../res/layout/adapter_offline_map_city.xml | 2 +- build.gradle | 2 +- .../library/data/entity/OfflineMapCityBean.kt | 48 +++++++++++++++++++ .../data/entity/OfflineMapCityRealmObject.kt | 43 +++++++++++++++++ 8 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 collect-library/src/main/java/com/navinfo/collect/library/data/entity/OfflineMapCityBean.kt create mode 100644 collect-library/src/main/java/com/navinfo/collect/library/data/entity/OfflineMapCityRealmObject.kt diff --git a/app/build.gradle b/app/build.gradle index aaa4809f..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' } -apply plugin: "realm-android" android { namespace 'com.navinfo.omqs' compileSdk 33 diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt index fd00662b..d4485441 100644 --- a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt +++ b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt @@ -7,7 +7,7 @@ enum class StatusEnum(val status: Int) { ERROR(4), DONE(5), UPDATE(6) } -open class OfflineMapCityBean : RealmObject{ +open class OfflineMapCityBean{ var id: String = "" var fileName: String = "" var name: String = "" diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt index 4f8481fc..92461d60 100644 --- a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt +++ b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt @@ -4,7 +4,7 @@ import io.realm.RealmObject import io.realm.annotations.PrimaryKey -open class OfflineMapCityRealmObject(): RealmObject() { +open class OfflineMapCityRealmObject(){ @PrimaryKey var id: String = "" var fileName: String="" 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 7578921b..46e0e27f 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 @@ -9,11 +9,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.navinfo.omqs.Constant import com.navinfo.omqs.bean.LoginUserBean -import com.navinfo.omqs.bean.OfflineMapCityRealmObject import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.* -import okio.ByteString.Companion.toByteString import okio.IOException import java.io.File import java.math.BigInteger @@ -64,7 +62,7 @@ class LoginViewModel( loginUser.value = LoginUserBean(username = "admin", password = "123456") } - fun initRealm() { + private fun initRealm() { val password = "password".encodeToByteArray().copyInto(ByteArray(64)) // 1110000011000010111001101110011011101110110111101110010011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 Log.d("", "密码是: ${BigInteger(1, password).toString(2).padStart(64, '0')}") 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..3a0c0d60 100644 --- a/app/src/main/res/layout/adapter_offline_map_city.xml +++ b/app/src/main/res/layout/adapter_offline_map_city.xml @@ -10,7 +10,7 @@ + type="com.navinfo.collect.library.data.entity.OfflineMapCityBean" /> Date: Mon, 3 Apr 2023 10:40:53 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E4=B8=8B=E8=BD=BD=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 2 + .../main/java/com/navinfo/omqs/Constant.kt | 2 +- .../navinfo/omqs/bean/OfflineMapCityBean.kt | 8 +- .../com/navinfo/omqs/hilt/GlobalModule.kt | 3 +- .../navinfo/omqs/hilt/MainActivityModule.kt | 8 +- .../omqs/http/RetrofitNetworkServiceAPI.kt | 3 +- .../OfflineMapDownloadManager.kt | 110 ++++++-- .../OfflineMapDownloadScope.kt | 239 +++++++++++++++++- .../omqs/ui/activity/login/LoginActivity.kt | 77 +++--- .../omqs/ui/activity/login/LoginViewModel.kt | 14 +- .../omqs/ui/activity/map/MainActivity.kt | 3 + .../fragment/offlinemap/OfflineMapAdapter.kt | 1 + .../offlinemap/OfflineMapCityListAdapter.kt | 107 +++++++- .../offlinemap/OfflineMapCityListFragment.kt | 21 +- .../offlinemap/OfflineMapCityListViewModel.kt | 2 +- .../offlinemap/OfflineMapViewHolder.kt | 10 + .../omqs/ui/other/BaseRecyclerViewAdapter.kt | 51 +++- .../navinfo/omqs/ui/other/BaseViewHolder.kt | 49 +++- .../navinfo/omqs/ui/widget/MyProgressBar.kt | 20 +- app/src/main/res/drawable/progress_bg.xml | 21 ++ app/src/main/res/layout/activity_login.xml | 3 +- .../res/layout/adapter_offline_map_city.xml | 127 ++++------ .../collect/library/utils/GeometryTools.java | 2 - 23 files changed, 705 insertions(+), 178 deletions(-) create mode 100644 app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapViewHolder.kt create mode 100644 app/src/main/res/drawable/progress_bg.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d175c01..4e0cee42 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,7 @@ + + suspend fun retrofitDownLoadFile(@Header("RANGE") start: String? = "0", @Url url: String):Response /** diff --git a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt index cc720498..d38896f0 100644 --- a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt +++ b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt @@ -1,48 +1,126 @@ package com.navinfo.omqs.http.offlinemapdownload import android.content.Context -import android.os.Environment import android.text.TextUtils +import android.util.Log +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.Observer import com.navinfo.omqs.Constant import com.navinfo.omqs.bean.OfflineMapCityBean +import com.navinfo.omqs.http.RetrofitNetworkServiceAPI import dagger.hilt.android.qualifiers.ActivityContext -import java.io.Serializable +import kotlinx.coroutines.cancel import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject /** * 管理离线地图下载 */ -class OfflineMapDownloadManager @Inject constructor(@ActivityContext context: Context) { +class OfflineMapDownloadManager @Inject constructor( + private val netApi: RetrofitNetworkServiceAPI +) { /** * 最多同时下载数量 */ - private val MAX_SCOPE = 5 + private val MAX_SCOPE = 3 /** - * 存储有哪些城市需要下载 + * 存储有哪些城市需要下载的队列 */ private val scopeMap: ConcurrentHashMap by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { ConcurrentHashMap() } - val downloadFolder: String? by lazy { - Constant.MAP_PATH + "/offline/" + /** + * 存储正在下载的城市队列 + */ + private val taskScopeMap: ConcurrentHashMap by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + ConcurrentHashMap() } /** + * 启动下载任务 + * 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用 + */ + fun launchScope(scope: OfflineMapDownloadScope) { + if (taskScopeMap.size >= MAX_SCOPE) { + return + } + if (taskScopeMap.contains(scope.cityBean.id)) { + return + } + taskScopeMap[scope.cityBean.id] = scope + scope.launch() + } + + /** + * 启动下一个任务,如果有正在等待中的任务的话 + * 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用 + * @param previousUrl 上一个下载任务的下载连接 + */ + fun launchNext(previousUrl: String) { + taskScopeMap.remove(previousUrl) + for (entrySet in scopeMap) { + val downloadScope = entrySet.value + if (downloadScope.isWaiting()) { + launchScope(downloadScope) + break + } + } + } + + /** + * 暂停任务 + * 只有等待中的任务和正在下载中的任务才可以进行暂停操作 + */ + fun pause(id: String) { + if (taskScopeMap.containsKey(id)) { + val downloadScope = taskScopeMap[id] + downloadScope?.let { + downloadScope.pause() + } + launchNext(id) + } + + } + + /** + * 将下载任务加入到协程作用域的下载队列里 * 请求一个下载任务[OfflineMapDownloadScope] * 这是创建[OfflineMapDownloadScope]的唯一途径,请不要通过其他方式创建[OfflineMapDownloadScope] */ - fun request(cityBean: OfflineMapCityBean): OfflineMapDownloadScope? { - //没有下载连接的不能下载 - if (TextUtils.isEmpty(cityBean.url)) return null -// if(scopeMap.containsKey()) - var downloadScope = scopeMap[cityBean.id] - if (downloadScope == null) { - scopeMap[cityBean.id] = OfflineMapDownloadScope(cityBean) - } - return downloadScope + fun start(id: String) { + scopeMap[id]?.start() } + + fun cancel(id: String) { + taskScopeMap.remove(id) + scopeMap[id]?.cancelTask() + } + + fun addTask(cityBean: OfflineMapCityBean) { + if (scopeMap.containsKey(cityBean.id)) { + return + } else { + scopeMap[cityBean.id] = OfflineMapDownloadScope(this, netApi, cityBean) + } + } + + + fun observer( + id: String, + lifecycleOwner: LifecycleOwner, + observer: Observer + ) { + if (scopeMap.containsKey(id)) { + val downloadScope = scopeMap[id] + downloadScope?.let { + downloadScope.observer(lifecycleOwner, observer) + } + } + } + + + } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt index a51f93d5..c518b504 100644 --- a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt +++ b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt @@ -1,26 +1,255 @@ package com.navinfo.omqs.http.offlinemapdownload +import android.util.Log +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import com.navinfo.omqs.Constant import com.navinfo.omqs.bean.OfflineMapCityBean -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job +import com.navinfo.omqs.http.RetrofitNetworkServiceAPI +import kotlinx.coroutines.* +import java.io.File +import java.io.IOException +import java.io.RandomAccessFile import kotlin.coroutines.EmptyCoroutineContext /** * 代表一个下载任务 * [OfflineMapCityBean.id]将做为下载任务的唯一标识 - * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.download]获取此对象 + * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.request]获取此对象 * 这是一个协程作用域, * EmptyCoroutineContext 表示一个不包含任何元素的协程上下文,它通常用于创建新的协程上下文,或者作为协程上下文的基础。 */ -class OfflineMapDownloadScope(cityBean: OfflineMapCityBean) : CoroutineScope by CoroutineScope(EmptyCoroutineContext) { +class OfflineMapDownloadScope( + private val downloadManager: OfflineMapDownloadManager, + private val netApi: RetrofitNetworkServiceAPI, + val cityBean: OfflineMapCityBean + +) : + CoroutineScope by CoroutineScope(EmptyCoroutineContext) { /** - * + *下载任务,用来取消的 */ private var downloadJob: Job? = null + /** + * 管理观察者,同时只有一个就行了 + */ + private var observer: Observer? = null + /** * */ private val downloadData = MutableLiveData() + + init { + downloadData.value = cityBean + } + + /** + * 开始任务的下载 + * [OfflineMapCityBean]是在协程中进行创建的,它的创建会优先从数据库和本地文件获取,但这种操作是异步的,详情请看init代码块 + * 我们需要通过观察者观察[OfflineMapCityBean]来得知它是否已经创建完成,只有当他创建完成且不为空(如果创建完成,它一定不为空) + * 才可以交由[OfflineMapDownloadManager]进行下载任务的启动 + * 任务的开始可能并不是立即的,任务会受到[OfflineMapDownloadManager]的管理 + * + * 这段原来代码没看懂:要触发 Observer 得观察的对象[OfflineMapCityBean]发生变化才行,原demo里没找到livedata的变化也触发了onChange,这里根本触发不了 + * + * 找到原因了:是[cityBean]根本没有设置到liveData中,但是还是不用这样了,因为cityBean是一定创建好了的 + */ + //原代码 +// fun start() { +// var observer: Observer? = null +// observer = Observer { cityBean -> +// Log.e("jingo","Observer 创建了,bean 为null吗?$cityBean") +// cityBean?.let { +// observer?.let { +// Log.e("jingo","Observer 这里为什么要解除观察?") +// downloadData.removeObserver(it) +// } +// Log.e("jingo","Observer 状态 ${cityBean.status} ") +// when (cityBean.status) { +// +// OfflineMapCityBean.PAUSE, OfflineMapCityBean.ERROR, OfflineMapCityBean.NONE -> { +// change(OfflineMapCityBean.WAITING) +// downloadManager.launchScope(this@OfflineMapDownloadScope) +// } +// } +// } +// } +// downloadData.observeForever(observer) +// } + //改进的代码 + fun start() { + change(OfflineMapCityBean.WAITING) + downloadManager.launchScope(this@OfflineMapDownloadScope) + } + + /** + * 暂停任务 + * 其实就是取消任务,移除监听 + */ + fun pause() { + downloadJob?.cancel("pause") + } + + /** + * 启动协程进行下载 + * 请不要尝试在外部调用此方法,那样会脱离[OfflineMapDownloadManager]的管理 + */ + fun launch() { + downloadJob = launch { + try { + download() + change(OfflineMapCityBean.DONE) + } catch (e: Throwable) { + Log.e("jingo DownloadScope", "error:${e.message}") + if (e.message == "pause") { + change(OfflineMapCityBean.PAUSE) + } else { + change(OfflineMapCityBean.ERROR) + } + } finally { + downloadManager.launchNext(cityBean.id) + } + } + } + + + /** + * 是否是等待任务 + */ + fun isWaiting(): Boolean { + val downloadInfo = downloadData.value + downloadInfo ?: return false + return downloadInfo.status == OfflineMapCityBean.WAITING + } + + /** + * 更新任务 + * @param status [OfflineMapCityBean.Status] + */ + private fun change(status: Int) { + downloadData.value?.let { + it.status = status + downloadData.postValue(it) + } + } + + /** + * 添加下载任务观察者 + */ + fun observer(lifecycleOwner: LifecycleOwner, ob: Observer) { + if (observer != null) { + downloadData.removeObserver(observer!!) + } + this.observer = ob + downloadData.observe(lifecycleOwner, observer!!) + } + + /** + * 下载文件 + */ + private suspend fun download() = withContext(context = Dispatchers.IO, block = { + + val downloadInfo = downloadData.value ?: throw IOException("jingo Download info is null") + //创建离线地图 下载文件夹,.map文件夹的下一级 + val fileDir = File("${Constant.OFFLINE_MAP_PATH}download") + if (!fileDir.exists()) { + fileDir.mkdirs() + } + //遍历文件夹,找到对应的省市.map文件 + val files = fileDir.listFiles() + for (item in files) { + //用id找到对应的文件 + if (item.isFile && item.name.startsWith(downloadInfo.id)) { + //判断文件的版本号是否一致 + if (item.name.contains("_${downloadInfo.version}.map")) { + //都一致,说明文件已经下载完成,不用再次下载 + change(OfflineMapCityBean.DONE) + return@withContext + }else{ + + } + break + } + } + + //查看下.map文件夹在不在 + val fileMap = File("${Constant.OFFLINE_MAP_PATH}${downloadInfo.fileName}") + val fileTemp = + File("${Constant.OFFLINE_MAP_PATH}download/${downloadInfo.id}_${downloadInfo.version}") + + + if (fileTemp.exists()) { + + } + + if (!fileMap.exists()) { + } + + change(OfflineMapCityBean.LOADING) + + + val startPosition = downloadInfo.currentSize + //验证断点有效性 + if (startPosition < 0) throw IOException("jingo Start position less than zero") + //下载的文件是否已经被删除 +// if (startPosition > 0 && !TextUtils.isEmpty(downloadInfo.path)) +// if (!File(downloadInfo.path).exists()) throw IOException("File does not exist") + val response = netApi.retrofitDownLoadFile( + start = "bytes=$startPosition-", + url = downloadInfo.url + ) + val responseBody = response.body() + + responseBody ?: throw IOException("jingo ResponseBody is null") + //文件长度 + downloadInfo.fileSize = responseBody.contentLength() + //保存的文件名称 +// if (TextUtils.isEmpty(downloadInfo.fileName)) +// downloadInfo.fileName = UrlUtils.getUrlFileName(downloadInfo.url) + +// //验证下载完成的任务与实际文件的匹配度 +// if (startPosition == downloadInfo.fileSize && startPosition > 0) { +// if (file.exists() && startPosition == file.length()) { +// change(OfflineMapCityBean.DONE) +// return@withContext +// } else throw IOException("jingo The content length is not the same as the file length") +// } + //写入文件 + val randomAccessFile = RandomAccessFile(fileTemp, "rwd") + randomAccessFile.seek(startPosition) +// if (downloadInfo.currentSize == 0L) { +// randomAccessFile.setLength(downloadInfo.fileSize) +// } + downloadInfo.currentSize = startPosition + val inputStream = responseBody.byteStream() + val bufferSize = 1024 * 2 + val buffer = ByteArray(bufferSize) + try { + var readLength = 0 + while (isActive) { + readLength = inputStream.read(buffer) + if (readLength != -1) { + randomAccessFile.write(buffer, 0, readLength) + downloadInfo.currentSize += readLength + change(OfflineMapCityBean.LOADING) + } else { + break + } + } + } finally { + inputStream.close() + randomAccessFile.close() + } + }) + + /** + * + */ + private fun checkFile(){ + + } + } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt index f4fc2611..36c50b0b 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt @@ -7,6 +7,7 @@ import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.navinfo.omqs.R import com.navinfo.omqs.databinding.ActivityLoginBinding @@ -30,41 +31,49 @@ class LoginActivity : PermissionsActivity() { initView() } - private fun initView() { - //登录校验,初始化成功 - viewModel.loginStatus.observe(this) { - when (it) { - LoginStatus.LOGIN_STATUS_NET_LOADING -> { - loginDialog("验证用户信息...") - } - LoginStatus.LOGIN_STATUS_FOLDER_INIT -> { - loginDialog("检查本地数据...") - } - LoginStatus.LOGIN_STATUS_FOLDER_FAILURE -> { - Toast.makeText(this, "文件夹初始化失败", Toast.LENGTH_SHORT).show() - loginDialog?.dismiss() - loginDialog = null - } - LoginStatus.LOGIN_STATUS_NET_FAILURE -> { - Toast.makeText(this, "网络访问失败", Toast.LENGTH_SHORT).show() - loginDialog?.dismiss() - loginDialog = null - } - LoginStatus.LOGIN_STATUS_SUCCESS -> { - val intent = Intent(this@LoginActivity, MainActivity::class.java) - startActivity(intent) + /** + * 观察登录状态,把Observer提出来是为了防止每次数据变化都会有新的observer创建 + * 还有为了方便释放(需不需要手动释放?不清楚)不需要释放,当viewmodel观察到activity/fragment 的生命周期时会自动释放 + * PS:不要在 observer 中修改 LiveData 的值的数据,会影响其他 observer + */ + private val loginObserve = Observer { + when (it) { + LoginStatus.LOGIN_STATUS_NET_LOADING -> { + loginDialog("验证用户信息...") + } + LoginStatus.LOGIN_STATUS_FOLDER_INIT -> { + loginDialog("检查本地数据...") + } + LoginStatus.LOGIN_STATUS_FOLDER_FAILURE -> { + Toast.makeText(this, "文件夹初始化失败", Toast.LENGTH_SHORT).show() + loginDialog?.dismiss() + loginDialog = null + } + LoginStatus.LOGIN_STATUS_NET_FAILURE -> { + Toast.makeText(this, "网络访问失败", Toast.LENGTH_SHORT).show() + loginDialog?.dismiss() + loginDialog = null + } + LoginStatus.LOGIN_STATUS_SUCCESS -> { + val intent = Intent(this@LoginActivity, MainActivity::class.java) + startActivity(intent) // finish() - loginDialog?.dismiss() - loginDialog = null - } - LoginStatus.LOGIN_STATUS_CANCEL -> { - loginDialog?.dismiss() - loginDialog = null - } + loginDialog?.dismiss() + loginDialog = null + } + LoginStatus.LOGIN_STATUS_CANCEL -> { + loginDialog?.dismiss() + loginDialog = null } } } + private fun initView() { + //登录校验,初始化成功 + viewModel.loginStatus.observe(this, loginObserve) + + } + /** * 登录dialog */ @@ -73,7 +82,7 @@ class LoginActivity : PermissionsActivity() { loginDialog = MaterialAlertDialogBuilder( this, com.google.android.material.R.style.MaterialAlertDialog_Material3 ).setTitle("登录").setMessage(message).show() - loginDialog!!.setCanceledOnTouchOutside(true) + loginDialog!!.setCanceledOnTouchOutside(false) loginDialog!!.setOnCancelListener { viewModel.cancelLogin() } @@ -84,13 +93,17 @@ class LoginActivity : PermissionsActivity() { //进应用根本不调用,待查 override fun onPermissionsGranted() { - Log.e("jingo","调用了吗") + Log.e("jingo", "调用了吗") } override fun onPermissionsDenied() { } + override fun onDestroy() { + super.onDestroy() + } + /** * 处理登录按钮 */ diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt index 2d6f29b6..ad02cc83 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt @@ -5,6 +5,7 @@ import android.util.Log import android.view.View import android.widget.Toast import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.navinfo.omqs.Constant @@ -65,7 +66,7 @@ class LoginViewModel( */ fun onClick(view: View) { loginUser.value!!.username = "admin2" - loginUser.postValue(loginUser.value) + loginUser.value = loginUser.value } /** @@ -81,7 +82,6 @@ class LoginViewModel( //不指定IO,会在主线程里运行 jobLogin = viewModelScope.launch(Dispatchers.IO) { loginCheck(context, userName, password) - Log.e("jingo", "运行完了1?${Thread.currentThread().name}") } } @@ -90,14 +90,12 @@ class LoginViewModel( */ private suspend fun loginCheck(context: Context, userName: String, password: String) { - Log.e("jingo", "我在哪个线程里?${Thread.currentThread().name}") //上面调用了线程切换,这里不用调用,即使调用了还是在同一个线程中,除非自定义协程域?(待验证) // withContext(Dispatchers.IO) { - Log.e("jingo", "delay之前?${Thread.currentThread().name}") //网络访问 loginStatus.postValue(LoginStatus.LOGIN_STATUS_NET_LOADING) - //假装网络访问,等待3秒 - delay(3000) + //假装网络访问,等待2秒 + delay(1000) //文件夹初始化 try { loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT) @@ -108,7 +106,6 @@ class LoginViewModel( //假装解压文件等 delay(1000) loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS) - Log.e("jingo", "delay之后?${Thread.currentThread().name}") // } } @@ -121,6 +118,7 @@ class LoginViewModel( sdCardPath?.let { Constant.ROOT_PATH = sdCardPath.absolutePath Constant.MAP_PATH = Constant.ROOT_PATH + "/map/" + Constant.OFFLINE_MAP_PATH = Constant.MAP_PATH + "offline/" val file = File(Constant.MAP_PATH) if (!file.exists()) { file.mkdirs() @@ -135,7 +133,7 @@ class LoginViewModel( Log.e("jingo", "取消了?${Thread.currentThread().name}") jobLogin?.let { it.cancel() - loginStatus.postValue(LoginStatus.LOGIN_STATUS_CANCEL) + loginStatus.value = LoginStatus.LOGIN_STATUS_CANCEL } } diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt index cb03179e..0ec5416c 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt @@ -11,6 +11,7 @@ import com.navinfo.collect.library.map.NIMapController import com.navinfo.omqs.Constant import com.navinfo.omqs.R import com.navinfo.omqs.databinding.ActivityMainBinding +import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager import com.navinfo.omqs.ui.activity.BaseActivity import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -27,6 +28,8 @@ class MainActivity : BaseActivity() { //注入地图控制器 @Inject lateinit var mapController: NIMapController + @Inject + lateinit var offlineMapDownloadManager: OfflineMapDownloadManager override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, false) diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt index 4c1e6786..f3c863a4 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt @@ -3,6 +3,7 @@ package com.navinfo.omqs.ui.fragment.offlinemap import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter +import dagger.hilt.EntryPoint /** * 离线地图主页面,viewpage适配器 diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt index 8f94464c..8894d104 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt @@ -1,33 +1,118 @@ package com.navinfo.omqs.ui.fragment.offlinemap -import androidx.databinding.ViewDataBinding +import android.content.Context +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.Observer import com.navinfo.omqs.R -import com.navinfo.omqs.BR import com.navinfo.omqs.bean.OfflineMapCityBean import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding +import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter import com.navinfo.omqs.ui.other.BaseViewHolder import javax.inject.Inject /** * 离线地图城市列表 RecyclerView 适配器 + * + * 在 RecycleView 的 ViewHolder 中监听 ViewModel 的 LiveData,然后此时传递的 lifecycleOwner 是对应的 Fragment。由于 ViewHolder 的生命周期是比 Fragment 短的,所以当 ViewHolder 销毁时,由于 Fragment 的 Lifecycle 还没有结束,此时 ViewHolder 会发生内存泄露(监听的 LiveData 没有解绑) + * 这种场景下有两种解决办法: + *使用 LiveData 的 observeForever 然后在 ViewHolder 销毁前手动调用 removeObserver + *使用 LifecycleRegistry 给 ViewHolder 分发生命周期(这里使用了这个) */ +class OfflineMapCityListAdapter @Inject constructor( + private val downloadManager: OfflineMapDownloadManager, private val context: Context +) : BaseRecyclerViewAdapter() { -class OfflineMapCityListAdapter @Inject constructor() : - BaseRecyclerViewAdapter() { - override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { - var binding: ViewDataBinding = holder.dataBinding - //立刻刷新UI,解决闪烁 -// binding.executePendingBindings() - binding.setVariable(BR.cityBean, data[position]) - (binding as AdapterOfflineMapCityBinding).offlineMapDownloadBtn.setOnClickListener { + private val downloadBtnClick = View.OnClickListener() { + if (it.tag != null) { + val cityBean = data[it.tag as Int] + when (cityBean.status) { + OfflineMapCityBean.NONE, OfflineMapCityBean.UPDATE, OfflineMapCityBean.PAUSE, OfflineMapCityBean.ERROR -> { + downloadManager.start(cityBean.id) + } + OfflineMapCityBean.LOADING, OfflineMapCityBean.WAITING -> { + downloadManager.pause(cityBean.id) + } +// OfflineMapCityBean.WAITING->{ +// downloadManager.cancel(cityBean.id) +// } + } } + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { + val viewBinding = + AdapterOfflineMapCityBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return BaseViewHolder(viewBinding) + } + + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + val binding: AdapterOfflineMapCityBinding = + holder.viewBinding as AdapterOfflineMapCityBinding + //牺牲性能立刻刷新UI,解决闪烁 这里不用 +// binding.executePendingBindings() + val cityBean = data[position] + binding.offlineMapDownloadBtn.tag = position + binding.offlineMapDownloadBtn.setOnClickListener(downloadBtnClick) + binding.offlineMapCityName.text = cityBean.name + binding.offlineMapCitySize.text = cityBean.getFileSizeText() + downloadManager.addTask(cityBean) + changeViews(binding, cityBean) + downloadManager.observer(cityBean.id, holder) { + if (cityBean.id == it.id) + changeViews(binding, it) + } + } + + private fun changeViews(binding: AdapterOfflineMapCityBinding, cityBean: OfflineMapCityBean) { + binding.offlineMapProgress.progress = + (cityBean.currentSize * 100 / cityBean.fileSize).toInt() + when (cityBean.status) { + OfflineMapCityBean.NONE -> { + if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility = + View.INVISIBLE + binding.offlineMapDownloadBtn.text = "下载" + } + OfflineMapCityBean.WAITING -> { + if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility = + View.VISIBLE + binding.offlineMapDownloadBtn.text = "等待中" + } + OfflineMapCityBean.LOADING -> { + if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility = + View.VISIBLE + binding.offlineMapDownloadBtn.text = "暂停" + } + OfflineMapCityBean.PAUSE -> { + if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility = + View.VISIBLE + binding.offlineMapDownloadBtn.text = "继续" + } + OfflineMapCityBean.ERROR -> { + if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility = + View.VISIBLE + binding.offlineMapDownloadBtn.text = "重试" + } + OfflineMapCityBean.DONE -> { + if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility = + View.INVISIBLE + binding.offlineMapDownloadBtn.text = "已完成" + } + OfflineMapCityBean.UPDATE -> { + if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility = + View.INVISIBLE + binding.offlineMapDownloadBtn.text = "更新" + } + } } override fun getItemViewType(position: Int): Int { return R.layout.adapter_offline_map_city } +} + -} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt index 23b74da5..1baa5823 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt @@ -10,17 +10,28 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import com.navinfo.omqs.databinding.FragmentOfflineMapCityListBinding +import com.navinfo.omqs.http.RetrofitNetworkServiceAPI +import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject /** * 离线地图城市列表 */ @AndroidEntryPoint class OfflineMapCityListFragment : Fragment() { + @Inject + lateinit var downloadManager: OfflineMapDownloadManager private var _binding: FragmentOfflineMapCityListBinding? = null private val viewModel by viewModels() private val binding get() = _binding!! - private val adapter: OfflineMapCityListAdapter by lazy { OfflineMapCityListAdapter() } + private val adapter: OfflineMapCityListAdapter by lazy { + OfflineMapCityListAdapter( + downloadManager, + requireContext() + ) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -33,8 +44,10 @@ class OfflineMapCityListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val layoutManager = LinearLayoutManager(context) - _binding!!.offlineMapCityListRecyclerview.layoutManager = layoutManager - _binding!!.offlineMapCityListRecyclerview.adapter = adapter + //// 设置 RecyclerView 的固定大小,避免在滚动时重新计算视图大小和布局,提高性能 + binding.offlineMapCityListRecyclerview.setHasFixedSize(true) + binding.offlineMapCityListRecyclerview.layoutManager = layoutManager + binding.offlineMapCityListRecyclerview.adapter = adapter viewModel.cityListLiveData.observe(viewLifecycleOwner) { adapter.refreshData(it) } @@ -44,6 +57,6 @@ class OfflineMapCityListFragment : Fragment() { override fun onDestroyView() { super.onDestroyView() _binding = null - Log.e("jingo","OfflineMapCityListFragment onDestroyView") + Log.e("jingo", "OfflineMapCityListFragment onDestroyView") } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt index 4eba17ae..93ddba13 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt @@ -32,7 +32,7 @@ class OfflineMapCityListViewModel @Inject constructor( viewModelScope.launch { when (val result = networkService.getOfflineMapCityList()) { is NetResult.Success -> { - cityListLiveData.postValue(result.data!!) + cityListLiveData.postValue(result.data?.sortedBy { bean -> bean.id }) } is NetResult.Error -> { Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT) diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapViewHolder.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapViewHolder.kt new file mode 100644 index 00000000..02a03583 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapViewHolder.kt @@ -0,0 +1,10 @@ +package com.navinfo.omqs.ui.fragment.offlinemap + +import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding +import com.navinfo.omqs.ui.other.BaseViewHolder + +class OfflineMapViewHolder(dataBinding: AdapterOfflineMapCityBinding) : BaseViewHolder(dataBinding) { + init{ + dataBinding.offlineMapDownloadBtn + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt index 33747c57..fa964626 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt @@ -1,6 +1,7 @@ package com.navinfo.omqs.ui.other import android.view.LayoutInflater +import android.view.View.OnClickListener import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.RecyclerView @@ -10,23 +11,51 @@ import androidx.recyclerview.widget.RecyclerView */ abstract class BaseRecyclerViewAdapter(var data: List = listOf()) : RecyclerView.Adapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { - return BaseViewHolder( - DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - viewType, - parent, - false - ) - ) - } + // private var recyclerView: RecyclerView? = null +// override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { +// +// +// +// return BaseViewHolder( +// DataBindingUtil.inflate( +// LayoutInflater.from(parent.context), +// viewType, +// parent, +// false +// ) +// ) +// } + override fun getItemCount(): Int { return data.size } - fun refreshData(newData:List){ + fun refreshData(newData: List) { this.data = newData this.notifyDataSetChanged() } + + + override fun onViewAttachedToWindow(holder: BaseViewHolder) { + super.onViewAttachedToWindow(holder) + holder.onStart() + } + + override fun onViewDetachedFromWindow(holder: BaseViewHolder) { + super.onViewDetachedFromWindow(holder) + holder.apply { + onStop() + } + } +// +// override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { +// super.onAttachedToRecyclerView(recyclerView) +// this.recyclerView = recyclerView +// } +// +// override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { +// super.onDetachedFromRecyclerView(recyclerView) +// this.recyclerView = null +// } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt b/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt index a06b61ca..e6f4d62c 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt @@ -1,11 +1,54 @@ package com.navinfo.omqs.ui.other -import androidx.databinding.ViewDataBinding +import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding /** * dataBinding viewHolder 基类 + * LifecycleRegistry 这是一个生命周期注册器,继承自 Lifecycle,LifecycleOwner 通过这个类来分发生命周期事件,并在 getLifecycle() 中返回 */ -open class BaseViewHolder(var dataBinding: ViewDataBinding) : - RecyclerView.ViewHolder(dataBinding.root) { +open class BaseViewHolder(val viewBinding: ViewBinding) : + RecyclerView.ViewHolder(viewBinding.root), LifecycleOwner { + private val lifecycleRegistry = LifecycleRegistry(this) + + init { +// dataBinding.lifecycleOwner = this + lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED + lifecycleRegistry.currentState = Lifecycle.State.CREATED + itemView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { + // View onDetached 的时候回调 onDestroy() + override fun onViewDetachedFromWindow(v: View) { + itemView.removeOnAttachStateChangeListener(this) + onDestroy() + } + + // View onAttached 的时候回调 onCreate() + override fun onViewAttachedToWindow(v: View) { + onStart() + } + }) + } + + fun onStart() { + lifecycleRegistry.currentState = Lifecycle.State.STARTED // + lifecycleRegistry.currentState = Lifecycle.State.RESUMED // ON_RESUME EVENT + } + + fun onStop() { + lifecycleRegistry.currentState = Lifecycle.State.STARTED // + lifecycleRegistry.currentState = Lifecycle.State.CREATED // ON_STOP EVENT + } + + fun onDestroy() { + lifecycleRegistry.currentState = Lifecycle.State.DESTROYED /// ON_DESTROY EVENT + } + + + override fun getLifecycle(): Lifecycle { + return lifecycleRegistry + } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt b/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt index 0f1180a6..aab03188 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt @@ -5,10 +5,13 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Rect -import android.opengl.ETC1.getHeight -import android.opengl.ETC1.getWidth import android.util.AttributeSet +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout import android.widget.ProgressBar +import com.navinfo.omqs.R /** @@ -18,8 +21,13 @@ class MyProgressBar : ProgressBar { private lateinit var mPaint: Paint private var text: String = "" private var rate = 0f + private lateinit var bar: ProgressBar constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { +// LayoutInflater.from(context).inflate( +// R.layout.my_projressbar, this, +// true +// ); initView() } @@ -33,6 +41,7 @@ class MyProgressBar : ProgressBar { mPaint.color = Color.BLUE } + @Synchronized override fun setProgress(progress: Int) { setText(progress) @@ -40,7 +49,7 @@ class MyProgressBar : ProgressBar { } private fun setText(progress: Int) { - rate = progress * 1.0f / this.getMax() + rate = progress * 1.0f / this.max val i = (rate * 100).toInt() text = "$i%" } @@ -57,8 +66,9 @@ class MyProgressBar : ProgressBar { // 如果为百分之百则在左边绘制。 x = width - rect.right } - val y: Int = 0 - rect.top - mPaint.textSize = 22f + mPaint.textSize = 24f + val y: Int = 10 - rect.top + canvas.drawText(text, x.toFloat(), y.toFloat(), mPaint) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/progress_bg.xml b/app/src/main/res/drawable/progress_bg.xml new file mode 100644 index 00000000..244083fc --- /dev/null +++ b/app/src/main/res/drawable/progress_bg.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index d1405c56..1d88e9f5 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -17,8 +17,7 @@ + android:layout_height="match_parent"> - - - - - - - - - + android:text="省市名称" + android:textColor="@color/white" + android:textSize="@dimen/default_font_size" /> - + - - + - + - - - \ No newline at end of file + + \ No newline at end of file diff --git a/collect-library/src/main/java/com/navinfo/collect/library/utils/GeometryTools.java b/collect-library/src/main/java/com/navinfo/collect/library/utils/GeometryTools.java index 4c6d0519..bf7066d2 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/utils/GeometryTools.java +++ b/collect-library/src/main/java/com/navinfo/collect/library/utils/GeometryTools.java @@ -429,7 +429,6 @@ public class GeometryTools { dList.add(lt + dis); total += dis; } - Log.e("jingo", "line lengh =" + total); total = total / 2; for (int i = 0; i < dList.size(); i++) { double a = dList.get(i); @@ -495,7 +494,6 @@ public class GeometryTools { dList.add(lt + dis); total += dis; } - Log.e("jingo", "line lengh =" + total); total = total / 2; for (int i = 0; i < dList.size(); i++) { double a = dList.get(i); From fa4ad254a59097f3f6bb6d1eb47c797c776cdb1e Mon Sep 17 00:00:00 2001 From: squallzhjch Date: Thu, 6 Apr 2023 10:46:19 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0realm=20=E5=9C=A8?= =?UTF-8?q?=E7=A6=BB=E7=BA=BF=E5=9C=B0=E5=9B=BE=E4=B8=AD=E7=9A=84=E4=BD=BF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/navinfo/omqs/Constant.kt | 12 + .../java/com/navinfo/omqs/OMQSApplication.kt | 7 +- .../navinfo/omqs/bean/OfflineMapCityBean.kt | 45 ---- .../omqs/bean/OfflineMapCityRealmObject.kt | 17 -- .../com/navinfo/omqs/hilt/GlobalModule.kt | 21 +- .../navinfo/omqs/hilt/MainActivityModule.kt | 8 +- .../com/navinfo/omqs/http/NetworkService.kt | 3 +- .../navinfo/omqs/http/NetworkServiceImpl.kt | 2 +- .../omqs/http/RetrofitNetworkServiceAPI.kt | 4 +- .../OfflineMapDownloadManager.kt | 40 ++- .../OfflineMapDownloadScope.kt | 233 ++++++------------ .../com/navinfo/omqs/tools/FileManager.kt | 95 +++++++ .../navinfo/omqs/tools/RealmCoroutineScope.kt | 57 +++++ .../java/com/navinfo/omqs/ui/MainActivity.kt | 6 - .../omqs/ui/activity/login/LoginActivity.kt | 6 + .../omqs/ui/activity/login/LoginViewModel.kt | 93 +++---- .../omqs/ui/activity/map/MainActivity.kt | 1 - .../omqs/ui/activity/map/MainViewModel.kt | 1 - .../offlinemap/OfflineMapCityListAdapter.kt | 34 ++- .../offlinemap/OfflineMapCityListFragment.kt | 1 - .../offlinemap/OfflineMapCityListViewModel.kt | 37 ++- .../fragment/offlinemap/OfflineMapFragment.kt | 1 - .../offlinemap/OfflineMapStateListFragment.kt | 1 - .../personalcenter/PersonalCenterFragment.kt | 6 - .../navinfo/omqs/ui/other/BaseViewHolder.kt | 1 + .../navinfo/omqs/ui/widget/MyProgressBar.kt | 5 +- .../library/data/entity/OfflineMapCityBean.kt | 65 ++--- .../data/handler/DataNiLocationHandler.kt | 1 - .../collect/library/map/NIMapController.kt | 3 - 29 files changed, 422 insertions(+), 384 deletions(-) delete mode 100644 app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt delete mode 100644 app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt create mode 100644 app/src/main/java/com/navinfo/omqs/tools/FileManager.kt create mode 100644 app/src/main/java/com/navinfo/omqs/tools/RealmCoroutineScope.kt diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt index 446bfc16..306ff4e3 100644 --- a/app/src/main/java/com/navinfo/omqs/Constant.kt +++ b/app/src/main/java/com/navinfo/omqs/Constant.kt @@ -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 /** diff --git a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt index 7205a54d..27e26fa3 100644 --- a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt +++ b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt @@ -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) } } \ 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 d4485441..00000000 --- a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt deleted file mode 100644 index 92461d60..00000000 --- a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt +++ /dev/null @@ -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 -} \ 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 ccfa89dc..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 对象 @@ -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) + } } \ 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 5587be3e..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,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销毁的时候同时销毁 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 34fa2cdf..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,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 网络请求接口 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 d38896f0..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,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 + id: String, lifecycleOwner: LifecycleOwner, observer: Observer ) { 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() } } - } \ 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 c518b504..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 @@ -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? = null +// private var observer: Observer? = null + private var lifecycleOwner: LifecycleOwner? = null /** - * + *通知UI更新 */ private val downloadData = MutableLiveData() @@ -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? = 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) { - if (observer != null) { - downloadData.removeObserver(observer!!) - } - this.observer = ob - downloadData.observe(lifecycleOwner, observer!!) + fun observer(owner: LifecycleOwner, ob: Observer) { + 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 + } + } } \ 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 index b0293ec3..6bad71e2 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt @@ -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 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 36c50b0b..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 @@ -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("检查离线地图...") + } } } 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 26072081..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 @@ -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 = 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() } - - } \ 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 0ec5416c..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 @@ -69,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/OfflineMapCityListAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt index 8894d104..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 @@ -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 { + 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() 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 1baa5823..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 @@ -57,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 93ddba13..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?.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().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/personalcenter/PersonalCenterFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt index 37d7f1dd..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 @@ -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 -> 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 e6f4d62c..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 @@ -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 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 aab03188..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 @@ -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 diff --git a/collect-library/src/main/java/com/navinfo/collect/library/data/entity/OfflineMapCityBean.kt b/collect-library/src/main/java/com/navinfo/collect/library/data/entity/OfflineMapCityBean.kt index e3791593..9d1f54fe 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/data/entity/OfflineMapCityBean.kt +++ b/collect-library/src/main/java/com/navinfo/collect/library/data/entity/OfflineMapCityBean.kt @@ -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(){ -// -// } -// } \ No newline at end of file diff --git a/collect-library/src/main/java/com/navinfo/collect/library/data/handler/DataNiLocationHandler.kt b/collect-library/src/main/java/com/navinfo/collect/library/data/handler/DataNiLocationHandler.kt index 2b0c66fb..5415baee 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/data/handler/DataNiLocationHandler.kt +++ b/collect-library/src/main/java/com/navinfo/collect/library/data/handler/DataNiLocationHandler.kt @@ -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}") } diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/NIMapController.kt b/collect-library/src/main/java/com/navinfo/collect/library/map/NIMapController.kt index fb6e7c1f..72ccc8dc 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/map/NIMapController.kt +++ b/collect-library/src/main/java/com/navinfo/collect/library/map/NIMapController.kt @@ -37,8 +37,5 @@ class NIMapController { mapView.setOptions(options) } - fun print() { - Log.e("jingo", "NIMapController 哈希code ${hashCode()}") - } }