locationListener 在前台服务 30 秒后不起作用
locationListener doesn't work after 30 seconds on foreground service
我创建了一个服务,可以查找用户的坐标,然后将其存储在 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 没有输出预期的结果。有什么想法吗?
我真的没有看到代码有任何问题,但我对 START_NOT_STICKY
有点怀疑。请尝试 START_STICKY
。
START_STICKY
If this service's process is killed while it is started (after
returning from onStartCommand(Intent, int, int)), then leave it in the
started state but don't retain this delivered intent. Later the system
will try to re-create the service. Because it is in the started state,
it will guarantee to call onStartCommand(Intent, int, int) after
creating the new service instance; if there are not any pending start
commands to be delivered to the service, it will be called with a null
intent object, so you must take care to check for this.
START_NOT_STICKY
If this service's process is killed while it is started (after
returning from onStartCommand(Intent, int, int)), and there are no new
start intents to deliver to it, then take the service out of the
started state and don't recreate until a future explicit call to
Context.startService(Intent). The service will not receive a
onStartCommand(Intent, int, int) call with a null Intent because it
will not be re-started if there are no pending Intents to deliver.
因此当您返回 START_NOT_STICKY
时,如果进程被终止,将不会再次调用 onStartCommand(),这是您初始化侦听器和 locationManager 的地方。
TL;DR:
将 android:foregroundServiceType="location"
添加到您的 Service's
清单条目。
解释
对于 Android 10,这一新行为与您所描述的完全相同:即使您可能正在使用前台服务,但在您的应用离开屏幕 30 秒后 - 位置更新停止。
您可能已经注意到,Android 10 台设备在授予位置权限(对于旧版 (API < 29) 应用或声明 ACCESS_BACKGROUND_LOCATION
权限):
- "Allow all the time"
- "Allow only while using this app"
"Allow only while using this app" 实际上意味着 "Allow while the app is visible onscreen"。这是因为用户现在可以根据该标准有选择地删除位置访问权限——甚至是前台服务。用户可以随时 更改此设置,即使您的应用是 运行.
Android docs 解释说解决方案 android:foregroundServiceType="location"
是为您的精确用例设计的:"Google Maps" 类应用程序,具有前台服务,但预计会继续如果用户切换到另一个应用程序,则处理位置数据。该技术称为 "continuing a user-initiated action",即使您的应用程序被放置在 "background".
中,它也允许您获取位置更新
(文档似乎在此处扩展了术语 "background" 的定义。过去,如果您有前台服务,您的应用会被视为 "in the foreground" —— 至少任务优先级、Doze 等目的。现在看来,如果某个应用在过去 30 秒内未出现在屏幕上,则它被视为 "in the background",关于位置访问。)
我不确定当您设置特定的 foregroundServiceType
时 UI 会发生什么变化(例如 Google Play 商店)。无论如何,在我看来,用户不太可能反对。
ANDROID 10 台设备的其他解决方案
或者,您可以将 targetSdkVersion
声明为 28 或更小,这将使您的应用在 "compatibility mode".
位置运行
您也可以选择获得 ACCESS_BACKGROUND_LOCATION
权限,但 docs 警告:
If your app doesn't require location access while running in the background, it's highly recommended that you not request ACCESS_BACKGROUND_LOCATION
...
对于您的用例,不需要这种方法,因为您的 Activity
用于启动您的 Service
;在 Service
开始获取后台位置更新之前,您可以保证您的应用至少出现在屏幕上一次。 (至少,我 假设 这就是 OS 确定 "user-initiated action" 开始的方式。据推测,foregroundServiceType
方法将不起作用如果您从 JobScheduler
或 BroadcastReceiver
或其他内容开始 Service
。)
PS: 坚持 WakeLock
代码。如果您想以 10 秒的稳定速度持续获取更新,您需要让设备保持唤醒状态。
我创建了一个服务,可以查找用户的坐标,然后将其存储在 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 没有输出预期的结果。有什么想法吗?
我真的没有看到代码有任何问题,但我对 START_NOT_STICKY
有点怀疑。请尝试 START_STICKY
。
START_STICKY
If this service's process is killed while it is started (after returning from onStartCommand(Intent, int, int)), then leave it in the started state but don't retain this delivered intent. Later the system will try to re-create the service. Because it is in the started state, it will guarantee to call onStartCommand(Intent, int, int) after creating the new service instance; if there are not any pending start commands to be delivered to the service, it will be called with a null intent object, so you must take care to check for this.
START_NOT_STICKY
If this service's process is killed while it is started (after returning from onStartCommand(Intent, int, int)), and there are no new start intents to deliver to it, then take the service out of the started state and don't recreate until a future explicit call to Context.startService(Intent). The service will not receive a onStartCommand(Intent, int, int) call with a null Intent because it will not be re-started if there are no pending Intents to deliver.
因此当您返回 START_NOT_STICKY
时,如果进程被终止,将不会再次调用 onStartCommand(),这是您初始化侦听器和 locationManager 的地方。
TL;DR:
将 android:foregroundServiceType="location"
添加到您的 Service's
清单条目。
解释
对于 Android 10,这一新行为与您所描述的完全相同:即使您可能正在使用前台服务,但在您的应用离开屏幕 30 秒后 - 位置更新停止。
您可能已经注意到,Android 10 台设备在授予位置权限(对于旧版 (API < 29) 应用或声明 ACCESS_BACKGROUND_LOCATION
权限):
- "Allow all the time"
- "Allow only while using this app"
"Allow only while using this app" 实际上意味着 "Allow while the app is visible onscreen"。这是因为用户现在可以根据该标准有选择地删除位置访问权限——甚至是前台服务。用户可以随时 更改此设置,即使您的应用是 运行.
Android docs 解释说解决方案 android:foregroundServiceType="location"
是为您的精确用例设计的:"Google Maps" 类应用程序,具有前台服务,但预计会继续如果用户切换到另一个应用程序,则处理位置数据。该技术称为 "continuing a user-initiated action",即使您的应用程序被放置在 "background".
(文档似乎在此处扩展了术语 "background" 的定义。过去,如果您有前台服务,您的应用会被视为 "in the foreground" —— 至少任务优先级、Doze 等目的。现在看来,如果某个应用在过去 30 秒内未出现在屏幕上,则它被视为 "in the background",关于位置访问。)
我不确定当您设置特定的 foregroundServiceType
时 UI 会发生什么变化(例如 Google Play 商店)。无论如何,在我看来,用户不太可能反对。
ANDROID 10 台设备的其他解决方案
或者,您可以将 targetSdkVersion
声明为 28 或更小,这将使您的应用在 "compatibility mode".
您也可以选择获得 ACCESS_BACKGROUND_LOCATION
权限,但 docs 警告:
If your app doesn't require location access while running in the background, it's highly recommended that you not request
ACCESS_BACKGROUND_LOCATION
...
对于您的用例,不需要这种方法,因为您的 Activity
用于启动您的 Service
;在 Service
开始获取后台位置更新之前,您可以保证您的应用至少出现在屏幕上一次。 (至少,我 假设 这就是 OS 确定 "user-initiated action" 开始的方式。据推测,foregroundServiceType
方法将不起作用如果您从 JobScheduler
或 BroadcastReceiver
或其他内容开始 Service
。)
PS: 坚持 WakeLock
代码。如果您想以 10 秒的稳定速度持续获取更新,您需要让设备保持唤醒状态。