fix: 优化定位速度并修复拍照水印缺失定位信息问题

- LocationHelper: 添加30秒位置缓存,减少超时时间,优化定位策略
- CameraScreen: 添加智能定位文本验证,确保拍照时水印包含有效定位信息
- 处理权限永久拒绝情况,引导用户到设置
This commit is contained in:
2026-03-01 20:02:23 +08:00
parent 247f5f31a5
commit 3ee14eabe6
2 changed files with 241 additions and 8 deletions

View File

@@ -2,6 +2,8 @@ package com.inspection.camera.ui.camera
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri 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) { LaunchedEffect(Unit) {
preferencesManager.watermarkStyle.collect { style -> preferencesManager.watermarkStyle.collect { style ->
@@ -177,7 +196,7 @@ fun CameraScreen(
flashMode = flashMode, flashMode = flashMode,
watermarkStyle = currentWatermarkStyle, watermarkStyle = currentWatermarkStyle,
imageQuality = currentImageQuality, imageQuality = currentImageQuality,
locationText = if (locationText.isNotBlank()) locationText else "未知地点", locationText = getValidLocationTextForPhoto(locationText, manualAddress, locationHelper),
onComplete = { uri -> onComplete = { uri ->
capturedImages.add(uri) capturedImages.add(uri)
isCapturing = false isCapturing = false
@@ -198,7 +217,9 @@ fun CameraScreen(
PermissionRequest( PermissionRequest(
onRequestPermission = { permissionsState.launchMultiplePermissionRequest() }, onRequestPermission = { permissionsState.launchMultiplePermissionRequest() },
showDialog = showPermissionDeniedDialog, showDialog = showPermissionDeniedDialog,
onDismissDialog = { showPermissionDeniedDialog = false } onDismissDialog = { showPermissionDeniedDialog = false },
hasPermanentlyDeniedPermission = hasPermanentlyDeniedPermission,
openAppSettings = openAppSettings
) )
} }
@@ -400,7 +421,9 @@ private fun BottomControls(
private fun PermissionRequest( private fun PermissionRequest(
onRequestPermission: () -> Unit, onRequestPermission: () -> Unit,
showDialog: Boolean, showDialog: Boolean,
onDismissDialog: () -> Unit onDismissDialog: () -> Unit,
hasPermanentlyDeniedPermission: Boolean,
openAppSettings: () -> Unit
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -415,14 +438,48 @@ private fun PermissionRequest(
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = "请授予权限以使用拍照和地点水印功能", text = if (hasPermanentlyDeniedPermission)
"权限被永久拒绝,请在设置中手动开启权限"
else
"请授予权限以使用拍照和地点水印功能",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
if (hasPermanentlyDeniedPermission) {
Button(onClick = openAppSettings) {
Text("打开设置")
}
} else {
Button(onClick = onRequestPermission) { Button(onClick = onRequestPermission) {
Text("授予权限") 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( private fun capturePhoto(

View File

@@ -12,8 +12,11 @@ import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority import com.google.android.gms.location.Priority
import android.location.LocationManager
import android.location.LocationListener
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.tasks.await import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withTimeoutOrNull
import java.util.Locale import java.util.Locale
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
@@ -30,14 +33,157 @@ class LocationHelper(private val context: Context) {
Geocoder(context, Locale.getDefault()) 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") @SuppressLint("MissingPermission")
suspend fun getCurrentLocation(): Location? { 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 { 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) { } 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 null
} }
} }
@@ -48,7 +194,9 @@ class LocationHelper(private val context: Context) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
suspend fun getAddressFromLocation(latitude: Double, longitude: Double): String { suspend fun getAddressFromLocation(latitude: Double, longitude: Double): String {
return try { return try {
Log.d("LocationHelper", "Getting address for: $latitude, $longitude")
val addresses = geocoder.getFromLocation(latitude, longitude, 1) val addresses = geocoder.getFromLocation(latitude, longitude, 1)
Log.d("LocationHelper", "Geocoder results: $addresses")
if (!addresses.isNullOrEmpty()) { if (!addresses.isNullOrEmpty()) {
val address = addresses[0] val address = addresses[0]
buildString { buildString {
@@ -60,6 +208,7 @@ class LocationHelper(private val context: Context) {
"${"%.4f".format(latitude)}, ${"%.4f".format(longitude)}" "${"%.4f".format(latitude)}, ${"%.4f".format(longitude)}"
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("LocationHelper", "Geocoder error", e)
"${"%.4f".format(latitude)}, ${"%.4f".format(longitude)}" "${"%.4f".format(latitude)}, ${"%.4f".format(longitude)}"
} }
} }
@@ -79,6 +228,33 @@ class LocationHelper(private val context: Context) {
Log.d("LocationHelper", "Network location: $location") Log.d("LocationHelper", "Network location: $location")
return location?.let { getAddressFromLocation(it.latitude, it.longitude) } ?: "" 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 <T> com.google.android.gms.tasks.Task<T>.await(): T { private suspend fun <T> com.google.android.gms.tasks.Task<T>.await(): T {