Android定位服务在特定设备每2秒重复更新问题排查
问题分析与解决方案
首先,咱们来拆解你遇到的这个定位重复触发的问题:
为什么部分设备会每2秒重复获取定位?
你在调用mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mlocListener, looper)时设置的时间间隔和距离间隔都是0,这在不同Android版本和厂商定制ROM里的行为差异很大:
- 在Android 6.0(API 23)及更早的系统中,当这两个参数为0时,GPS模块会以硬件支持的最高频率返回位置更新。像三星Galaxy S6这类设备的定制ROM,把这个“最高频率”设定成了每2秒一次。
- 更关键的是,你在
onLocationChanged里的操作顺序有问题:先调用了handlerThread.quit(),再尝试移除定位监听。这可能导致Looper已经退出,移除监听的代码没被系统正确执行,后续的定位更新还会持续触发。而Android 7.0+系统对定位服务的调度做了优化,所以你的S7表现正常。
现有代码的快速修复方案
如果你暂时不想迁移到Google Play定位API,可以按以下步骤修改现有代码:
调整定位请求参数:
如果你只需要一次定位,优先使用requestSingleUpdate(虽然API 29已弃用,但对Android 6+仍有效),或者把时间间隔设为较大的值(比如10000ms)、距离间隔设为100m,避免过度频繁的更新。修正
onLocationChanged的操作顺序:
先移除定位监听,再退出线程,最后停止服务,确保移除操作被正确执行:@Override public void onLocationChanged(Location loc) { mLoc = loc; // 保留你的日志、网络请求、Toast代码... // 第一步:先移除定位监听,阻断后续更新 try { mlocManager.removeUpdates(mlocListener); } catch(Exception e){ Log.e(TAG, "Failed to remove location updates", e); } Log.e(TAG, "removed updates(TrackingService)"); // 第二步:安全退出线程(quitSafely比quit更可靠,确保当前任务完成) handlerThread.quitSafely(); // 第三步:停止服务 TrackingService.this.stopSelf(); Log.e(TAG, "called stopSelf on TrackingService"); }
迁移到Google Play Services Location API(推荐)
Google的Fused Location Provider API是官方推荐的定位方案,兼容性更好,还能自动切换GPS/网络定位,并且可以明确指定只获取一次定位。以下是在Service中实现的示例:
1. 添加依赖
在你的app模块的build.gradle中添加:
implementation 'com.google.android.gms:play-services-location:21.0.1'
2. 实现FusedTrackingService
public class FusedTrackingService extends Service { private static final String TAG = FusedTrackingService.class.getSimpleName(); private FusedLocationProviderClient fusedLocationClient; private LocationCallback locationCallback; private AppObj appObj; @Override public void onCreate() { super.onCreate(); fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); appObj = (AppObj) getApplication(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.e(TAG, "inside fused tracking service onStartCommand"); requestSingleLocationUpdate(); // 设置45秒超时,防止一直等待定位 new Handler().postDelayed(this::endServiceWithNoLocation, 45 * 1000); return START_NOT_STICKY; } private void requestSingleLocationUpdate() { LocationRequest locationRequest = LocationRequest.create(); locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); locationRequest.setNumUpdates(1); // 核心:明确只请求一次定位更新 locationRequest.setInterval(0); locationRequest.setFastestInterval(0); locationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { if (locationResult == null || locationResult.getLocations().isEmpty()) { endServiceWithNoLocation(); return; } Location loc = locationResult.getLocations().get(0); Log.e(TAG, "got location: " + loc.getLatitude() + ", " + loc.getLongitude()); // 处理定位数据,发送到Web服务 DateTime dt = new DateTime(DateTimeZone.UTC); DateTimeFormatter df3 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS") .withLocale(Locale.UK) .withOffsetParsed(); String formattedNowTime3 = df3.print(dt); appObj.webService.sendUserLocation(formattedNowTime3, String.valueOf(loc.getLatitude()), String.valueOf(loc.getLongitude())); // 显示Toast new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(getApplicationContext(), "co-ods = " + loc.getLatitude() + " " + loc.getLongitude(), Toast.LENGTH_LONG).show()); // 清理资源并停止服务 fusedLocationClient.removeLocationUpdates(locationCallback); stopSelf(); } }; // 检查定位权限(需提前在Manifest声明并请求运行时权限) if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); } else { endServiceWithNoLocation(); } } private void endServiceWithNoLocation() { DateTime dt = new DateTime(); DateTimeFormatter df3 = DateTimeFormat.forPattern("yyyy-MM-dd H:mm:ss.SSS"); String formattedNowTime3 = df3.print(dt); appObj.webService.sendUserLocation(formattedNowTime3, "NO_LAT", "NO_LON"); if (locationCallback != null) { fusedLocationClient.removeLocationUpdates(locationCallback); } stopSelf(); Log.e(TAG, "service ended with no location"); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); if (locationCallback != null) { fusedLocationClient.removeLocationUpdates(locationCallback); } } }
这个示例中,setNumUpdates(1)会告诉定位服务只返回一次定位结果,从根本上避免了重复触发的问题,同时兼容性覆盖绝大多数Android设备。
内容的提问来源于stack exchange,提问作者turtleboy




