Почему Android Oreo убивает приоритетную службу приложения? - PullRequest
0 голосов
/ 30 мая 2019

В прошлый раз я нашел приложение, которое получает местоположение в фоновом режиме на Android Oreo. Я заметил, что система убивает службу переднего плана, когда я открываю или нажимаю некоторые опции в настройках или приложении «Камера». Вместо этого, уведомление от приложения, система показывает свое собственное с названием «Приложение с использованием батареи». Тестирую приложение на планшете Huawei MediaPad T5 с Android 8.

Я установил Игнорировать оптимизации и Управлять вручную в параметрах батареи. Я также устанавливаю возвращаемое значение в onStartCommand равным START_STICKY. Я добавил WakeLock в метод сервиса onCreate. Я проверил, что у этого устройства была свободная RAM, и, несмотря на это, Android убил мой сервис. Я также пытался заблокировать уведомление «Приложение с использованием батареи», но система прервала обслуживание переднего плана и перезапустила его через некоторое время.

Я использовал сервисы adb shell сервисы dumpsys и служба переднего плана находится в середине списка LRU. Несмотря на это, 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);
        }
    });
}

Я изменил START_NOT_STICKY в onStartCommand на START_STICKY и добавил код WakeLock для onCreate в LocationUpdatesService.

Служба после убийства возобновляется с нескольких секунд до нескольких минут. Худший случай - когда система убивает сервис, а пользователь выключает экран. Тогда Android не перезапускает этот сервис и приложение не получает местоположения в фоновом режиме. Пользователь должен включить экран и разблокировать устройство.

Я бы хотел, чтобы система не убивала мой сервис. Если это невозможно, Android должен перезапустить его немедленно. Как этого добиться?

...