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.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,

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.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 <T> com.google.android.gms.tasks.Task<T>.await(): T {