2016-08-10 84 views
1

根據stackoverflow上的this thread,應該可以管理來自外部主/ UI線程的通知。實際上是。我在SyncAdapter中創建通知以通知用戶後臺同步已啓動並更新上傳進度,並且上傳完成後,我將在某些定義的超時後取消通知。我的問題是通知自動取消不可預測。有時它會自動取消自己,有時直到下一次同步時纔會顯示。NotificationManager更新/隱藏來自SyncAdapter的通知(外部UI線程)

這裏是整個適配器:

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()); 
    } 
} 

整個項目是OSS所以full source code可用。要獲得更大的圖片HttpPostService.java可能會有趣。

+0

如果在直接調用notifyManager.cancel()時沒有延遲向主線程發佈Runnable,是否會遇到非確定性自動取消操作? –

+0

不記得嘗試。你指着Handler 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

我認爲你的問題如下:你在UI線程上發佈通知取消,但並行地在後臺線程上發佈更新。取消和上次更新之間存在競爭狀態 - 有時取消是通知管理器獲取的最後一個命令,有時它會在取消後收到附加更新(這會使他再次彈出通知)。

爲什麼你首先在主線程上發佈取消?只需檢查uploadListener(int)中的狀態,然後決定是要更新通知還是取消通知...

+0

在postDelayed處理程序方法中取消取消的原因顯然是取消延遲。上傳完成後,我不想立即取消通知,但是在delayInMilliseconds爲5secs之後,我不想立即取消通知。 – mauron85

+0

@ mauron85,你可以在同步完成之前取消通知(如果超過5秒)? – Vasiliy

+0

@ mauron85,就像我上面所說的,嘗試直接從finally塊中調用notifyManager.cancel()。我非常肯定你的不確定行爲會消失。 –