android - locationListener 在前台服务 30 秒后不起作用
问题描述
我创建了一个服务,它查找用户的坐标并将其存储在 SQLite 数据库中。
public class GPS_Service extends Service {
DatabaseHelper myDb;
private LocationListener locationListener;
private LocationManager locationManager;
private String latitude, longitude;
@Override
public void onCreate() {
super.onCreate();
myDb = new DatabaseHelper(this);
}
@SuppressLint("MissingPermission")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Service")
.setContentText("Coordinates Location Running")
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.d("myTag", "Hello");
latitude = String.valueOf(location.getLatitude());
longitude = String.valueOf(location.getLongitude());
insertCoordinates(latitude, longitude);
Intent i = new Intent("location_update");
i.putExtra("latitude", latitude);
i.putExtra("longitude",longitude);
sendBroadcast(i);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
Intent i = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
};
locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 0, locationListener);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if(locationManager != null)
locationManager.removeUpdates(locationListener);
}
private void insertCoordinates(String latitude, String longitude) {
boolean inserted = myDb.insertData(latitude, longitude); //Insert coordinates
//Check if insertion is completed
if(inserted)
Toast.makeText(GPS_Service.this, "Coordinates Inserted", Toast.LENGTH_SHORT).show();
else
Toast.makeText(GPS_Service.this, "Coordinates Not Inserted", Toast.LENGTH_SHORT).show();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
我可以像这样从 MainActivity 启动或停止服务
private void enable_buttons() {
buttonStartService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent serviceIntent = new Intent(getApplicationContext(), GPS_Service.class);
//Checks if the SDK version is higher than 26 to act accordingly
ContextCompat.startForegroundService(MainActivity.this, serviceIntent);
}
});
buttonStopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent serviceIntent = new Intent(MainActivity.this, GPS_Service.class);
stopService(serviceIntent);
}
});
}
问题是,当我启动此服务时,如果我完全关闭应用程序或将其留在后台,locationListener 将工作 30 秒然后停止。如果我重新打开该应用程序,该服务将继续从它停止的地方工作。如果服务正在运行,我还检查了开发人员选项,即使 locationListener 没有输出预期的结果。有任何想法吗?
解决方案
TL;博士:
添加android:foregroundServiceType="location"
到您的Service's
清单条目。
解释
对于 Android 10,这种新行为与您所描述的完全一样:即使您可能正在使用前台服务,但在您的应用离开屏幕 30 秒后 - 位置更新停止。
您可能已经注意到,Android 10 设备在授予位置权限时向用户提供了两个新选择(对于旧版 (API < 29) 应用程序或声明ACCESS_BACKGROUND_LOCATION
权限的应用程序):
- “一直允许”
- “仅在使用此应用程序时允许”
“仅在使用此应用程序时允许”实际上意味着“当应用程序在屏幕上可见时允许”。这是因为用户现在可以选择根据该标准选择性地删除位置访问——甚至是前台服务。用户可以随时更改此设置,即使您的应用正在运行。
Android 文档解释说,该解决方案适用android:foregroundServiceType="location"
于您的精确用例:类似于“Google 地图”的应用程序,它们具有前台服务,但如果用户切换到另一个应用程序,预计将继续处理位置数据。该技术被称为“继续用户发起的操作”,即使在您的应用程序被放置在“后台”之后,它也允许您获取位置更新。
(文档似乎在这里扩展了术语“后台”的定义。过去,如果您有前台服务,您的应用程序被认为是“在前台” - 至少出于任务优先级的目的,Doze ,等等。现在看来,如果某个应用程序在过去 30 秒内没有出现在屏幕上,那么就位置访问而言,它被认为是“在后台”。)
我不确定当您设置特定的foregroundServiceType
. 无论如何,在我看来,用户不太可能反对。
适用于 Android 10 设备的其他解决方案
或者,您可以将 a 声明targetSdkVersion
为 28 或更少,这将使您的应用程序在某个位置“兼容模式”下运行。
您还可以选择获得ACCESS_BACKGROUND_LOCATION
许可,但文档警告不要这样做:
如果您的应用在后台运行时不需要位置访问权限,强烈建议您不要请求
ACCESS_BACKGROUND_LOCATION
...
对于您的用例,这种方法不是必需的,因为您Activity
用于启动Service
; Service
在开始获取后台位置更新之前,您可以保证您的应用程序至少在屏幕上出现过一次。(至少,我认为这是操作系统确定“用户启动的操作”开始的方式。大概,如果您从 a或 a或其他东西开始,该foregroundServiceType
方法将不起作用。)Service
JobScheduler
BroadcastReceiver
PS:坚持那个WakeLock
代码。如果您想以稳定的 10 秒速度保持更新,您将需要让设备保持唤醒状态。
推荐阅读
- javascript - webRTC 如何强制从 javascript Web 应用程序客户端发送 PLI 数据包?
- mysql - 孤岛和间隙排序问题 MYSQL 8.0
- angular - PrimeNg ConfirmationService - 错误 NG6002
- android - 从另一个动态模块导航到某个片段
- passport.js - 使用 passport.js 的 Facebook 身份验证不起作用
- javascript - 当互联网连接突然中断时,websocket 超时是多少?
- sql - 使用 WHERE 在 PowerShell 中过滤 SQL 查询
- python - 如何修复 selenium.common.exceptions.NoSuchElementException:消息:没有这样的元素:无法找到制作运动鞋机器人的元素
- javascript - 仅在特定条件下触发 useEffect
- mysql - 在插入到 wordpress 中的自定义数据表之前合并来自 csv 的数据