2016-08-10 2 views
1

Согласно this thread в stackoverflow, должно быть возможно управлять уведомлением из внешнего основного/пользовательского потока. И это на самом деле. Я создаю уведомление в SyncAdapter, чтобы уведомить пользователя о том, что фоновая синхронизация запущена и обновлена ​​загрузка, и после завершения загрузки. Я отменяю уведомление после определенного таймаута. Моя проблема заключается в том, что автоматическая отмена уведомлений не предсказуема. Иногда он автоматически отменяет себя, иногда это видно до следующей синхронизации.NotificationManager, обновляющее/скрывающее уведомление от SyncAdapter (за пределами потока пользовательского интерфейса)

Здесь весь адаптер:

package com.marianhello.bgloc.sync; 

import android.accounts.Account; 
import android.app.NotificationManager; 
import android.content.AbstractThreadedSyncAdapter; 
import android.content.ContentProviderClient; 
import android.content.ContentResolver; 
import android.content.Context; 
import android.content.SyncResult; 
import android.os.Bundle; 
import android.os.Handler; 
import android.os.Looper; 
import android.support.v4.app.NotificationCompat; 

import com.marianhello.bgloc.Config; 
import com.marianhello.bgloc.HttpPostService; 
import com.marianhello.bgloc.UploadingCallback; 
import com.marianhello.bgloc.data.ConfigurationDAO; 
import com.marianhello.bgloc.data.DAOFactory; 
import com.marianhello.logging.LoggerManager; 

import org.json.JSONException; 

import java.io.File; 
import java.io.IOException; 
import java.net.HttpURLConnection; 
import java.text.SimpleDateFormat; 
import java.util.Calendar; 
import java.util.HashMap; 

/** 
* Handle the transfer of data between a server and an 
* app, using the Android sync adapter framework. 
*/ 
public class SyncAdapter extends AbstractThreadedSyncAdapter implements UploadingCallback { 

    private static final int NOTIFICATION_ID = 1; 

    ContentResolver contentResolver; 
    private ConfigurationDAO configDAO; 
    private NotificationManager notifyManager; 
    private BatchManager batchManager; 

    private org.slf4j.Logger log; 

    /** 
    * Set up the sync adapter 
    */ 
    public SyncAdapter(Context context, boolean autoInitialize) { 
     super(context, autoInitialize); 
     log = LoggerManager.getLogger(SyncAdapter.class); 

     /* 
     * If your app uses a content resolver, get an instance of it 
     * from the incoming Context 
     */ 
     contentResolver = context.getContentResolver(); 
     configDAO = DAOFactory.createConfigurationDAO(context); 
     batchManager = new BatchManager(this.getContext()); 
     notifyManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); 
    } 


    /** 
    * Set up the sync adapter. This form of the 
    * constructor maintains compatibility with Android 3.0 
    * and later platform versions 
    */ 
    public SyncAdapter(
      Context context, 
      boolean autoInitialize, 
      boolean allowParallelSyncs) { 
     super(context, autoInitialize, allowParallelSyncs); 

     log = LoggerManager.getLogger(SyncAdapter.class); 

     /* 
     * If your app uses a content resolver, get an instance of it 
     * from the incoming Context 
     */ 
     contentResolver = context.getContentResolver(); 
     configDAO = DAOFactory.createConfigurationDAO(context); 
     batchManager = new BatchManager(this.getContext()); 
     notifyManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); 
    } 

    /* 
    * Specify the code you want to run in the sync adapter. The entire 
    * sync adapter runs in a background thread, so you don't have to set 
    * up your own background processing. 
    */ 
    @Override 
    public void onPerformSync(
      Account account, 
      Bundle extras, 
      String authority, 
      ContentProviderClient provider, 
      SyncResult syncResult) { 

     Config config = null; 
     try { 
      config = configDAO.retrieveConfiguration(); 
     } catch (JSONException e) { 
      log.error("Error retrieving config: {}", e.getMessage()); 
     } 

     if (config == null) return; 

     log.debug("Sync request: {}", config.toString()); 
     if (config.hasUrl() || config.hasSyncUrl()) { 
      Long batchStartMillis = System.currentTimeMillis(); 

      File file = null; 
      try { 
       file = batchManager.createBatch(batchStartMillis); 
      } catch (IOException e) { 
       log.error("Failed to create batch: {}", e.getMessage()); 
      } 

      if (file == null) { 
       log.info("Nothing to sync"); 
       return; 
      } 

      log.info("Syncing batchStartMillis: {}", batchStartMillis); 
      String url = config.hasSyncUrl() ? config.getSyncUrl() : config.getUrl(); 
      HashMap<String, String> httpHeaders = new HashMap<String, String>(); 
      httpHeaders.putAll(config.getHttpHeaders()); 
      httpHeaders.put("x-batch-id", String.valueOf(batchStartMillis)); 

      if (uploadLocations(file, url, httpHeaders)) { 
       log.info("Batch sync successful"); 
       batchManager.setBatchCompleted(batchStartMillis); 
       if (file.delete()) { 
        log.info("Batch file has been deleted: {}", file.getAbsolutePath()); 
       } else { 
        log.warn("Batch file has not been deleted: {}", file.getAbsolutePath()); 
       } 
      } else { 
       log.warn("Batch sync failed due server error"); 
       syncResult.stats.numIoExceptions++; 
      } 
     } 
    } 

    private boolean uploadLocations(File file, String url, HashMap httpHeaders) { 
     NotificationCompat.Builder builder = new NotificationCompat.Builder(getContext()); 
     builder.setOngoing(true); 
     builder.setContentTitle("Syncing locations"); 
     builder.setContentText("Sync in progress"); 
     builder.setSmallIcon(android.R.drawable.ic_dialog_info); 
     notifyManager.notify(NOTIFICATION_ID, builder.build()); 

     try { 
      int responseCode = HttpPostService.postJSON(url, file, httpHeaders, this); 
      if (responseCode == HttpURLConnection.HTTP_OK) { 
       builder.setContentText("Sync completed"); 
      } else { 
       builder.setContentText("Sync failed due server error"); 
      } 

      return responseCode == HttpURLConnection.HTTP_OK; 
     } catch (IOException e) { 
      log.warn("Error uploading locations: {}", e.getMessage()); 
      builder.setContentText("Sync failed: " + e.getMessage()); 
     } finally { 
      builder.setOngoing(false); 
      builder.setProgress(0, 0, false); 
      builder.setAutoCancel(true); 
      notifyManager.notify(NOTIFICATION_ID, builder.build()); 

      Handler h = new Handler(Looper.getMainLooper()); 
      long delayInMilliseconds = 5000; 
      h.postDelayed(new Runnable() { 
       public void run() { 
        notifyManager.cancel(NOTIFICATION_ID); 
       } 
      }, delayInMilliseconds); 
     } 

     return false; 
    } 

    public void uploadListener(int progress) { 
     NotificationCompat.Builder builder = new NotificationCompat.Builder(getContext()); 
     builder.setOngoing(true); 
     builder.setContentTitle("Syncing locations"); 
     builder.setContentText("Sync in progress"); 
     builder.setSmallIcon(android.R.drawable.ic_dialog_info); 
     builder.setProgress(100, progress, false); 
     notifyManager.notify(NOTIFICATION_ID, builder.build()); 
    } 
} 

Весь проект ОСС так full source code доступна. Для более крупного изображения также HttpPostService.java может быть интересно.

+0

Вы испытываете недетерминированное автоматическое отмена при вызове функции notifyManager.cancel() напрямую, без задержки отправив Runnable в основной поток? –

+0

Не помните, как это пытались. Вы указываете на обработчик h? – mauron85

+0

Да. Можно управлять уведомлением из другого основного потока, но вам действительно не нужно управлять несколькими потоками, если вы не используете какой-либо механизм блокировки. Просто управляйте всей вещью из фонового потока, в котором работает ваш AsyncTask. –

ответ

2

Я нашел решение моей проблемы в this stackoverflow thread.

When I changed NOTIFICATION_ID from 1 to [RANDOM_NUMBER], it magically started working. I assume that 1 is somehow reserved, although there is no note in any documentation...

An of course make sure you use the same NOTIFICATION_ID to cancel: notificationManager.cancel(NOTIFICATION_ID);

1

Я думаю, что ваша проблема заключается в следующем: вы отправляете уведомление об отмене в потоке пользовательского интерфейса, но параллельно вы публикуете обновления в фоновом потоке. Существует условие гонки между отменой и последним обновлением (обновлениями) - иногда отменяется последняя команда, которую получает менеджер уведомлений, и иногда она получает дополнительные обновления после отмены (что заставляет его снова всплывать уведомление).

Почему вы отправляете сообщение об отмене в главной теме в первую очередь? Просто проверьте статус в uploadListener(int) и решите, хотите ли вы обновить уведомление или отменить его ...

+0

Причина отмены обхода в методе обработчика postDelayed - это, очевидно, задержка отмены. Я не хочу отменять уведомление сразу после завершения загрузки, но после задержкиInMilliseconds, которая составляет 5 секунд. – mauron85

+0

@ mauron85, и вы согласны с отменой уведомления до завершения синхронизации (если это занимает более 5 секунд)? – Vasiliy

+0

@ mauron85, как я уже говорил выше, попробуйте вызвать notifyManager.cancel() прямо из вашего блока finally. Я почти уверен, что ваше недетерминированное поведение исчезает. –

Смежные вопросы