Skip to content

Instantly share code, notes, and snippets.

@Jenjen1324
Last active August 28, 2023 07:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Jenjen1324/4a0c03beff827082cb641fc8fe2c4e71 to your computer and use it in GitHub Desktop.
Save Jenjen1324/4a0c03beff827082cb641fc8fe2c4e71 to your computer and use it in GitHub Desktop.
Easy Communication between an Activity and BroadcastReceiver which is safe from Memory Leaks
package ch.jvogler.aebus;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Utility Class for Communicating between an Activity and a BroadcastReceiver
* <p>
* Usage: Create a static instance of this class accessible from the Activity and BroadcastReceiver.
* <h1>BroadcastReceiver Side</h1>
* When there's a broadcast incoming pass it to {@link AEBus#parseReceiverMessage(Object, Intent)} and pass your callback with it.
* <h1>Activity Side</h1>
* When there's a new Intent (either {@link Activity#onNewIntent(Intent)} or {@link Activity#onCreate(Bundle)} [ with {@link Activity#getIntent()} ])
* pass it to {@link AEBus#parseActivityIntent(Object, Intent)}. and it will call the method in the callback.
* <p>
* Created by jvogler on 26.01.2018.
*/
public class AEBus<A, B> {
private static final String TAG = "AEBus";
private Context ctx;
private Class<A> activityReceiverClz;
private Class<B> broadcastReceiverClz;
private Class<? extends Activity> targetActivity;
private String broadcastAction;
/**
* Define two interfaces, one for incoming messages on the broadcastSide and one for incoming messages on the activitySide.
* The messages will be sent to the targetActivity and for the other way to the broadcastAction. Your BroadcastReceiver will have to listen
* for that action.
*
* @param applicationContext Context used for sending Intents
* @param activityReceiverClz The interface used for calls on the Activity
* @param broadcastReceiver The interface for calls on the BroadcastReceiver
* @param targetActivity Target Activity to send Intents to
* @param broadcastAction Action which the BroadcastReceiver listens on
*/
@SuppressWarnings("unchecked")
public AEBus(Context applicationContext,
Class<A> activityReceiverClz, Class<B> broadcastReceiver,
Class<? extends Activity> targetActivity, String broadcastAction) {
this.ctx = applicationContext.getApplicationContext();
this.activityReceiverClz = activityReceiverClz;
this.broadcastReceiverClz = broadcastReceiver;
this.targetActivity = targetActivity;
this.broadcastAction = broadcastAction;
}
/**
* Call this method from the BroadcastReceiver to get the Object to send events to. You can store this safely in a InstanceVariable
*
* @return Proxy of the activityReceiver interface
*/
@SuppressWarnings("unchecked")
public A getActivityEmitter() {
return (A) Proxy.newProxyInstance(activityReceiverClz.getClassLoader(), new Class<?>[]{activityReceiverClz},
getInvocationHandler(this::emitToActivity));
}
/**
* Call this method from the Activity to get the Object to send events to. You can store this safely in a InstanceVariable
*
* @return Proxy of the broadcastReceiver interface
*/
@SuppressWarnings("unchecked")
public B getBroadcastEmitter() {
return (B) Proxy.newProxyInstance(broadcastReceiverClz.getClassLoader(), new Class<?>[]{broadcastReceiverClz},
getInvocationHandler(this::emitToBroadcast));
}
/**
* Call this from the Activity when there's a new Intent, either from {@link Activity#onNewIntent(Intent)} or on creation with {@link Activity#getIntent()}
*
* @param receiver Interface implementation of the ActivityReceiver. Best used when your Activity implements this
* @param intent The received Intent
* @return True if the Intent was passed from the BroadcastReceiver and handled appropriately
*/
public boolean parseActivityIntent(A receiver, Intent intent) {
Log.d(TAG, "Parsing from Activity: " + intent);
return parseIntent(receiver, intent);
}
/**
* Call this from the BroadcastReceiver when there's an incoming message {@link android.content.BroadcastReceiver#onReceive(Context, Intent)}
*
* @param receiver Interface implementation of the BroadcastReceiverInterface. Best used when your BroadcastReceiver implements this
* @param intent The received Intent
* @return True if the Intent was passed from the Activity and handled appropriately
*/
public boolean parseReceiverMessage(B receiver, Intent intent) {
Log.d(TAG, "Parsing from Receiver: " + intent);
return parseIntent(receiver, intent);
}
/**
* Creates a InvocationHandler which serializes the called method and parameters and passes it as a Bundle to the BundleRunnable
*
* @param runnable The method that will be invoked when a method was invoked
* @return The InvocationHandler used for the Proxy
*/
private InvocationHandler getInvocationHandler(BundleRunnable runnable) {
return (proxy, method, args) -> {
Bundle b = new Bundle();
b.putString("methodName", method.getName());
if (args != null) {
Serializable sArgs[] = new Serializable[args.length];
int i = 0;
for (Object o : args) {
if (!(o instanceof Serializable)) {
throw new IllegalArgumentException("Can't serialize a parameter in Method! Method: " + method.getName() + " ArgVal: " + o);
}
sArgs[i] = (Serializable) o;
}
b.putSerializable("arguments", sArgs);
}
runnable.call(b);
return null;
};
}
/**
* Parses an Intent and calls the method on the Object
*
* @param receiver The Object to call the serialized method from the Intent
* @param intent The intent containing the method call
* @return True if there was a serialized method in the Intent
*/
private boolean parseIntent(Object receiver, Intent intent) {
if (intent.hasExtra("methodName")) {
try {
Method m = receiver.getClass().getMethod(intent.getStringExtra("methodName"));
if (intent.hasExtra("arguments")) {
Object[] args = (Object[]) intent.getSerializableExtra("arguments");
m.invoke(receiver, args);
} else {
m.invoke(receiver);
}
return true;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Failed to run method", e);
}
}
return false;
}
/**
* Sends a Intent to the Activity with the Bundle
*
* @param b The Bundle to send
*/
private void emitToActivity(Bundle b) {
Log.d(TAG, "Emitting to Activity: " + b.toString());
Intent i = new Intent(ctx, targetActivity);
i.putExtras(b);
i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(i);
}
/**
* Sends a Intent to the BroadcastReceiver with the Bundle
*
* @param b The Bundle to send
*/
private void emitToBroadcast(Bundle b) {
Log.d(TAG, "Emitting to BroadcastReceiver: " + b.toString());
Intent i = new Intent(broadcastAction);
i.putExtras(b);
ctx.sendBroadcast(i);
}
interface BundleRunnable {
void call(Bundle b);
}
}
package ch.jvogler.aebus;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
/**
* Created by jvogler on 29.01.2018.
*/
public class ExampleActivity extends Activity implements Listeners.ActivityListener {
private Listeners.BroadcastListener emitter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
emitter = ExampleBroadcastReceiver.BUS.getBroadcastEmitter();
ExampleBroadcastReceiver.BUS.parseActivityIntent(this, getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
if(!ExampleBroadcastReceiver.BUS.parseActivityIntent(this, getIntent())) {
super.onNewIntent(intent);
}
}
void example() {
emitter.car();
emitter.goo();
}
@Override
public void foo() {
}
@Override
public void bar() {
}
}
package ch.jvogler.aebus;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import ch.raumcode.positioningservice.core.api.RCApplication;
/**
* Replace RCApplication.getContext() with a static reference to the ApplicationContext (this is allowed as that Object lives for the runtime of your App).
* It's best to create a static reference in your {@link android.app.Application}
*
* Created by jvogler on 29.01.2018.
*/
public class ExampleBroadcastReceiver extends BroadcastReceiver implements Listeners.BroadcastListener {
private static final String BROADCAST_EVENT = "ch.jvogler.aebus.EVENT";
static final AEBus<Listeners.ActivityListener, Listeners.BroadcastListener> BUS = new AEBus<>(
RCApplication.getContext(),
Listeners.ActivityListener.class, Listeners.BroadcastListener.class,
ExampleActivity.class, BROADCAST_EVENT
);
private Listeners.ActivityListener emitter;
public ExampleBroadcastReceiver() {
emitter = BUS.getActivityEmitter();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BROADCAST_EVENT);
RCApplication.getContext().registerReceiver(this, intentFilter);
}
@Override
public void onReceive(Context context, Intent intent) {
BUS.parseReceiverMessage(this, intent);
}
@Override
public void goo() {
emitter.bar();
emitter.foo();
}
@Override
public void car() {
}
}
package ch.jvogler.aebus;
/**
* Created by jvogler on 29.01.2018.
*/
public class Listeners {
public interface ActivityListener {
void foo();
void bar();
}
public interface BroadcastListener {
void goo();
void car();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment