Last active
August 28, 2023 07:58
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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