android - 为什么 Android Oreo 会杀死应用程序的前台服务?
问题描述
上次,我发现了一个应用程序,它在 Android Oreo 的后台获取位置。我注意到,当我打开或单击“设置”或“相机”应用程序中的某些选项时,该系统会终止前台服务。相反,来自应用程序的通知,系统显示自己的标题为“使用电池的应用程序”。我在装有 Android 8 的平板电脑华为 MediaPad T5 上测试了一个应用程序。
我在电池选项中手动设置了忽略优化和管理。我还将 onStartCommand 中的返回值设置为 START_STICKY。我在 onCreate 服务的方法中添加了 WakeLock。我检查了一下,该设备有可用的 RAM,尽管如此,Android 还是终止了我的服务。我也尝试阻止“应用程序使用电池”通知,但系统终止了前台服务并稍后重新启动。
我使用了 adb shell dumpsys 活动服务,前台服务位于 LRU 列表的 dle 中间。尽管如此,Android 8 还是终止了该服务。
我使用来自GitHub 存储库的应用程序。
完整的 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 应立即重新启动它。如何实现?
解决方案
推荐阅读
- .net - 切换按钮中的 wpf 图像不显示
- java - 我们可以对 MySQL 和 Java 中的加密列执行通配符搜索吗
- amazon-web-services - 在查询结构列时面临访问被拒绝
- mql4 - 以不同于 EA 在 MQL4 中运行的交易品种进行交易
- docker - File created in interactive session within container dissapears after exiting container (container running in background)
- ruby-on-rails - has_many 中的表名通过关系
- javascript - 超越 JavaScript Snake Game - 游戏所需的不同 berry 代码帮助
- c++ - Visual Studio 中的警告 C4267:“参数”:从“size_t”转换为“const _Elem”,可能丢失数据
- java - 如何将蓝牙接收代码添加到我的代码中?
- java - 如何制作二进制翻译器?