如何正确取消Android 8中终止的前台服务的通知

时间:2018-10-30 10:00:14

标签: android android-notifications android-8.0-oreo foreground-service android-workmanager

“我的应用”正在使用Android 8中引入的新的Foreground服务设计。

在执行服务期间,取消系统托盘中显示的notification时遇到问题。即使服务未运行,某些服务仍会在通知栏上导致与服务冻结有关的通知。

我想问一些建议,以正确的方式进行操作,因为在这种情况下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()方法结束下载的错误和成功状态,该方法应触发父 BaseIntentService 服务中的 onDestroy()事件。

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 :(得分:1)

两次stopForeground(int)的单独调用导致了此现象。 stopForeground(int)采用标志的按位组合,因此不应连续调用两次(因为第一次调用将使您的服务停止成为前台服务,这意味着不再适合使用stopForeground()与之沟通)。我什至不知道在这种情况下记录的行为是什么。

解决方案

无论操作系统版本如何,只需调用stopForeground(true)

答案 1 :(得分:0)

在WorkManager中运行服务没有任何意义。