From ca1be58db2b9b93d8f8b85861112d18f826f3ef8 Mon Sep 17 00:00:00 2001 From: squallzhjch Date: Thu, 30 Mar 2023 16:08:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A6=BB=E7=BA=BF=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E4=B8=8B=E8=BD=BD=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/navinfo/omqs/Constant.kt | 7 + .../com/navinfo/omqs/hilt/GlobalModule.kt | 10 +- .../navinfo/omqs/hilt/MainActivityModule.kt | 16 ++- .../OfflineMapDownloadManager.kt | 48 +++++++ .../OfflineMapDownloadScope.kt | 26 ++++ .../omqs/ui/activity/login/LoginActivity.kt | 72 +++++++++- .../omqs/ui/activity/login/LoginViewModel.kt | 128 +++++++++++++++++- .../omqs/ui/activity/map/MainActivity.kt | 11 +- .../collect/library/map/NIMapController.kt | 4 +- .../map/handler/LayerManagerHandler.kt | 4 +- .../collect/library/system/Constant.java | 9 +- 11 files changed, 308 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt create mode 100644 app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt index 73cedcea..92d50246 100644 --- a/app/src/main/java/com/navinfo/omqs/Constant.kt +++ b/app/src/main/java/com/navinfo/omqs/Constant.kt @@ -2,6 +2,12 @@ package com.navinfo.omqs class Constant { companion object { + /** + * sd卡根目录 + */ + lateinit var ROOT_PATH: String + lateinit var MAP_PATH: String + /** * 服务器地址 */ @@ -14,6 +20,7 @@ class Constant { const val message_version_right_off = "1" //立即发送 const val MESSAGE_PAGE_SIZE = 30 //消息列表一页最多数量 + } } \ 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 34697b56..90cac2bb 100644 --- a/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt +++ b/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt @@ -25,11 +25,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 对象 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 f081b20e..92dd5207 100644 --- a/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt +++ b/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt @@ -1,22 +1,34 @@ package com.navinfo.omqs.hilt -import android.util.Log +import android.content.Context import com.navinfo.collect.library.map.NIMapController -import com.navinfo.omqs.ui.activity.map.MainViewModel +import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager 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) @Module class MainActivityModule { + /** + * 注入地图控制器,在activity范围内使用,单例 + */ @ActivityRetainedScoped @Provides fun providesMapController(): NIMapController = NIMapController() + /** + * 注入离线地图下载管理,在activity范围内使用,单例 + */ + @ActivityRetainedScoped + @Provides + fun providesOfflineMapDownloadManager(@ActivityContext context: Context): OfflineMapDownloadManager = + OfflineMapDownloadManager(context) + /** * 实验失败,这样创建,viewmodel不会在activity销毁的时候同时销毁 */ 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 new file mode 100644 index 00000000..cc720498 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt @@ -0,0 +1,48 @@ +package com.navinfo.omqs.http.offlinemapdownload + +import android.content.Context +import android.os.Environment +import android.text.TextUtils +import com.navinfo.omqs.Constant +import com.navinfo.omqs.bean.OfflineMapCityBean +import dagger.hilt.android.qualifiers.ActivityContext +import java.io.Serializable +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject + +/** + * 管理离线地图下载 + */ +class OfflineMapDownloadManager @Inject constructor(@ActivityContext context: Context) { + /** + * 最多同时下载数量 + */ + private val MAX_SCOPE = 5 + + /** + * 存储有哪些城市需要下载 + */ + private val scopeMap: ConcurrentHashMap by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + ConcurrentHashMap() + } + + val downloadFolder: String? by lazy { + Constant.MAP_PATH + "/offline/" + } + + + /** + * 请求一个下载任务[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 + } +} \ 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 new file mode 100644 index 00000000..a51f93d5 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt @@ -0,0 +1,26 @@ +package com.navinfo.omqs.http.offlinemapdownload + +import androidx.lifecycle.MutableLiveData +import com.navinfo.omqs.bean.OfflineMapCityBean +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlin.coroutines.EmptyCoroutineContext + +/** + * 代表一个下载任务 + * [OfflineMapCityBean.id]将做为下载任务的唯一标识 + * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.download]获取此对象 + * 这是一个协程作用域, + * EmptyCoroutineContext 表示一个不包含任何元素的协程上下文,它通常用于创建新的协程上下文,或者作为协程上下文的基础。 + */ +class OfflineMapDownloadScope(cityBean: OfflineMapCityBean) : CoroutineScope by CoroutineScope(EmptyCoroutineContext) { + /** + * + */ + private var downloadJob: Job? = null + + /** + * + */ + private val downloadData = MutableLiveData() +} \ 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 32c8c503..f4fc2611 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 @@ -2,12 +2,16 @@ package com.navinfo.omqs.ui.activity.login import android.content.Intent import android.os.Bundle +import android.util.Log +import android.widget.Toast import androidx.activity.viewModels +import androidx.appcompat.app.AlertDialog import androidx.databinding.DataBindingUtil +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.navinfo.omqs.R import com.navinfo.omqs.databinding.ActivityLoginBinding -import com.navinfo.omqs.ui.activity.map.MainActivity import com.navinfo.omqs.ui.activity.PermissionsActivity +import com.navinfo.omqs.ui.activity.map.MainActivity /** * 登陆页面 @@ -16,16 +20,72 @@ class LoginActivity : PermissionsActivity() { private lateinit var binding: ActivityLoginBinding private val viewModel by viewModels() - + private var loginDialog: AlertDialog? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_login) binding.loginUserModel = viewModel binding.lifecycleOwner = this binding.activity = this + 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) +// finish() + loginDialog?.dismiss() + loginDialog = null + } + LoginStatus.LOGIN_STATUS_CANCEL -> { + loginDialog?.dismiss() + loginDialog = null + } + } + } + } + + /** + * 登录dialog + */ + private fun loginDialog(message: String) { + if (loginDialog == null) { + loginDialog = MaterialAlertDialogBuilder( + this, com.google.android.material.R.style.MaterialAlertDialog_Material3 + ).setTitle("登录").setMessage(message).show() + loginDialog!!.setCanceledOnTouchOutside(true) + loginDialog!!.setOnCancelListener { + viewModel.cancelLogin() + } + } else { + loginDialog!!.setMessage(message) + } + } + + //进应用根本不调用,待查 override fun onPermissionsGranted() { + Log.e("jingo","调用了吗") + } override fun onPermissionsDenied() { @@ -35,8 +95,8 @@ class LoginActivity : PermissionsActivity() { * 处理登录按钮 */ fun onClickLoginButton() { - val intent = Intent(this@LoginActivity, MainActivity::class.java) - startActivity(intent) -// finish() + viewModel.onClickLoginBtn( + this, binding.loginUsername.text.toString(), binding.loginPassword.text.toString() + ) } -} \ No newline at end of file +} 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 5701aa09..2d6f29b6 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 @@ -1,17 +1,65 @@ package com.navinfo.omqs.ui.activity.login +import android.content.Context +import android.util.Log import android.view.View +import android.widget.Toast import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.navinfo.omqs.Constant import com.navinfo.omqs.bean.LoginUserBean +import kotlinx.coroutines.* +import okio.IOException +import java.io.File -class LoginViewModel : ViewModel() { +enum class LoginStatus { + /** + * 访问服务器登陆中 + */ + LOGIN_STATUS_NET_LOADING, + + /** + * 初始化文件夹 + */ + LOGIN_STATUS_FOLDER_INIT, + + /** + * 创建文件夹失败 + */ + LOGIN_STATUS_FOLDER_FAILURE, + + /** + * 网络访问失败 + */ + LOGIN_STATUS_NET_FAILURE, + + /** + * 成功 + */ + LOGIN_STATUS_SUCCESS, + + /** + * 取消 + */ + LOGIN_STATUS_CANCEL, +} + +class LoginViewModel( +) : ViewModel() { + //用户信息 val loginUser: MutableLiveData = MutableLiveData() + //是不是登录成功 + val loginStatus: MutableLiveData = MutableLiveData() + + var jobLogin: Job? = null; + init { loginUser.value = LoginUserBean(username = "admin", password = "123456") } + /** * 处理注册按钮 */ @@ -19,4 +67,82 @@ class LoginViewModel : ViewModel() { loginUser.value!!.username = "admin2" loginUser.postValue(loginUser.value) } + + /** + * 点击 + */ + fun onClickLoginBtn(context: Context, userName: String, password: String) { + if (userName.isEmpty()) { + Toast.makeText(context, "请输入用户名", Toast.LENGTH_SHORT).show() + } + if (password.isEmpty()) { + Toast.makeText(context, "请输入密码", Toast.LENGTH_SHORT).show() + } + //不指定IO,会在主线程里运行 + jobLogin = viewModelScope.launch(Dispatchers.IO) { + loginCheck(context, userName, password) + Log.e("jingo", "运行完了1?${Thread.currentThread().name}") + } + } + + /** + * 如果不用挂起函数的方式,直接把下面这段代码替换到上面,在delay之后,线程和delay之前不是同一个,有啥影响未知。。。 + */ + + 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) + //文件夹初始化 + try { + loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT) + createRootFolder(context) + } catch (e: IOException) { + loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_FAILURE) + } + //假装解压文件等 + delay(1000) + loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS) + Log.e("jingo", "delay之后?${Thread.currentThread().name}") + +// } + } + + + @Throws(IOException::class) + private fun createRootFolder(context: Context) { + // 在SD卡创建项目目录 + val sdCardPath = context.getExternalFilesDir(null) + sdCardPath?.let { + Constant.ROOT_PATH = sdCardPath.absolutePath + Constant.MAP_PATH = Constant.ROOT_PATH + "/map/" + val file = File(Constant.MAP_PATH) + if (!file.exists()) { + file.mkdirs() + } + } + } + + /** + * 取消登录 + */ + fun cancelLogin() { + Log.e("jingo", "取消了?${Thread.currentThread().name}") + jobLogin?.let { + it.cancel() + loginStatus.postValue(LoginStatus.LOGIN_STATUS_CANCEL) + } + } + + override fun onCleared() { + 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 8eb6f6cb..cb03179e 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 @@ -1,12 +1,14 @@ package com.navinfo.omqs.ui.activity.map import android.os.Bundle +import android.provider.ContactsContract.Contacts import android.util.Log import androidx.activity.viewModels import androidx.core.view.WindowCompat import androidx.databinding.DataBindingUtil import androidx.lifecycle.viewModelScope 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.ui.activity.BaseActivity @@ -32,7 +34,12 @@ class MainActivity : BaseActivity() { binding = DataBindingUtil.setContentView(this, R.layout.activity_main) //初始化地图 - mapController.init(this, binding.mapView.mainActivityMap) + mapController.init( + this, + binding.mapView.mainActivityMap, + null, + Constant.ROOT_PATH + "/map/" + ) //关联生命周期 binding.lifecycleOwner = this //给xml转递对象 @@ -59,7 +66,7 @@ class MainActivity : BaseActivity() { super.onDestroy() mapController.mMapView.onDestroy() mapController.locationLayerHandler.stopLocation() - Log.e("jingo","MainActivity 销毁") + Log.e("jingo", "MainActivity 销毁") } override fun onResume() { 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 c959fe81..fb6e7c1f 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 @@ -5,6 +5,7 @@ import android.util.Log import com.navinfo.collect.library.map.handler.* import com.navinfo.collect.library.map.maphandler.MeasureLayerHandler import com.navinfo.collect.library.map.handler.ViewportHandler +import com.navinfo.collect.library.system.Constant /** * 地图控制器 @@ -22,7 +23,8 @@ class NIMapController { lateinit var measureLayerHandler: MeasureLayerHandler - fun init(context: Context, mapView: NIMapView, options: NIMapOptions? = null) { + fun init(context: Context, mapView: NIMapView, options: NIMapOptions? = null, mapPath: String) { + Constant.MAP_PATH = mapPath layerManagerHandler = LayerManagerHandler(context, mapView) locationLayerHandler = LocationLayerHandler(context, mapView) animationHandler = AnimationHandler(context, mapView) diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt b/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt index c5a4ca79..3db6b728 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt +++ b/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt @@ -5,6 +5,7 @@ import android.os.Environment import com.navinfo.collect.library.map.NIMapView import com.navinfo.collect.library.map.NIMapView.LAYER_GROUPS import com.navinfo.collect.library.map.source.NavinfoMapRastorTileSource +import com.navinfo.collect.library.system.Constant import okhttp3.Cache import okhttp3.OkHttpClient import org.oscim.layers.Layer @@ -18,7 +19,6 @@ import java.io.File */ class LayerManagerHandler(context: Context, mapView: NIMapView) : BaseHandler(context, mapView) { - lateinit var mLocationLayer: LocationLayer private var baseRasterLayer: Layer? = null init { @@ -62,7 +62,7 @@ class LayerManagerHandler(context: Context, mapView: NIMapView) : // 如果使用缓存 if (useCache) { val cacheDirectory: File = - File(Environment.getExternalStorageState() + "/" + "lalalal", "tiles-raster") + File(Constant.MAP_PATH, "tiles-raster") val cacheSize = 300 * 1024 * 1024 // 300 MB val cache = Cache(cacheDirectory, cacheSize.toLong()) builder.cache(cache) diff --git a/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java b/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java index bb5aeeb0..d6156d20 100644 --- a/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java +++ b/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java @@ -11,14 +11,7 @@ import java.util.Map; public class Constant { - public static String SD_PATH = Environment.getExternalStorageDirectory() + ""; - public static String ROOT_PATH = SD_PATH + "/NavinfoCollect"; - - public static String PHOTO_PATH = ROOT_PATH + "/image"; - - - public static double CONVERSION_FACTOR = 1000000d; - + public static String MAP_PATH = Environment.getExternalStorageDirectory() + "/map/"; public static void setVisibleTypeMap(Map visibleTypeMap) { Map HD_LAYER_VISIABLE_MAP= new HashMap<>();