Как правильно отменить уведомление о прекращении Foreground Service в Android 8 - PullRequest
0 голосов
/ 30 октября 2018

Мое приложение использует новый дизайн сервиса Foreground, представленный в Android 8.

У меня проблема с отменой уведомления , отображаемого в системном трее во время выполнения сервиса. Некоторые службы по-прежнему вызывают это уведомление, связанное с зависанием службы на панели уведомлений, даже если служба не запущена.

Я хотел бы спросить о некоторых рекомендациях, как сделать это правильно, потому что документация Android не ясна в этом случае.

Ниже упоминается мой подход к оформлению услуги и отображению / отмене уведомления. Любое предложение приветствуется.

Примечание. Я добавил метод тайм-аута в UserManualCheckService для принудительного вызова метода stopSelf () .

1) Служба запущена с использованием Worker Manager в качестве экземпляра Worker:

List<OneTimeWorkRequest> workRequestList = new ArrayList<>();
    OneTimeWorkRequest workRequestUserManual = new OneTimeWorkRequest.Builder(UserManualWorker.class).build();
workRequestList.add(workRequestUserManual);
mWorkManagerInstance.enqueue(workRequestList);

Рабочий пример

public class UserManualWorker extends Worker {

    private Context context;

    public UserManualWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
        this.context = context;
    }

    @NonNull
    @Override
    public Result doWork() {
        Intent i = new Intent(context, UserManualCheckService.class);
        CommonHelper.runService(context, i);
        return Result.SUCCESS;
    }
}

2) Сервисный пример

Сервис загружает некоторые данные, используя HTTP-запрос. Ошибка и успешное состояние загрузки завершаются с помощью метода stopSelf () , который должен вызвать onDestroy () в родительской BaseIntentService службе.

public class UserManualCheckService extends BaseIntentService implements HttpResponseListener {

    private Context context = null;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     */
    public UserManualCheckService() {
        super(UserManualCheckService.class.getName());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        try { context = this;
            //IF DEVICE IS ONLINE FETCH MESSAGES FROM REMOTE SERVER
            if (CommonHelper.isConnectedToInternet(context)) {
                Logger.i("UserManualCheckService started");
                CommonHelper.showServiceNotification(this);
                this.getRemoteData();
                this.startTimeoutForRequest();
            } else {
                Logger.i("No need to sync UserManualCheckService now");
                stopSelf();
            }
        } catch (Exception e) {
            TrackingExceptionHelper.logException(e);
            stopSelf();
        }
        return START_STICKY_COMPATIBILITY;
    }

    @Override
    protected void onHandleIntent(Intent intent) {
    }

    private void getRemoteData() throws Exception {
        JsonRequest request = HttpHelper.createRequest(Constants.Request.JSON_OBJECT, Request.Method.GET,
                Constants.URL.API_URL_BASE_SCHEME, Constants.URL.API_URL_MEDIA_CHECK_MANUAL,
                null, this, Request.Priority.HIGH);
        HttpHelper.makeRequest(request, true);
    }

    @Override
    public void onError(VolleyError error) {
        TrackingExceptionHelper.logException(error);
        stopSelf();
    }

    @Override
    public void onResponse(Object response) throws JSONException {
        Logger.d("onResponse");
        if (response instanceof JSONObject) {
            final JSONObject resp = (JSONObject) response;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        checkLastSavedVersion(resp);
                    } catch (Exception e) {
                        TrackingExceptionHelper.logException(e);
                    }
                }
            }).start();
            stopSelf();
        } else {
            Logger.e("Parsing Response data as JSON object is not implemented");
        }
    }

    private void checkLastSavedVersion(JSONObject mediaResource)  {
        try {
            Integer serverManualSize = mediaResource.getInt(Constants.Global.KEY_MANUAL_FILE_SIZE);
            String localManualSizeAsString = SharedPrefsHelper.getInstance(context).readString(Constants.Global.KEY_MANUAL_FILE_SIZE);
            Integer localManualSize = localManualSizeAsString == null ? 0 : Integer.parseInt(localManualSizeAsString);
            if(!serverManualSize.equals(localManualSize)) {
                new DownloadUserManualAsyncTask(serverManualSize, context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            } else {
                Logger.i("User manual already downloaded and up-to date");
            }
        } catch (Exception e) {
            TrackingExceptionHelper.logException(e);
        } finally {
            stopSelf();
        }
    }

    private void startTimeoutForRequest() {
        new android.os.Handler().postDelayed(
                new Runnable() {
                    public void run() {
                        stopSelf();
                    }
                },
                10000);
    }
}

BaseIntentService

Родительский сервис для всех фоновых сервисов. Вызов stopSelf () для потомков передается родителю и перехватывается в onDestroy () , где остановлена ​​служба, и уведомление СЛЕДУЕТ каждый раз отменять.

public abstract class BaseIntentService extends IntentService {

    Context context = null;

    @Override
    public void onCreate() {
        super.onCreate();
        this.context = this;
    }


    public BaseIntentService(String name) {
        super(name);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Logger.d("service done, hiding system tray notification");
        CommonHelper.stopService(context, this);
        NotificationHelper.cancelNotification(Constants.Notification.SERVICE_NOTIFICATION_ID, context);
    }
}

Запуск выполнения Foreground Service с использованием вспомогательного класса:

  public static void runService(Context context, Intent i) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            ContextCompat.startForegroundService(context, i);
        } else {
            context.startService(i);
        }
    }

Отображение уведомления:

public static void addServiceNotification(Service context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager != null) {
            setupNotificationChannelsLowImportance(notificationManager);
        }
        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(context, ANDROID_CHANNEL_ID_SYNC);
        Notification notification = mBuilder
                .setOngoing(false) //Always true in start foreground
                .setAutoCancel(true)
                .setSmallIcon(R.drawable.ic_sync)
                .setContentTitle(context.getClass().getSimpleName())
                //.setContentTitle(context.getString(R.string.background_sync_is_active))
                .setPriority(NotificationManager.IMPORTANCE_LOW)
                .setVisibility(Notification.VISIBILITY_PRIVATE)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();
        notification.flags |=Notification.FLAG_AUTO_CANCEL;
        context.startForeground(Constants.Notification.SERVICE_NOTIFICATION_ID, notification);
        if(notificationManager != null) {
            //notificationManager.notify(Constants.Notification.SERVICE_NOTIFICATION_ID, notification);
        }
    }
}

Остановка сервиса производится следующим образом:

   public static void stopService(Context context, Service s) {

        Intent i = new Intent(context, s.getClass());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            s.stopForeground(Service.STOP_FOREGROUND_DETACH);
            s.stopForeground(Service.STOP_FOREGROUND_REMOVE);
        } else {
            s.stopForeground(true);
        }
        context.stopService(i);
    }

Отмена метода уведомления, вызванного из BaseIntentService onDestroy ()

   public static void cancelNotification(int notificationId, Context context) {
        NotificationManager notificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager != null) {
            notificationManager.cancel(notificationId);
        }
    }

Обозначение службы в AndroidManifest.xml

 <!-- [START USER MANUAL CHECK SERVICE] -->
        <service
            android:name=".service.UserManualCheckService"
            android:enabled="true"
            android:exported="false"/>
        <!-- [END USER MANUAL SERVICE] -->

Ответы [ 2 ]

0 голосов
/ 01 августа 2019

Я не вижу смысла в запуске службы из WorkManager.

0 голосов
/ 30 октября 2018

Два отдельных вызова stopForeground(int) вызывают такое поведение. stopForeground(int) принимает побитовую комбинацию флагов и не должна вызываться дважды подряд (поскольку первый вызов приведет к тому, что ваша служба перестанет быть приоритетной службой, что означает, что для связи с ней больше не нужно использовать stopForeground() ). Я даже не уверен, что задокументированное поведение в этом случае.

РЕШЕНИЕ

Просто позвоните stopForeground(true) независимо от версии ОС.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...