You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)可解决问题,但硬编码延迟的方式缺乏可靠性和优雅性。


原因分析

  1. 前后台定位权限差异:
    Android 10(API 29)及以上版本对后台定位有严格限制,需单独申请ACCESS_BACKGROUND_LOCATION权限。前台时应用处于活跃状态,定位服务优先级高,开启后能快速响应请求;后台时系统为省电会限制定位模块启动速度,导致调用requestSingleUpdate时,Provider虽已开启但未完成初始化,无法立即返回位置。
  2. 模拟器与真机的硬件差异:
    模拟器无真实硬件定位模块,定位服务是模拟实现,启动速度远快于真机的硬件模块,因此不会出现延迟问题。
  3. 广播接收器生命周期限制:
    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可确保后台任务持续执行直到获取位置:

  1. 定义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);
    }
}
  1. 在广播接收器中触发任务:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 23:01:13