NotificationManager更新/隐藏来自SyncAdapter的通知(在UI线程之外)



根据stackoverflow上的这个线程,应该可以管理来自主/UI线程外部的通知。事实确实如此。我在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());
    }
}

整个项目是OSS,所以完整的源代码是可用的。

我认为你的问题是:你在UI线程上发布通知取消,但同时你在后台线程上发布更新。取消和最后一次更新之间存在竞争条件——有时取消是通知管理器得到的最后一个命令,有时它在取消后收到额外的更新(这使他再次弹出通知)。

为什么首先要在主线程上发布取消呢?只需检查uploadListener(int)中的状态,并决定您是要更新通知还是取消通知…

我在这个stackoverflow线程中找到了解决我的问题的方法。

当我将NOTIFICATION_ID从1更改为[RANDOM_NUMBER]时,它神奇地开始工作。我假设1在某种程度上是保留的,尽管在任何文档中都没有注释…

当然,确保你使用相同的NOTIFICATION_ID来取消:notificationManager.cancel (NOTIFICATION_ID);

相关内容

  • 没有找到相关文章

最新更新