I have implemented an Android App Widget.
Clicking on the widget will launch a broadcast by using PendingIntent.getBroadcast(...)
.
I want to do a network request inside onReceive
of the Broadcast Receiver.
(You ask why don't I use PendingIntent.getService(...)
and launch an IntentService
? Well that's a natural idea, but sadly due to background limitations, the service cannot be started if the app is not in foreground. You can take a look at this post.)
To prove it works, I have implemented a sample 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()
}
}
}
Yeah it works.
However, I noticed that if I tap on the widget multiple times, multiple broadcast will be created and queued up one by one until the previous one's pendingResult.finish()
is called.
This can be explained by the documentation of 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.
So I want to know if there is a way to prevent the same broadcast from firing multiple times if it is already in the queue?
Or any other way to prevent queued up calls due to crazy clicks on the widget?
Edit 2: a possible solution for a widget is:
Save a timestamp
to the SharedPreferences
(for each action if you need it) once your action is completed.
Once the onReceive
is called again check the timestamp
for your preferred millis
delta and only run the action again if the delta is long enough.
Edit1: the answer below does not work for widgets, I'll leave it for anyone looking for the "regular" case
I've tried quite a few things (including using a Handler
and Reflection
), finally I've come up with the following solution: when you receive a message you do not want to get again, unregister
(that specific action) and register
when the action is done. the BroadcastReceiver
is below and 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");
}
}