位置侦听器在前台服务上 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;DR:

android:foregroundServiceType="location"添加到您的Service's清单条目中。

解释

对于Android 10来说,这种新行为正是你所描述的:即使你可能正在使用前台服务,在你的应用程序离开屏幕30秒后,位置更新就会停止。

您可能已经注意到,Android 10设备在授予位置权限时为用户提供了两个新的选择(对于传统(API<29)应用程序或声明ACCESS_BACKGROUND_LOCATION权限的应用程序):

  • "允许所有时间">
  • "仅在使用此应用时允许">

"仅在使用此应用程序时允许"实际上意味着"在应用程序在屏幕上可见时允许"。这是因为用户现在可以根据该标准选择性地删除位置访问,甚至是前台服务。用户可以随时更改此设置,即使您的应用程序正在运行。

安卓文档解释说,android:foregroundServiceType="location"解决方案适用于您的精确用例:类似"谷歌地图"的应用程序,具有前台服务,但如果用户切换到另一个应用程序,预计将继续处理位置数据。这项技术被称为"继续用户发起的操作",即使你的应用程序被放在"后台",它也可以让你获得位置更新。

(在这里,文档似乎正在扩展"后台"一词的定义。过去,如果你有前台服务,你的应用程序被认为是"在前台"——至少出于任务优先级、Doze等目的。现在,如果应用程序在过去30秒内没有出现在屏幕上,它似乎被认为是在位置访问方面的"后台"。)

我不确定当你设置一个特定的foregroundServiceType时会发生什么UI变化(比如在Google Play商店中)。无论如何,在我看来,用户不太可能反对。

安卓10设备的其他解决方案

或者,您可以声明targetSdkVersion为28或更低,这将使您的应用程序在位置"兼容模式"下运行。

您也可以选择获得ACCESS_BACKGROUND_LOCATION权限,但文档提醒您不要这样做:

如果您的应用程序在后台运行时不需要位置访问,强烈建议您不要请求ACCESS_BACKGROUND_LOCATION。。。

对于您的用例来说,这种方法不是必需的,因为您的Activity用于启动Service;在Service开始获取后台位置更新之前,您可以保证您的应用程序至少在屏幕上出现过一次。(至少,我假设操作系统就是这样决定"用户启动的操作"的开始的。假设,如果您从JobSchedulerBroadcastReceiver或其他什么启动ServiceforegroundServiceType方法将不起作用。)

PS:抓紧那个WakeLock代码。如果你想以稳定的10秒速度获取更新,你需要让设备保持清醒。

我确实没有看到代码中有任何问题,但我对START_NOT_STICKY有点怀疑。请尝试START_STICKY

START_STICKY

如果此服务的进程在启动时被终止(之后从onStartCommand(Intent,int,int)返回),然后将其保留在已启动状态,但不保留此已交付的意图。稍后系统将尝试重新创建服务。因为它处于启动状态,它将保证在之后调用onStartCommand(Intent,int,int)创建所述新的服务实例;如果没有任何挂起的启动要传递到服务的命令,将使用null调用intent对象,所以您必须小心检查。

START_NOT_STICKY

如果此服务的进程在启动时被终止(之后从onStartCommand返回(Intent,int,int)),并且没有新的启动意图交付给它,然后从已启动状态,并且在将来显式调用之前不重新创建Context.startService(Intent)。该服务将不会收到onStartCommand(Intent,int,int)调用的Intent为null,因为它如果没有待交付的意向,将不会重新启动。

因此,当您返回START_NOT_STICKY时,如果进程被终止,将不会再次调用onStartCommand(),这是初始化侦听器和位置管理器的地方。

最新更新