如何通过AlarmManager实现每10分钟单次获取用户位置(非持续监听)
解决每10分钟单次获取后台位置的问题
之前用AlarmService启动LocationCheckService导致持续监听位置,确实会浪费电量和系统资源。我给你两种可行的方案,优先推荐官方的WorkManager,它能更好地适配Android各个版本的后台限制,即使应用退出(非强制停止)也能稳定执行任务。
方案一:WorkManager(官方推荐)
WorkManager是专门为后台延迟/周期性任务设计的组件,能自动处理系统休眠、后台限制等问题,不用自己操心复杂的版本适配细节。
1. 添加依赖
在你的build.gradle(app)文件中加入WorkManager的依赖:
dependencies { def work_version = "2.8.1" implementation "androidx.work:work-runtime:$work_version" }
2. 编写位置获取Worker类
这个Worker只执行单次位置请求,拿到位置后就结束任务,不会持续监听:
public class LocationFetchWorker extends Worker { private FusedLocationProviderClient fusedLocationClient; public LocationFetchWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); fusedLocationClient = LocationServices.getFusedLocationProviderClient(context); } @NonNull @Override public Result doWork() { // 先检查位置权限,无权限直接返回失败 if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return Result.failure(); } // 发起单次位置请求,平衡精度与电量消耗 fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, null) .addOnSuccessListener(location -> { if (location != null) { // 这里处理你的位置业务逻辑:比如保存到本地、上传服务器 double latitude = location.getLatitude(); double longitude = location.getLongitude(); // TODO: 替换为你的实际业务代码 } }) .addOnFailureListener(e -> { Log.e("LocationWorker", "获取位置失败: " + e.getMessage()); }); // 无论位置是否获取成功,Worker任务都视为完成 return Result.success(); } }
3. 调度周期性任务
在MainActivity的onCreate方法中配置每10分钟执行一次任务:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 配置每10分钟执行一次,预留5分钟弹性窗口让系统优化电池使用 PeriodicWorkRequest locationTask = new PeriodicWorkRequest.Builder( LocationFetchWorker.class, 10, TimeUnit.MINUTES, 5, TimeUnit.MINUTES) .build(); // 提交任务,确保同一时刻只有一个任务实例在运行 WorkManager.getInstance(this).enqueueUniquePeriodicWork( "LocationFetchTask", ExistingPeriodicWorkPolicy.REPLACE, locationTask); }
方案二:AlarmManager + 单次位置请求(兼容旧版本)
如果你坚持使用AlarmManager,需要改造LocationCheckService,让它仅执行单次位置请求,完成后立即停止服务:
1. 改造LocationCheckService
public class LocationCheckService extends Service { private FusedLocationProviderClient fusedLocationClient; private LocationCallback locationCallback; @Override public void onCreate() { super.onCreate(); fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); // 定义位置回调,拿到位置后立即停止监听并关闭服务 locationCallback = new LocationCallback() { @Override public void onLocationResult(@NonNull LocationResult locationResult) { super.onLocationResult(locationResult); if (locationResult != null && locationResult.getLastLocation() != null) { Location location = locationResult.getLastLocation(); // 处理位置信息 double lat = location.getLatitude(); double lng = location.getLongitude(); // TODO: 替换为你的实际业务代码 // 清理资源并停止服务 fusedLocationClient.removeLocationUpdates(locationCallback); stopSelf(); } } }; } @Override public int onStartCommand(Intent intent, int flags, int startId) { checkPermissionsAndFetchLocation(); // 系统杀死服务后无需重启 return START_NOT_STICKY; } private void checkPermissionsAndFetchLocation() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { stopSelf(); return; } // 配置单次位置请求参数 LocationRequest locationRequest = LocationRequest.create(); locationRequest.setPriority(Priority.PRIORITY_BALANCED_POWER_ACCURACY); locationRequest.setNumUpdates(1); // 仅获取一次位置更新 locationRequest.setInterval(0); locationRequest.setFastestInterval(0); fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); // 确保移除位置监听,防止内存泄漏 if (fusedLocationClient != null && locationCallback != null) { fusedLocationClient.removeLocationUpdates(locationCallback); } } }
2. 调整AlarmManager调度逻辑
注意Android 8.0+版本需要使用前台Service,否则服务会被系统立即杀死:
private void setupLocationAlarm() { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(this, LocationCheckService.class); PendingIntent pendingIntent; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { pendingIntent = PendingIntent.getForegroundService(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); } else { pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); } long interval = 10 * 60 * 1000; // 10分钟对应的毫秒数 long triggerTime = System.currentTimeMillis() + interval; // 适配不同Android版本的闹钟API if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } else { alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, interval, pendingIntent); } }
重要注意事项
- 权限要求:必须申请
ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION权限,Android 10及以上版本还需要ACCESS_BACKGROUND_LOCATION才能在后台获取位置。 - 电池优化:如果用户开启了电池优化,可能会影响任务执行,建议引导用户将应用加入电池优化白名单。
- 前台通知:使用AlarmManager方案时,Android 8.0+版本需要让Service显示前台通知,否则会被系统立即终止。
内容的提问来源于stack exchange,提问作者Hitrene
相关产品推荐
相关产品推荐

