上次,我找到了一个应用程序,该应用程序在Android Oreo的后台获取位置。我注意到,当我打开或单击“设置”或“相机”应用程序中的某些选项时,该系统会终止前台服务。而是从应用程序发出的通知,系统显示自己的标题为“使用电池的应用程序”。我在装有Android 8的平板电脑Huawei MediaPad T5上测试了一个应用程序。
我在“电池”选项中设置了“忽略优化”和“手动管理”。我还将onStartCommand中的返回值设置为START_STICKY。我在onCreate服务的方法中添加了WakeLock。我检查了一下,发现该设备具有可用RAM,尽管如此,Android还是终止了我的服务。我也尝试阻止“使用电池的应用程序”通知,但是系统终止了前台服务,并在稍后的某个时间重新启动了它。
我用过 亚行shell dumpsys活动服务 并且前台服务位于LRU列表的中间。尽管如此,Android 8还是终止了该服务。
我使用GitHub repository中的应用。
完整的LocationUpdatesService类:
public class LocationUpdatesService extends Service {
private static final String PACKAGE_NAME =
"com.google.android.gms.location.sample.locationupdatesforegroundservice";
private static final String TAG = "resPOINT";
/**
* The name of the channel for notifications.
*/
private static final String CHANNEL_ID = "channel_01";
static final String ACTION_BROADCAST = PACKAGE_NAME + ".broadcast";
static final String EXTRA_LOCATION = PACKAGE_NAME + ".location";
private static final String EXTRA_STARTED_FROM_NOTIFICATION = PACKAGE_NAME +
".started_from_notification";
private final IBinder mBinder = new LocalBinder();
/**
* The desired interval for location updates. Inexact. Updates may be more or less frequent.
*/
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 1000;
/**
* The fastest rate for active location updates. Updates will never be more frequent
* than this value.
*/
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
UPDATE_INTERVAL_IN_MILLISECONDS / 2;
/**
* The identifier for the notification displayed for the foreground service.
*/
private static final int NOTIFICATION_ID = 12345678;
/**
* Used to check whether the bound activity has really gone away and not unbound as part of an
* orientation change. We create a foreground service notification only if the former takes
* place.
*/
private boolean mChangingConfiguration = false;
private NotificationManager mNotificationManager;
/**
* Contains parameters used by {@link com.google.android.gms.location.FusedLocationProviderApi}.
*/
private LocationRequest mLocationRequest;
/**
* Provides access to the Fused Location Provider API.
*/
private FusedLocationProviderClient mFusedLocationClient;
/**
* Callback for changes in location.
*/
private LocationCallback mLocationCallback;
private Handler mServiceHandler;
Double latitude, longitude;
/**
* The current location.
*/
private Location mLocation;
/**
* Realtime location save in firestore or firebase*/
GeoFire geoFire;
FirebaseFirestore firebaseFirestore;
DocumentReference documentReference;
FirebaseAuth firebaseAuth;
@SuppressWarnings("deprecation")
public LocationUpdatesService() {
}
@SuppressWarnings("deprecation")
@Override
public void onCreate() {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
onNewLocation(locationResult.getLastLocation());
}
};
createLocationRequest();
getLastLocation();
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
mServiceHandler = new Handler(handlerThread.getLooper());
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "wl:location");
wakeLock.acquire();
// Android O requires a Notification Channel.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.app_name);
// Create the channel for the notification
NotificationChannel mChannel =
new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT);
// Set the Notification Channel for the Notification Manager.
mNotificationManager.createNotificationChannel(mChannel);
}
}
@SuppressWarnings("deprecation")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Service started");
boolean startedFromNotification = intent.getBooleanExtra(EXTRA_STARTED_FROM_NOTIFICATION,
false);
// We got here because the user decided to remove location updates from the notification.
if (startedFromNotification) {
removeLocationUpdates();
stopSelf();
}
// Tells the system to not try to recreate the service after it has been killed.
return START_STICKY;
}
@SuppressWarnings("deprecation")
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mChangingConfiguration = true;
}
@SuppressWarnings("deprecation")
@Override
public IBinder onBind(Intent intent) {
// Called when a client (MainActivity in case of this sample) comes to the foreground
// and binds with this service. The service should cease to be a foreground service
// when that happens.
Log.i(TAG, "in onBind()");
stopForeground(true);
mChangingConfiguration = false;
// Register Firestore when service will restart
firebaseAuth = FirebaseAuth.getInstance();
firebaseFirestore = FirebaseFirestore.getInstance();
return mBinder;
}
@SuppressWarnings("deprecation")
@Override
public void onRebind(Intent intent) {
// Called when a client (MainActivity in case of this sample) returns to the foreground
// and binds once again with this service. The service should cease to be a foreground
// service when that happens.
Log.i(TAG, "in onRebind()");
stopForeground(true);
mChangingConfiguration = false;
// Register Firestore when service will restart
firebaseAuth = FirebaseAuth.getInstance();
firebaseFirestore = FirebaseFirestore.getInstance();
super.onRebind(intent);
}
@SuppressWarnings("deprecation")
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "Last client unbound from service");
// Called when the last client (MainActivity in case of this sample) unbinds from this
// service. If this method is called due to a configuration change in MainActivity, we
// do nothing. Otherwise, we make this service a foreground service.
if (!mChangingConfiguration && Utils.requestingLocationUpdates(this)) {
Log.d(TAG, "Starting foreground service");
/*
// TODO(developer). If targeting O, use the following code.
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
mNotificationManager.startServiceInForeground(new Intent(this,
LocationUpdatesService.class), NOTIFICATION_ID, getNotification());
} else {
startForeground(NOTIFICATION_ID, getNotification());
}
*/
startForeground(NOTIFICATION_ID, getNotification());
}
return true; // Ensures onRebind() is called when a client re-binds.
}
@SuppressWarnings("deprecation")
@Override
public void onDestroy() {
mServiceHandler.removeCallbacksAndMessages(null);
}
/**
* Makes a request for location updates. Note that in this sample we merely log the
* {@link SecurityException}.
*/
public void requestLocationUpdates() {
Log.i(TAG, "Requesting location updates");
Utils.setRequestingLocationUpdates(this, true);
startService(new Intent(getApplicationContext(), LocationUpdatesService.class));
try {
mFusedLocationClient.requestLocationUpdates(mLocationRequest,
mLocationCallback, Looper.myLooper());
} catch (SecurityException unlikely) {
Utils.setRequestingLocationUpdates(this, false);
Log.d(TAG, "Lost location permission. Could not request updates. " + unlikely);
}
}
/**
* Removes location updates. Note that in this sample we merely log the
* {@link SecurityException}.
*/
public void removeLocationUpdates() {
Log.i(TAG, "Removing location updates");
try {
mFusedLocationClient.removeLocationUpdates(mLocationCallback);
Utils.setRequestingLocationUpdates(this, false);
stopSelf();
} catch (SecurityException unlikely) {
Utils.setRequestingLocationUpdates(this, true);
Log.d(TAG, "Lost location permission. Could not remove updates. " + unlikely);
}
}
/**
* Returns the {@link NotificationCompat} used as part of the foreground service.
*/
private Notification getNotification() {
Intent intent = new Intent(this, LocationUpdatesService.class);
CharSequence text = Utils.getLocationText(mLocation);
// Extra to help us figure out if we arrived in onStartCommand via the notification or not.
intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true);
// The PendingIntent that leads to a call to onStartCommand() in this service.
PendingIntent servicePendingIntent = PendingIntent.getService(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
// The PendingIntent to launch activity.
PendingIntent activityPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.addAction(R.drawable.ic_launch, getString(R.string.launch_activity),
activityPendingIntent)
.addAction(R.drawable.ic_cancel, getString(R.string.remove_location_updates),
servicePendingIntent)
.setContentText(text)
.setContentTitle(Utils.getLocationTitle(this))
.setOngoing(true)
.setPriority(Notification.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker(text)
.setWhen(System.currentTimeMillis());
// Set the Channel ID for Android O.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CHANNEL_ID); // Channel ID
}
return builder.build();
}
private void getLastLocation() {
try {
mFusedLocationClient.getLastLocation()
.addOnCompleteListener(new OnCompleteListener<Location>() {
@Override
public void onComplete(@NonNull Task<Location> task) {
if (task.isSuccessful() && task.getResult() != null) {
mLocation = task.getResult();
} else {
Log.w(TAG, "Failed to get location.");
}
}
});
} catch (SecurityException unlikely) {
Log.d(TAG, "Lost location permission." + unlikely);
}
}
private void onNewLocation(Location location) {
Log.d(TAG, "New location: " + location);
mLocation = location;
// Notify anyone listening for broadcasts about the new location.
Intent intent = new Intent(ACTION_BROADCAST);
intent.putExtra(EXTRA_LOCATION, location);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
// Update notification content if running as a foreground service.
if (serviceIsRunningInForeground(this)) {
mNotificationManager.notify(NOTIFICATION_ID, getNotification());
// Getting location when notification was call.
latitude = location.getLatitude();
longitude = location.getLongitude();
// Here using to call Save to serverMethod
SavetoServer();
}
}
/**
* Sets the location request parameters.
*/
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
/**
* Class used for the client Binder. Since this service runs in the same process as its
* clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocationUpdatesService getService() {
return LocationUpdatesService.this;
}
}
/**
* Returns true if this is a foreground service.
*
* @param context The {@link Context}.
*/
public boolean serviceIsRunningInForeground(Context context) {
ActivityManager manager = (ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(
Integer.MAX_VALUE)) {
if (getClass().getName().equals(service.service.getClassName())) {
if (service.foreground) {
return true;
}
}
}
return false;
}
/**
* Save a value in realtime to firestore when user in background
* For foreground you have to call same method to activity
* */
private void SavetoServer(){
Toast.makeText(this, "Save to server", Toast.LENGTH_SHORT).show();
Log.d("resMM", "Send to server");
Log.d("resML", String.valueOf(latitude));
Log.d("resMLL", String.valueOf(longitude));
Map<String , String> driverMap = new HashMap<>();
driverMap.put("name" , String.valueOf(latitude));
driverMap.put("email" , String.valueOf(longitude));
documentReference = firebaseFirestore
.collection("driverAvaliable")
.document("newdriver");
documentReference.update("latitude", String.valueOf(latitude),
"longitude", String.valueOf(longitude),
"timeStamp", FieldValue.serverTimestamp())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "DocumentSnapshot successfully updated!");
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d(TAG, "Error updating document", e);
}
});
}
我将onStartCommand中的START_NOT_STICKY更改为START_STICKY,并将WakeLock代码添加到LocationUpdatesService中的onCreate。
终止后的服务会从几秒钟重新启动到几分钟。最坏的情况是系统停止服务并且用户将屏幕关闭。然后,Android不会重新启动此服务,并且应用程序不会在后台获取位置。用户必须打开屏幕并解锁设备。
我想系统不终止我的服务。如果不可能,Android应该立即重启它。如何实现?