如何阻止相同的广播(来自 PendingIntent)排队?
How to stop same broadcast (from PendingIntent) from queuing up?
场景
我已经实现了一个 Android 应用程序小部件。
单击小部件将使用 PendingIntent.getBroadcast(...)
启动广播。
我想在广播接收器的 onReceive
中进行网络请求。
(你问我为什么不使用PendingIntent.getService(...)
然后启动一个IntentService
?这是一个自然的想法,但遗憾的是由于后台限制,如果应用程序无法启动服务不在前台。你可以看看。)
问题
为了证明它有效,我实现了一个示例 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()
的文档来解释:
Keep in mind that the work you do here will block further broadcasts until it completes, so taking advantage of this at all excessively can be counter-productive and cause later events to be received more slowly.
所以我想知道如果同一个广播已经在队列中,是否有办法防止多次触发?
或者任何其他方法来防止由于疯狂点击小部件而导致呼叫排队?
编辑 2:一个可能的小部件解决方案是:
完成操作后,将 timestamp
保存到 SharedPreferences
(如果需要,为每个操作保存)。
再次调用 onReceive
后,检查 timestamp
以获得您首选的 millis
增量,如果增量足够长,则仅再次 运行 操作。
Edit1:下面的答案不适用于widgets,我会离开任何寻找 "regular" 案例
的人
我尝试了很多方法(包括使用 Handler
和 Reflection
),最后我想出了以下解决方案:当您收到一条您不想要的消息时再次获取,unregister
(该特定操作)和 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");
}
}
场景
我已经实现了一个 Android 应用程序小部件。
单击小部件将使用 PendingIntent.getBroadcast(...)
启动广播。
我想在广播接收器的 onReceive
中进行网络请求。
(你问我为什么不使用PendingIntent.getService(...)
然后启动一个IntentService
?这是一个自然的想法,但遗憾的是由于后台限制,如果应用程序无法启动服务不在前台。你可以看看
问题
为了证明它有效,我实现了一个示例 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()
的文档来解释:
Keep in mind that the work you do here will block further broadcasts until it completes, so taking advantage of this at all excessively can be counter-productive and cause later events to be received more slowly.
所以我想知道如果同一个广播已经在队列中,是否有办法防止多次触发?
或者任何其他方法来防止由于疯狂点击小部件而导致呼叫排队?
编辑 2:一个可能的小部件解决方案是:
完成操作后,将 timestamp
保存到 SharedPreferences
(如果需要,为每个操作保存)。
再次调用 onReceive
后,检查 timestamp
以获得您首选的 millis
增量,如果增量足够长,则仅再次 运行 操作。
Edit1:下面的答案不适用于widgets,我会离开任何寻找 "regular" 案例
的人我尝试了很多方法(包括使用 Handler
和 Reflection
),最后我想出了以下解决方案:当您收到一条您不想要的消息时再次获取,unregister
(该特定操作)和 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");
}
}