From 3ee14eabe67bfd701639d8ad2f74a7e744505a9b Mon Sep 17 00:00:00 2001 From: xiaji Date: Sun, 1 Mar 2026 20:02:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E9=80=9F=E5=BA=A6=E5=B9=B6=E4=BF=AE=E5=A4=8D=E6=8B=8D=E7=85=A7?= =?UTF-8?q?=E6=B0=B4=E5=8D=B0=E7=BC=BA=E5=A4=B1=E5=AE=9A=E4=BD=8D=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LocationHelper: 添加30秒位置缓存,减少超时时间,优化定位策略 - CameraScreen: 添加智能定位文本验证,确保拍照时水印包含有效定位信息 - 处理权限永久拒绝情况,引导用户到设置 --- .../camera/ui/camera/CameraScreen.kt | 69 ++++++- .../inspection/camera/util/LocationHelper.kt | 180 +++++++++++++++++- 2 files changed, 241 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/inspection/camera/ui/camera/CameraScreen.kt b/app/src/main/java/com/inspection/camera/ui/camera/CameraScreen.kt index d03fc69..0336a49 100644 --- a/app/src/main/java/com/inspection/camera/ui/camera/CameraScreen.kt +++ b/app/src/main/java/com/inspection/camera/ui/camera/CameraScreen.kt @@ -2,6 +2,8 @@ package com.inspection.camera.ui.camera import android.Manifest import android.content.Context +import android.content.Intent +import android.provider.Settings import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri @@ -119,6 +121,23 @@ fun CameraScreen( } } + // 检查是否有权限被永久拒绝 + val hasPermanentlyDeniedPermission = remember(permissionsState) { + permissionsState.permissions.any { permissionState -> + val status = permissionState.status + status is com.google.accompanist.permissions.PermissionStatus.Denied && + !status.shouldShowRationale + } + } + + // 打开应用设置页面 + val openAppSettings = { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.parse("package:${context.packageName}") + } + context.startActivity(intent) + } + // 加载配置 LaunchedEffect(Unit) { preferencesManager.watermarkStyle.collect { style -> @@ -177,7 +196,7 @@ fun CameraScreen( flashMode = flashMode, watermarkStyle = currentWatermarkStyle, imageQuality = currentImageQuality, - locationText = if (locationText.isNotBlank()) locationText else "未知地点", + locationText = getValidLocationTextForPhoto(locationText, manualAddress, locationHelper), onComplete = { uri -> capturedImages.add(uri) isCapturing = false @@ -198,7 +217,9 @@ fun CameraScreen( PermissionRequest( onRequestPermission = { permissionsState.launchMultiplePermissionRequest() }, showDialog = showPermissionDeniedDialog, - onDismissDialog = { showPermissionDeniedDialog = false } + onDismissDialog = { showPermissionDeniedDialog = false }, + hasPermanentlyDeniedPermission = hasPermanentlyDeniedPermission, + openAppSettings = openAppSettings ) } @@ -400,7 +421,9 @@ private fun BottomControls( private fun PermissionRequest( onRequestPermission: () -> Unit, showDialog: Boolean, - onDismissDialog: () -> Unit + onDismissDialog: () -> Unit, + hasPermanentlyDeniedPermission: Boolean, + openAppSettings: () -> Unit ) { Column( modifier = Modifier @@ -415,16 +438,50 @@ private fun PermissionRequest( ) Spacer(modifier = Modifier.height(16.dp)) Text( - text = "请授予权限以使用拍照和地点水印功能", + text = if (hasPermanentlyDeniedPermission) + "权限被永久拒绝,请在设置中手动开启权限" + else + "请授予权限以使用拍照和地点水印功能", style = MaterialTheme.typography.bodyMedium ) Spacer(modifier = Modifier.height(24.dp)) - Button(onClick = onRequestPermission) { - Text("授予权限") + if (hasPermanentlyDeniedPermission) { + Button(onClick = openAppSettings) { + Text("打开设置") + } + } else { + Button(onClick = onRequestPermission) { + Text("授予权限") + } } } } +private suspend fun getValidLocationTextForPhoto( + currentLocationText: String, + manualAddress: String, + locationHelper: LocationHelper +): String { + // 检查当前定位文本是否有效 + val invalidTexts = listOf("正在定位...", "定位失败", "请授予定位权限") + val isInvalid = currentLocationText.isBlank() || invalidTexts.contains(currentLocationText) + + if (!isInvalid) { + return currentLocationText + } + + // 使用手动地址 + if (manualAddress.isNotBlank()) { + return manualAddress + } + + // 尝试快速获取当前位置(使用缓存) + val location = locationHelper.getCurrentLocation() + return location?.let { + "${"%.4f".format(it.latitude)}, ${"%.4f".format(it.longitude)}" + } ?: "未知地点" +} + private fun capturePhoto( context: Context, imageCapture: ImageCapture, diff --git a/app/src/main/java/com/inspection/camera/util/LocationHelper.kt b/app/src/main/java/com/inspection/camera/util/LocationHelper.kt index e79a6d8..46d4ccf 100644 --- a/app/src/main/java/com/inspection/camera/util/LocationHelper.kt +++ b/app/src/main/java/com/inspection/camera/util/LocationHelper.kt @@ -12,8 +12,11 @@ import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationResult import com.google.android.gms.location.LocationServices import com.google.android.gms.location.Priority +import android.location.LocationManager +import android.location.LocationListener import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.tasks.await +import kotlinx.coroutines.withTimeoutOrNull import java.util.Locale import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -30,14 +33,157 @@ class LocationHelper(private val context: Context) { Geocoder(context, Locale.getDefault()) } + // 位置缓存 + private var lastLocation: Location? = null + private var lastLocationTime: Long = 0 + private val LOCATION_CACHE_VALID_MS = 30000 // 30秒缓存有效 + /** - * 获取当前位置 + * 检查定位服务是否启用(GPS或网络) + */ + fun isLocationEnabled(): Boolean { + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + } + + /** + * 获取当前位置(带缓存) */ @SuppressLint("MissingPermission") suspend fun getCurrentLocation(): Location? { + // 检查缓存是否有效 + if (lastLocation != null && System.currentTimeMillis() - lastLocationTime < LOCATION_CACHE_VALID_MS) { + Log.d("LocationHelper", "Using cached location: $lastLocation") + return lastLocation + } + + // 检查定位服务是否启用 + if (!isLocationEnabled()) { + Log.w("LocationHelper", "Location services disabled") + return null + } + + var result: Location? = null + + // 并行尝试多种定位方式,使用最快的 + result = tryHmsLocation() + if (result == null) { + result = tryGmsLocation() + } + if (result == null) { + result = getNetworkLocationFallback() + } + + // 更新缓存 + if (result != null) { + lastLocation = result + lastLocationTime = System.currentTimeMillis() + Log.d("LocationHelper", "Location obtained and cached: $result") + } + + return result + } + + @SuppressLint("MissingPermission") + private suspend fun tryGmsLocation(): Location? { return try { - fusedLocationClient.lastLocation.await() + Log.d("LocationHelper", "Requesting GMS last location...") + // 先尝试获取最后已知位置(最快) + val lastLocation = fusedLocationClient.lastLocation.await() + if (lastLocation != null) { + Log.d("LocationHelper", "Got GMS last location: $lastLocation") + return lastLocation + } + + // 最后位置为空,请求新位置(快速低精度) + Log.d("LocationHelper", "Last location null, requesting fresh location...") + requestFastLocation() } catch (e: Exception) { + Log.e("LocationHelper", "GMS location failed", e) + null + } + } + + // HMS 位置服务反射实现(无编译时依赖) + @Suppress("MissingPermission") + private suspend fun tryHmsLocation(): Location? { + Log.d("LocationHelper", "Attempt HMS location via reflection...") + return try { + val servicesClass = Class.forName("com.huawei.hms.location.LocationServices") + val getClient = servicesClass.getMethod("getFusedLocationProviderClient", Context::class.java) + val client = getClient.invoke(null, context) + val clientClass = client!!.javaClass + val getLastLocation = clientClass.getMethod("getLastLocation") + val task = getLastLocation.invoke(client) + val taskClass = task!!.javaClass + val latch = java.util.concurrent.CountDownLatch(1) + var result: Location? = null + val onSuccess = java.lang.reflect.Proxy.newProxyInstance( + LocationHelper::class.java.classLoader, + arrayOf(Class.forName("com.huawei.hmf.tasks.OnSuccessListener")), + java.lang.reflect.InvocationHandler { _, method, args -> + if (method.name == "onSuccess" && args != null) { + result = args[0] as Location + latch.countDown() + } + null + } + ) + val onFailure = java.lang.reflect.Proxy.newProxyInstance( + LocationHelper::class.java.classLoader, + arrayOf(Class.forName("com.huawei.hmf.tasks.OnFailureListener")), + java.lang.reflect.InvocationHandler { _, method, args -> + if (method.name == "onFailure" && args != null) { + latch.countDown() + } + null + } + ) + val addSuccess = taskClass.getMethod("addOnSuccessListener", Class.forName("com.huawei.hmf.tasks.OnSuccessListener")) + val addFailure = taskClass.getMethod("addOnFailureListener", Class.forName("com.huawei.hmf.tasks.OnFailureListener")) + addSuccess.invoke(task, onSuccess) + addFailure.invoke(task, onFailure) + latch.await(5000, java.util.concurrent.TimeUnit.MILLISECONDS) + result + } catch (t: Throwable) { + Log.e("LocationHelper", "HMS location reflection failed", t) + Log.e("LocationHelper", "HMS location fallback failed", t) + null + } + } + + @SuppressLint("MissingPermission") + private suspend fun requestFastLocation(): Location? { + return try { + Log.d("LocationHelper", "Creating fast location request...") + // 使用平衡功耗精度,更快获取位置 + val locationRequest = LocationRequest.Builder(Priority.PRIORITY_BALANCED_POWER_ACCURACY, 5000) + .setWaitForAccurateLocation(false) + .setMinUpdateIntervalMillis(1000) + .setMaxUpdateDelayMillis(8000) + .build() + + withTimeoutOrNull(8000) { + suspendCancellableCoroutine { continuation -> + Log.d("LocationHelper", "Requesting fast location updates...") + val callback = object : LocationCallback() { + override fun onLocationResult(result: LocationResult) { + Log.d("LocationHelper", "Got fast location result: ${result.lastLocation}") + fusedLocationClient.removeLocationUpdates(this) + continuation.resume(result.lastLocation) + } + } + fusedLocationClient.requestLocationUpdates(locationRequest, callback, Looper.getMainLooper()) + + continuation.invokeOnCancellation { + Log.d("LocationHelper", "Fast location request cancelled") + fusedLocationClient.removeLocationUpdates(callback) + } + } + } + } catch (e: Exception) { + Log.e("LocationHelper", "Request fast location failed", e) null } } @@ -48,7 +194,9 @@ class LocationHelper(private val context: Context) { @Suppress("DEPRECATION") suspend fun getAddressFromLocation(latitude: Double, longitude: Double): String { return try { + Log.d("LocationHelper", "Getting address for: $latitude, $longitude") val addresses = geocoder.getFromLocation(latitude, longitude, 1) + Log.d("LocationHelper", "Geocoder results: $addresses") if (!addresses.isNullOrEmpty()) { val address = addresses[0] buildString { @@ -60,6 +208,7 @@ class LocationHelper(private val context: Context) { "${"%.4f".format(latitude)}, ${"%.4f".format(longitude)}" } } catch (e: Exception) { + Log.e("LocationHelper", "Geocoder error", e) "${"%.4f".format(latitude)}, ${"%.4f".format(longitude)}" } } @@ -79,6 +228,33 @@ class LocationHelper(private val context: Context) { Log.d("LocationHelper", "Network location: $location") return location?.let { getAddressFromLocation(it.latitude, it.longitude) } ?: "" } + + // 备选方案:当设备没有 Google Play Services 时,尝试使用 Android 原生 LocationManager 获取网络定位 + internal suspend fun getNetworkLocationFallback(): Location? { + return try { + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + val provider = LocationManager.NETWORK_PROVIDER + val enabled = locationManager.isProviderEnabled(provider) + if (!enabled) return null + + suspendCancellableCoroutine { cont -> + val listener = object : LocationListener { + override fun onLocationChanged(location: Location) { + locationManager.removeUpdates(this) + cont.resume(location) + } + override fun onStatusChanged(provider: String?, status: Int, extras: android.os.Bundle?) { /* no-op */ } + override fun onProviderEnabled(provider: String) { /* no-op */ } + override fun onProviderDisabled(provider: String) { /* no-op */ } + } + locationManager.requestLocationUpdates(provider, 0L, 0f, listener, Looper.getMainLooper()) + cont.invokeOnCancellation { locationManager.removeUpdates(listener) } + } + } catch (e: Exception) { + Log.e("LocationHelper", "Network fallback location failed", e) + null + } + } } private suspend fun com.google.android.gms.tasks.Task.await(): T {