如何阻止相同的广播(来自 PendingIntent)排队?

Posted

技术标签:

【中文标题】如何阻止相同的广播(来自 PendingIntent)排队?【英文标题】:How to stop same broadcast (from PendingIntent) from queuing up? 【发布时间】:2019-05-13 07:10:21 【问题描述】:

场景

我已经实现了一个 android App Widget。

单击小部件将使用PendingIntent.getBroadcast(...) 启动广播。

我想在广播接收器的onReceive 内进行网络请求。

(你问我为什么不使用PendingIntent.getService(...)并启动IntentService?这是一个自然的想法,但遗憾的是由于后台限制,如果应用程序不在前台,则无法启动服务。你可以看看this post。)

问题

为了证明它有效,我实现了一个示例 BroadcastReceiver:

class WidgetClickBroadcastReceiver : BroadcastReceiver() 

    override fun onReceive(context: Context?, intent: Intent?) 
        if (context == null || intent == null) return

        Log.i("Sira", "onReceive called")

        val pendingResult = goAsync()
        Observable.just(true).delay(3, TimeUnit.SECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe 
                Log.i("Sira", "Fake completion of network call")
                pendingResult.finish()
        
    


是的,它有效。

但是,我注意到,如果我多次点击小部件,则会创建多个广播并一个一个排队,直到调用前一个的pendingResult.finish()

这可以通过goAsync()的文档来解释:

请记住,您在此处所做的工作将阻止进一步的广播,直到其完成,因此过度利用这一点可能会适得其反,并导致稍后的事件接收速度更慢。

所以我想知道如果同一个广播已经在队列中,是否有办法防止它多次触发?

或者任何其他方法来防止由于疯狂点击小部件而导致排队呼叫?

【问题讨论】:

【参考方案1】:

编辑 2:小部件的可能解决方案是: 操作完成后,将 timestamp 保存到 SharedPreferences(如果需要,可用于每个操作)。 再次调用 onReceive 后,请检查 timestamp 以获得您首选的 millis 增量,并且仅在增量足够长时再次运行该操作。

Edit1:下面的答案适用于widgets,我会把它留给任何寻找“常规”案例的人

我尝试了很多东西(包括使用HandlerReflection),最后我想出了以下解决方案:当您收到不想再收到的消息时,@987654329 @(该特定操作)和register(当操作完成时)。 BroadcastReceiver 在下方,here is the full example project

package com.exmplae.testbroadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

public class SelfRegisteringBroadcastReceiver extends BroadcastReceiver 

    private static final String TAG = "SelfRegisteringBR";
    public static final String TEST_ACTION1 = "TEST_ACTION1";
    public static final String TEST_ACTION2 = "TEST_ACTION2";
    private final ArrayList<String> registeredActions = new ArrayList<>();
    private final ILogListener logListener;
    private final Object registeringLock = new Object();

    public static IntentFilter getIntentFilter() 
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(TEST_ACTION1);
        intentFilter.addAction(TEST_ACTION2);

        return intentFilter;
    

    public SelfRegisteringBroadcastReceiver(ILogListener logListener) 
        this.logListener = logListener;
        registeredActions.add(TEST_ACTION1);
        registeredActions.add(TEST_ACTION2);
    

    private void register(Context context, String action) 
        synchronized (registeringLock) 
            if (!registeredActions.contains(action)) 
                registeredActions.add(action);
                context.unregisterReceiver(this);
                register(context);
            
        
    

    private void register(Context context) 
        IntentFilter intentFilter = new IntentFilter();
        for (String action : registeredActions) 
            intentFilter.addAction(action);
        

        context.registerReceiver(this, intentFilter);
    

    private void unregister(Context context, String action) 
        synchronized (registeringLock) 
            if (registeredActions.contains(action)) 
                registeredActions.remove(action);
                context.unregisterReceiver(this);
                register(context);
            
        
    

    @Override
    public void onReceive(Context context, Intent intent) 
        logListener.d(TAG, "onReceive");
        if (intent == null) 
            logListener.e(TAG, "intent = null");
            return;
        

        String action = intent.getAction();
        if (action == null) 
            logListener.e(TAG, "action = null");
            return;
        

        //noinspection IfCanBeSwitch
        if (action.equals(TEST_ACTION1)) 
            doAction1(context, TEST_ACTION1);
         else if (action.equals(TEST_ACTION2)) 
            doAction2();
         else 
            logListener.e(TAG, "Received unknown action: " + action);
        
    

    private void doAction1(final Context context, final String actionName) 
        logListener.d(TAG, "doAction1 start (and unregister)");
        unregister(context, actionName);
        Observable.just(true).delay(10, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Boolean>() 
                    @Override
                    public void onSubscribe(Disposable d) 
                        logListener.d(TAG, "doAction1 - onSubscribe");
                    

                    @Override
                    public void onNext(Boolean aBoolean) 
                        logListener.d(TAG, "doAction1 - onNext");
                    

                    @Override
                    public void onError(Throwable e) 
                        logListener.e(TAG, "doAction1 - onError");
                    

                    @Override
                    public void onComplete() 
                        logListener.d(TAG, "doAction1 - onComplete (and register)");
                        register(context, actionName);
                    
                );

        logListener.d(TAG, "doAction1 end");
    

    private void doAction2() 
        logListener.d(TAG, "doAction2 start");
        Observable.just(true).delay(3, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Boolean>() 
                    @Override
                    public void onSubscribe(Disposable d) 
                        logListener.d(TAG, "doAction2 - onSubscribe");
                    

                    @Override
                    public void onNext(Boolean aBoolean) 
                        logListener.d(TAG, "doAction2 - onNext");
                    

                    @Override
                    public void onError(Throwable e) 
                        logListener.e(TAG, "doAction2 - onError");
                    

                    @Override
                    public void onComplete() 
                        logListener.d(TAG, "doAction2 - onComplete");
                    
                );

        logListener.d(TAG, "doAction2 end");
    


【讨论】:

感谢您的回答!您的确实可以作为本地广播。但不幸的是,小部件的情况不是;并且您还可以观察到您的 action2 也没有排队,即使前一个正在运行,它仍然会被触发。它只是堆叠起来,但没有排队。对于widget(PendingIntent)的情况,是排队的。 @SiraLam 我不确定我是否关注你,action1 旨在工作一次(同时)和 action2 多次 - 为了示例。你的意思是它不像在主线程中那样在小部件中工作? widget 的广播接收器与您的示例有两点非常不同,(1)广播是通过一个pendingIntent 接收的; (2)因此,它没有意图过滤器,在构建待处理的Intent时直接指定广播接收器,无需任何操作(意图过滤器)。 我明白了,我已经有一段时间没有看小部件了,我的错 :) 然后我有一个想法,但它很老套:你可以使用 SharedPreferences 来存储你的状态和在开始操作之前检查它 但关键是,我什至没有机会检查,因为 onReceive 仅在前一个完成后才会调用 lol

以上是关于如何阻止相同的广播(来自 PendingIntent)排队?的主要内容,如果未能解决你的问题,请参考以下文章

局域网出现广播风暴怎么办?如何阻止广播风暴?

Android pendingInten 使用方法具体解释

Android 中止广播接收器

如何在 Python 3.5.3 上播放来自互联网广播的流式音频

冲突域广播域

来自 Windows Azure 的请求被某个服务器阻止