LocalBroadcastManager and the Activity lifecycle
android, android-lifecycle, localbroadcastmanager
Solution
As documented in the `onRetainNonConfigurationInstance()` method of the `Activity`, the system disables the message queue processing in the main thread while the `Activity` is in the process of being restarted. This ensures that events posted to the main thread will always be delivered at a stable point in the lifecycle of the `Activity`.
However, there seems to be a design flaw in the `sendBroadcast()` method of `LocalBroadcastManager`, in that it evaluates the registered `BroadcastReceiver`s from the posting thread before queuing the broadcast to be delivered on the main thread, instead of evaluating them on the main thread at the time of broadcast delivery. While this enables it to report the success or failure of the delivery, it does not provide the proper semantics to allow `BroadcastReceiver`s to be safely unregistered temporarily from the main thread without the possibility of losing potential broadcasts.
The solution to this is to use a `Handler` to post the broadcasts from the main thread, using the `sendBroadcastSync()` method so that the broadcasts are delivered immediately instead of being reposted. Here's a sample utility class implementing this:
public class LocalBroadcastUtils extends Handler {
private final LocalBroadcastManager manager;
private LocalBroadcastUtils(Context context) {
super(context.getMainLooper());
manager = LocalBroadcastManager.getInstance(context);
}
@Override
public void handleMessage(Message msg) {
manager.sendBroadcastSync((Intent) msg.obj);
}
private static LocalBroadcastUtils instance;
public static void sendBroadcast(Context context, Intent intent) {
if (Looper.myLooper() == context.getMainLooper()) {
// If this is called from the main thread, we can retain the
// "optimization" provided by the LocalBroadcastManager semantics.
// Or we could just revert to evaluating matching BroadcastReceivers
// at the time of delivery consistently for all cases.
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
} else {
synchronized (LocalBroadcastUtils.class) {
if (instance == null) {
instance = new LocalBroadcastUtils(context);
}
}
instance.sendMessage(instance.obtainMessage(0, intent));
}
}
}
Problem
So I'm looking into the feasibility of changing from callback interfaces to local broadcasts for some long-running network operations. Since the Activity lifecycle creates all sorts of complication for asynchronous requests that need to modify the UI (disconnect the Activity from the callback in `onDestroy()`, don't modify `FragmentTransaction`s after `onSaveInstanceState()`, etc.), I'm thinking that using local broadcasts makes more sense, and we can just register/unregister the receiver at the lifecycle events. However, when the Activity is destroyed and recreated during a configuration change, there's this small window of time when the broadcast receiver would not be registered (in between `onPause()/onResume()` for example). So if, for example, we start an asynchronous request in `onCreate()` if `savedInstanceState == null` (e.g. for the first launch of the Activity), isn't it possible that the broadcast sent upon completion would be lost if the user changes their device orientation right before the operation completes? (i.e. the receiver is unregistered on onPause(), then the operation completes, then the receiver is re-registered in onResume()) If that's the case, then it adds a lot of extra complexity we would need to add support for, and it's probably not worth the switch. I've looked into other things such as the Otto EventBus library but I'm not sure whether or not it has the same concerns to worry about.