Мое приложение использует новый дизайн сервиса 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] -->