Android中LocationManager与BroadcastReceiver真机后台异常问题
问题:后台开启定位后无法获取坐标(仅真机异常)
场景与代码
需求是收到广播时获取当前坐标,相关代码如下:
广播接收器代码
public class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); if(!locationManager.isLocationEnabled()){ enableLocation(context); } Criteria locationCriteria = new Criteria(); locationCriteria.setAccuracy(Criteria.ACCURACY_FINE); locationManager.requestSingleUpdate(locationCriteria, new LocationListener() { @Override public void onLocationChanged(@NonNull Location location) { Log.d("SmsHandler", "Going to send coordinates: " + location.getLatitude() + " " + location.getLongitude()); doThings(); } @Override public void onProviderEnabled(@NonNull String provider) { Log.d("SmsHandler", "PROVIDER ENABLED"); } @Override public void onProviderDisabled(@NonNull String provider) { Log.d("SmsHandler", "PROVIDER DISABLED"); } }, null); }
开启定位的工具方法
public static boolean enableLocation(Context context){ if (Build.VERSION.SDK_INT < 19) { return false; } if(ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED){ return false; } return android.provider.Settings.Secure.putInt(context.getContentResolver(), android.provider.Settings.Secure.LOCATION_MODE, android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); }
异常现象
- 定位已开启时,代码运行正常,
doThings()可被正确调用 - 定位未开启且应用在前台/最近应用列表中时,运行正常
- 定位未开启且应用在后台时,模拟器正常,但真机仅输出以下日志,无法触发
onLocationChanged:2022-11-08 22:08:12.048 : PROVIDER DISABLED 2022-11-08 22:08:12.120 : PROVIDER ENABLED
临时方案(不推荐)
在请求定位前添加Thread.sleep(1000L)可解决问题,但硬编码延迟的方式缺乏可靠性和优雅性。
原因分析
- 前后台定位权限差异:
Android 10(API 29)及以上版本对后台定位有严格限制,需单独申请ACCESS_BACKGROUND_LOCATION权限。前台时应用处于活跃状态,定位服务优先级高,开启后能快速响应请求;后台时系统为省电会限制定位模块启动速度,导致调用requestSingleUpdate时,Provider虽已开启但未完成初始化,无法立即返回位置。 - 模拟器与真机的硬件差异:
模拟器无真实硬件定位模块,定位服务是模拟实现,启动速度远快于真机的硬件模块,因此不会出现延迟问题。 - 广播接收器生命周期限制:
BroadcastReceiver的onReceive运行在主线程且生命周期极短,开启定位后立即请求更新,可能在Provider就绪前,onReceive已执行完毕,对应的LocationListener被回收,无法接收后续位置回调。
优化解决方案
方案1:监听Provider状态,就绪后再请求定位
修改广播接收器,在onProviderEnabled回调中重新发起定位请求,确保Provider真正就绪后获取位置:
public class SmsReceiver extends BroadcastReceiver { private LocationManager locationManager; private Context appContext; private final LocationListener locationListener = new LocationListener() { @Override public void onLocationChanged(@NonNull Location location) { Log.d("SmsHandler", "Going to send coordinates: " + location.getLatitude() + " " + location.getLongitude()); doThings(); // 定位完成后移除监听,避免内存泄漏 if (locationManager != null) { locationManager.removeUpdates(this); } } @Override public void onProviderEnabled(@NonNull String provider) { Log.d("SmsHandler", "PROVIDER ENABLED"); // Provider开启后重新请求定位 Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); locationManager.requestSingleUpdate(criteria, this, null); } @Override public void onProviderDisabled(@NonNull String provider) { Log.d("SmsHandler", "PROVIDER DISABLED"); } }; @Override public void onReceive(Context context, Intent intent) { appContext = context.getApplicationContext(); locationManager = (LocationManager) appContext.getSystemService(Context.LOCATION_SERVICE); if (!locationManager.isLocationEnabled()) { boolean enabled = enableLocation(appContext); if (!enabled) { // 处理开启定位失败的情况 return; } } else { // 定位已开启,直接请求 Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); locationManager.requestSingleUpdate(criteria, locationListener, null); } // 注册Provider状态监听 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener); } // enableLocation方法保持不变 public static boolean enableLocation(Context context){ if (Build.VERSION.SDK_INT < 19) { return false; } if(ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED){ return false; } return android.provider.Settings.Secure.putInt(context.getContentResolver(), android.provider.Settings.Secure.LOCATION_MODE, android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); } private void doThings(){ // 你的业务逻辑 } }
方案2:用WorkManager处理后台定位任务
由于BroadcastReceiver生命周期限制,将定位任务交给WorkManager可确保后台任务持续执行直到获取位置:
- 定义Worker类:
public class LocationWorker extends Worker { private LocationManager locationManager; private final CompletableFuture<Location> locationFuture = new CompletableFuture<>(); public LocationWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } @NonNull @Override public Result doWork() { // 检查并开启定位 if (!locationManager.isLocationEnabled()) { boolean enabled = enableLocation(getApplicationContext()); if (!enabled) { return Result.failure(); } } LocationListener locationListener = new LocationListener() { @Override public void onLocationChanged(@NonNull Location location) { locationFuture.complete(location); locationManager.removeUpdates(this); } @Override public void onProviderEnabled(@NonNull String provider) { Log.d("SmsHandler", "PROVIDER ENABLED"); requestLocationUpdate(); } @Override public void onProviderDisabled(@NonNull String provider) { Log.d("SmsHandler", "PROVIDER DISABLED"); } }; // 注册监听 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener); requestLocationUpdate(); try { // 等待获取位置,设置30秒超时 Location location = locationFuture.get(30, TimeUnit.SECONDS); Log.d("SmsHandler", "Going to send coordinates: " + location.getLatitude() + " " + location.getLongitude()); doThings(location); return Result.success(); } catch (Exception e) { e.printStackTrace(); locationManager.removeUpdates(locationListener); return Result.retry(); } } private void requestLocationUpdate() { Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); locationManager.requestSingleUpdate(criteria, new LocationListener() { @Override public void onLocationChanged(@NonNull Location location) { locationFuture.complete(location); } @Override public void onProviderEnabled(@NonNull String provider) {} @Override public void onProviderDisabled(@NonNull String provider) {} }, null); } private void doThings(Location location) { // 业务逻辑 } // enableLocation方法保持不变 public static boolean enableLocation(Context context){ if (Build.VERSION.SDK_INT < 19) { return false; } if(ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED){ return false; } return android.provider.Settings.Secure.putInt(context.getContentResolver(), android.provider.Settings.Secure.LOCATION_MODE, android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); } }
- 在广播接收器中触发任务:
public class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { OneTimeWorkRequest locationWork = new OneTimeWorkRequest.Builder(LocationWorker.class).build(); WorkManager.getInstance(context).enqueue(locationWork); } }
方案3:配置后台定位权限
针对Android 10+,在AndroidManifest.xml中添加后台定位权限:
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
同时在运行时主动请求该权限,避免系统限制后台定位行为。
内容的提问来源于stack exchange,提问作者Alessandro
相关产品推荐
相关产品推荐

