I have a service (it's for communications) that needs to call openOptionsMenu() of the current running activity. I have about 3 or so activities that need to show their options menu upon this request from the service. How can I find this current running activity?
What I'd suggest is that from the active Activity you call bindService() and open a connection to the service. One of the methods defined on your Service's binder connection should take a Binder as an argument. The Activity will instantiate this Binder and pass it into the Service's binder method. When the interesting event happens, the Service should invoke a method on Binder that the Activity passed it.
When one of your Activities goes into the background, it should unregister the Binder it previously passed in and unbind from the Service.
So, you've have an AIDL for the Service that looks something like
interface IServiceConnection {
void registerCallback(IActivityCallback callback);
void unregisterCallback(IActivityCallback callback);
}
Then you have an IActivityCallback.aidl that looks like
oneway interface IActivityCallback {
void openOptionsMenu();
}
In your Service you have
private ArrayList<IActivityCallback> mCallbacks = new ArrayList<IActivityCallback>();
private IServiceConnection.Stub mBinder = new IServiceConnection.Stub() {
public void registerCallback(IActivityCallback callback) {
mCallbacks.add(callback);
}
public void registerCallback(IActivityCallback callback) {
mCallbacks.remove(callback);
}
}
private void onEvent() {
for (int ptr = mCallbacks.size() - 1; ptr > -1; ptr--) {
mCallbacks.get(ptr).openOptionsMenu();
}
}
In your Service's onBind method you return mBinder.
In your activity you have
private IActivityCallback.Stub mPlayerBinder = new IActivityCallback.Stub() {
public void openOptionsMenu() {
// call method in Activity to show options menu
}
}
You bind to your Service from your Activity with a bindService call and when the ServiceConnection receives onServiceConnected you cast its stub to IServiceConnection and then invoke the registerCallback method.
I don't advise that plan of action. Manipulating and UI features without the users permission and foreknowledge is unkind. But to do it, you will need a solid reference to your Activity maintained via a static field or a field in your Application extension (or a singleton state machine etc...).
Related
I have an object in app A that implements IServiceConnection which binds to a foreground Service in a different app (app B):
public class MyServiceConnection : Java.Lang.Object, IServiceConnection
{
private IBinder _binder;
private TaskCompletionSource<bool> _connectTcs;
public MyServiceConnection()
{
BeginConnect();
}
public void BeginConnect() => ConnectAsync().ContinueWith(EndConnect);
public Task<bool> ConnectAsync(CancellationToken ct = default)
{
if (_connectTcs?.IsCompleted ?? true)
{
_connectTcs = new TaskCompletionSource<bool>();
ct.Register(() => _connectTcs.TrySetCanceled(ct));
var package = "com.sample.appb";
var componentName = new Componentname(package, $"{package}.ServiceInB");
var service = new Intent().SetComponent(componentName);
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
Application.Context.StartForegroundService(service);
}
else
{
Application.Context.StartService(service);
}
Application.Context.BindService(service, this, Bind.AutoCreate);
}
return await _connectTcs;
}
private void EndConnect(Task task)
{
// handle task result
}
public void OnServiceConnected(ComponentName name, IBinder service)
{
_binder = service;
}
public void OnServiceDisconnected(ComponentName name)
{
_binder = null;
}
// methods that are called on the service
}
When the object is created, I bind to the Service and it is successful. When app B crashes or I call am force-stop on app B, OnServiceDisconnected() gets called, which is expected. I handle the lost connection by connecting again, but on the second connection OnServiceConnected() never gets called, so I never get my IBinder object to call methods on. If I restart app A I can perform a single bind again. How do I properly get the Service binder multiple times during one run time?
To solve this issue, I split the original object into two objects: One that inherits from IServiceConnection and holds the IBinder from the service, and another that holds instances of these. When the holder needs to connect, it first disposes any held instance of the IServiceConnection, then calls Context.BindService() with a new instance. When the holder needs to disconnect, it calls Context.UnbindService(), then disposes the IServiceConnection instance.
I am building an SDK and need to implement callbacks between activities, without actually finish an activity. I previously used onActivityResult to provide results back to caller activity. However, this closes activity and I need to deliver callback, without finishing activity from SDK. My current implementation:
fun initializeSDK(){
SDK.getInstance().initialize(resultsCallbackImpl)
}
val resultsCallbackImpl:ResultsCallback = object : ResultsCallback {
override fun response1() {
}
override fun response2() {
}
};
For example, the client calls initializeSDK() from his activity after the button click. Then the client passes interface as parameter, which is set as a property in SDK singleton. Then I use that interface to return results.
The problem occurs after process death. The interface becomes null, because it is not serialized and I can't return callback to client anymore. How should I edit my code to tackle this issue? Is it even possible?
I know that client can initialize SDK in the application class, then it will be re-set after process death. However, such an approach will result in difficulty for the client to communicate results back to activity from application class.
Update:
Do a right click on the project tree and add a new AIDL file called IMyAidlInterface.aidl:
package com.test.aidlsample;
import com.test.aidlsample.MyData;
interface IMyAidlInterface {
List<MyData> getData(long id);
}
If you need to return objects to your client you need to declare and define them as parcelable and import them in aidl file too, here is the MyData.aidl that should be beside the other aidl file:
package com.test.aidlsample;
// Declare MyData so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable MyData;
and this is MyData.java in the java folder:
public class MyData implements Parcelable {
private long productId;
private String productName;
private long productValue;
public MyData(long productId, String productName, long productValue) {
this.productId = productId;
this.productName = productName;
this.productValue = productValue;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.productId);
dest.writeString(this.productName);
dest.writeLong(this.productValue);
}
protected MyData(Parcel in) {
this.productId = in.readLong();
this.productName = in.readString();
this.productValue = in.readLong();
}
public static final Parcelable.Creator<MyData> CREATOR = new Parcelable.Creator<MyData>() {
#Override
public MyData createFromParcel(Parcel source) {
return new MyData(source);
}
#Override
public MyData[] newArray(int size) {
return new MyData[size];
}
};
}
Now build the project so Stub class gets built. After a successful build continue with the service:
public class SdkService extends Service {
private IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
#Override
public List<MyData> getData(long id) throws RemoteException {
//TODO: get data from db by id;
List<MyData> data = new ArrayList<>();
MyData aData = new MyData(1L, "productName", 100L);
data.add(aData);
return data;
}
};
#Nullable
#Override
public IBinder onBind(Intent intent) {
return binder;
}
}
and add the service to the sdk manifest. If you are adding sdk as a dependency to the client like: implementation project(':sdk') you don't need to add AIDL files to client. If not, you have to add them and build the client application. Now, only remains to implement the client activity:
public class MainActivity extends AppCompatActivity {
IMyAidlInterface mService;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mService = IMyAidlInterface.Stub.asInterface(service);
try {
List<MyData> data = mService.getData(1L);
updateUi(data);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
}
};
private void updateUi(List<MyData> data) {
//TODO: Update UI here
}
#Override
protected void onResume() {
if (mService == null) {
Intent serviceIntent = new Intent();
//CAREFUL: serviceIntent.setComponent(new ComponentName("your.client.package", "your.sdk.service.path"));
serviceIntent.setComponent(new ComponentName("com.test.sampleclient", "com.test.aidlsample.SdkService"));
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
} else {
try {
updateUi(mService.getData(1L));
} catch (RemoteException e) {
e.printStackTrace();
}
}
super.onResume();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
every time your client activity gets visibility, it gets data from sdk service. Just build your logic over this template. In sdk activity save data to a database and in service query them from database. I've used simple parameters in this sample.
I assumed your sdk is a library in the client app. If not, you need to do some small modifications maybe. And as I mentioned before you can find more details here: Android Interface Definition Language (AIDL). There are lots of samples and even more Q/A here in the SO on the subject. Good luck.
Original: You need to get callbacks from an activity that is currently invisible since your SDK activity is in front, right? To do that you can create a database for your SDK, persist data to your database and get data via an AIDL in the starting activity:
SdkService sdkService;
CallbackData callbackData
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
sdkService = SdkService.Stub.asInterface(service);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
sdkService = null;
}
};
in onCreate:
Intent i = new Intent()
i.setClassName("your.sdk.packageName", "your.sdk.service.path.and.name");
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
and in whenever needed:
if(sdkService != null){
callbackData = sdkService.getCallbacks();
updateUI();
}
Just be careful getting a binder is an async job so if you call bindService and right after call sdkService.getCallbackData you get a NullPointerException. So you might want to move getCallbacks and updateUI inside the onServiceConnected and call bindService in onResume so every time activity becomes visible you would check if there is CallbackData so you can update your UI or whatever.
You cannot use interfaces directly to communicate between activities.
As soon as you start a new activity and new activity becomes visible android OS can kill 1st activity anytime (you can try this with a flag inside developer option "Don't keep activities"). So user of your SDK will complain about certain random "null pointer exception".
So, Now if you want to share data between current and previous screen, you might have to rethought your solution using Fragments.
Exposing your UI using a fragment and communicating back your result to activity which then would update proper fragment which needs the data.
I faced similar issue in one existing app which I was asked to fix. I switched entire app to fragments and single activity, first to release a hot fix.
The problem occurs after process death. The interface becomes null, because it is not serialised and I can't return callback to client anymore. How should I edit my code to tackle this issue? Is it even possible?
This is not possible. If the client process dies, all of its executing code - including your SDK - gets wiped away.
I know that client can initialise SDK in the application class, then it will be re-set after process death. However, such approach will result in difficulty for client to communicate results back to activity from application class.
So what? If the client Activity is restarted, it should call the SDK again to set a new callback instance which you can use from that point forward.
You can use a sharedviewmodel that is bound to both activities; have a mutablelivedata variable that you can observe from the two activities.
ideally on the first activity you can just put the value inside the mutablelivedata variable. Then on the second activity get the activity.
Follow the following link to give you a guideline.
ViewModel Overview
I have a service in Android that encapsulates a framework that has a start method. The service boils down to something like this, many things omitted:
public class MyService extends Service {
private IBinder thisBinder;
public MyService(){
thisBinder = new LocalBinder();
}
#Override
public IBinder onBind(Intent intent) {
return thisBinder;
}
public void start(Map<String, Object> options)
{
getDriverManager().start(options);
}
}
I also have a bridging class that makes calls to the service:
public class MyServiceBridge implements ServiceConnection {
private boolean started = false;
private boolean bound = false;
private MyService myService;
public MyServiceBridge(Context context){
this.context = context;
}
public void bindService(){
Intent intent = new Intent(getContext(), MyService.class);
getContext().bindService(intent, this, getContext().BIND_AUTO_CREATE);
getContext().startService(intent);
}
// Here's a sample call, and the one that is relevant
public void start(Map<String, Object> options){
setOptions(options);
if(bound == true){
getMyService().start(options);
}
else{
started = true;
}
}
}
I call the bridge's start method in order to run the service. This works fine, except in this particular situation (so far). The MyApplication class calls the bridge's start method on onCreate:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
getServiceBridge().start(null);
}
}
This, according to the docs is "Called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created.". Indeed it appears to be so, because the service does not start, and instead starts when I close the app (odd, at least). This works if I move the call to an activity's onCreate method, but that's not ideal because I can also stop the service, and I want the service to run for the lifetime of the app. That is, the service should start when the app starts and stop when the app terminates. Does this make sense? Is there another way to do this?
In my opinion, you did a good job when you decided to run service in Application onCreate. but it is strange to hear that service is started when you close the app.
I have done this several times, and service starts in application onCreate which must be called.
Have you checked if your application is alive and run in background? Please make sure that you killed you application before testing. Use Task Killer or something like that, to be sure that application is always freshly started.
Sadly, Android does not have appropriate mechanism to notify you when application is exited, because it is still alive until system decides to kill it and free resources.
I've come to a point where I don't know an elegant way to do this.
Let's say I've a Fragment, named FragmentA, and a Service named BackupService
On FragmentA I bound it to the BackupService using:
private ServiceConnection backupServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
backupBoundService = binder.getService();
isBound = true;
// How to let the fragment know this has happened?
// Use an eventBus? EventBus.getDefault().post(backupBoundService); ?
Log.d(TAG, "On Service Connected -> Yup!");
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
And:
Intent intent = new Intent(ApplicationContextProvider.getContext(), BackupsService.class);
ApplicationContextProvider.getContext().bindService(intent, backupServiceConnection, Context.BIND_AUTO_CREATE); // Using application context
Now I know the binding is an asynchronous task and here is where my question comes in.
I came up with the idea of using an EventBus but I don't find it elegant as the fragment would be posting the object (in this case backupBoundService), referencing the service, and at the same time would be listening/receiving the event from the bus, eg, would be the same fragment posting and receiving the event (posting to himself).
Is there an elegant way to get a reference for the running service when the fragment is bounded to it? I'm quite sure there's a pattern for this case but I've been googling and searching here to no luck so far.
Hi you can use eventbus or you can create simple call back methods using Interfcae for you requirement.
If you don't have idea to create call backs using interface then look this piece of code.
It is same like eventbus :)
// The callback interface
interface MyCallback {
void callbackCall();
}
// The class that takes the callback
class Worker {
MyCallback callback;
void onEvent() {
callback.callbackCall();
}
public void setCallBack(MyCallback callback)
this.callback=callback;
}
/////////////////////////////
class Callback implements MyCallback {
....
Worker worker= new Worker()
Worker.setCallback(this);
.. .
void callbackCall() {
// callback code goes here
//onEvent this method will execute
}
}
Hope this will helpful.
Good luck :)
I am running a Socket.IO client on my Android app and I am having trouble passing data from the Service (that handles the connection of the socket) to one of the Fragments.
In one of my Activities, I open up a fragment that has a profile page. When the profile fragment opens, I emit an event to the server asking for the profile information.
I am getting the information back from the server with no problems, but I am having trouble sending that data (a JSON string) to the current fragment.
What would be the best practice to pass this information from the Service to the Fragment (or the Activity)?
Thank you!
You can bind to the service from Activity and create a service connection. So that you will have the instance of service to communicate.
See my answer here How to pass a handler from activity to service on how to bind to service and establish a service connection.
Apart from this, have an interface defined in your service
public interface OnServiceListener{
public void onDataReceived(String data);
}
Add a set Listener method in service to register the listener from Activity
private OnServiceListener mOnServiceListener = null;
public void setOnServiceListener(OnServiceListener serviceListener){
mOnServiceListener = serviceListener;
}
Next, In your Activity implement the Listener interface
public class MainActivity extends ActionBarActivity
implements CustomService.OnServiceListener{
#Override
public void onDataReceived(String data) {
}
}
Next, When the service connection is established , register the listener
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
customService = ((CustomService.LocalBinder) iBinder).getInstance();
customService.setOnServiceListener(MainActivity.this);
}
Now, When you receive the data in service pass the data to the Activity through onDataReceived method.
if(mOnServiceListener != null){
mOnServiceListener.onDataReceived("your data");
}