Bind to a service - call back when bind has been successfully - Android - android

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 :)

Related

Why can't I bind to my foreground service twice from the same object?

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.

How to use interface to communicate between activities after process death?

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

Updating UI from countDownTimer running in a service

So, from what I understand, I should use intents to update the UI of an activity from a service.
But, I am a bit concerned about the efficiency of what I want to do.
Theoretically, it shouldn't really pose a serious problem, but I don't want to have a situation in which I wake up my phone, and have 20-30 onTick-s running at once, each time generating a new intent.
What I want (if possible) is for each onTick's intent to overwrite the previous one, so that the receiver only has process one at a time.
I know pending intents have FLAG_UPDATE_CURRENT, but doing things the right way means (if I understand correctly) using a local broadcast, which doesn't work with pending intents.
This turned out to be surprisingly simple.
A pskink suggested in his comment, all you need is a binder:
Create a class that extends the Binder class. Keep in mind that the Binder class implements IBinder, which is why you can pass an instance of your new class as an instance of IBinder (which is something you will be doing later on)
In this class, implement various functions you want your main activity to be able to use. If you want your service to be able to run things from the activity as well, you can pass an instance of an inner class of the activity, which has the relevant functions in it, or even pass a pointer to the activity (this).
Keep in mind: Do these things at your own risk. If, for example, your activity is destroyed - for example, due a change of orientation, the pointers might become worthless and errors will be thrown
I recommend implementing an invalidate inside your new class to help you handle these situations, but perhaps there are better practices I am not personally familiar with.
Now, you have a two-way communication channel with your activity.
Below is some sample code. Note that if you don't want your service to be destroyed when the activity is, you need (as far as I understand it) to also start it using startService (in addition to using bindService)
public class MyService extends Service {
private IBinder yourBinder; //A class that will connect your service and your activity
#Override
public void onCreate() {
super.onCreate();
yourBinder = new YourBinderClass();
}
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return yourBinder;
}
Your binder class:
public class YourBinderClass extends Binder {
private Service thisService;
public YourBinderClass(Service myService)
{
thisService = myService;
}
public Service getService()
{
return thisService;
}
And finally, inside your main activity:
private YourBinderClass yourBinderClass;
private Service yourService;
private ServiceConnection sc = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
yourBinderClass = (YourBinderClass) service;
yourService = yourBinderClass.getService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
}
};
And to launch the service:
getBaseContext().bindService(new Intent(getBaseContext(),MyService.class), sc, 0)
Note that the service won't be ready immediately, so if your activity is dependent on it, you might want to signal that you are ready to do stuff in onServiceConnected

How to open Activity.openOptionsMenu() from a service

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...).

sendBroadcast(intent) gives a null pointer exception

This is my second post today as fixing the 1st post lead to another problem which I am really struggling on. I want to use Broadcasts and intent to send data packets back from a service to the UI. I managed to bind and start the service successfully
see my other post if you want history and code
The null pointer exception comes on the sendBroadcast() inside the service. The service class does have its constructor re-called after binding the UI to the service. This happens from another class so Context can not be easily used. So I guess the sendBroadcast has no-where to go :(
I suspect this is my problem...the re-calling of the Service constructor after the initial binding. I have onDestroy, onPause and onResume covered with binding an unbinding.
Any ideas or suggestion would be great or maybe I am just going about this wrong is there another way?
EDIT
The previous problem of binding is now solved and due to me being new to the forums a slight delay in accepting the answer...sorry.
The class diagram is like this (it is ported C#code)
Activity doBind (on Curve.class)---> Activity starts a worker class (not treated as a service) Comm.class with a Handler for some comms--> the Comm starts another worker class --> previous worker class finally calls new Curve.class.
It is this last stage Curve.class where the sendBroadcastReceiver() then throws a nullpointer ref becasue the binder is lost.
I tested the broadcastreceiver just with a simple timer cutting out the worker classes in between and it works fine. Problems start when the Curve.class is recalled later further down the hierarchy and the binder gets nulled or "lost".
I removed all references of the binder from Curve except in onBind(). This might not be a good idea. Again the code below does work if used with a simple timer started directly from the UI (no other worker classes).
Some more code here:
The service
public class Curve extends Service
{
private NewCurvePointEventArgs newpoint = null;
private static final String TAG = "Curve";
Intent intent;
int counter = 0;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
IBinder mBinder = new LocalBinder<Curve>(this);
return mBinder;
}
#Override
public void onStart(Intent intent, int startId) {
}
#Override
public void onDestroy() {
}
public Curve(){
}
private void refreshintent(NewCurvePointEventArgs tmp)
{
ArrayList<String> thepoint = new ArrayList<String>();
thepoint.add()//various things added here
Bundle bundle = new Bundle();
// add data to bundle
bundle.putStringArrayList("New_Point", thepoint);
intent = new Intent();
intent.setAction(NEWCURVE_POINT);
intent.putExtra("NEW_POINT", bundle
sendBroadcast(intent);
}
The activity has this code now. the doBind() is called after the onCreate of the activity.
private BroadcastReceiver CurveReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Curve.NEWCURVE_POINT)) {
displaynewpoint(intent);
}
}
};
private ServiceConnection CurveServiceConncetion = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
CurveService = ((LocalBinder<Curve>) service).getService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
CurveService = null;
}
};
#Override
public synchronized void onResume() {
super.onResume();
if(D) Log.e(TAG, "+ ON RESUME +");
}
#Override
public synchronized void onPause() {
super.onPause();
if(D) Log.e(TAG, "- ON PAUSE -");
}
#Override
public void onStop() {
super.onStop();
if(D) Log.e(TAG, "-- ON STOP --");
}
#Override
public void onDestroy() {
super.onDestroy();
if(D) Log.e(TAG, "--- ON DESTROY ---");
unregisterReceiver(CurveReceiver);
unbindService(CurveServiceConncetion);
}
public void doBind(){
Boolean tmp;
tmp = bindService(new Intent(this, Curve.class), CurveServiceConncetion, Context.BIND_AUTO_CREATE);//Context.BIND_AUTO_CREATE
IntentFilter filter = new IntentFilter(Curve.NEWCURVE_POINT);
registerReceiver(CurveReceiver, filter);
}
This problem to me is because the Curve.class has its constructor called again after the initial doBind().
Surely there must be a way around this otherwise I have to load my worker classes closer in hierarchy to the UI with the code from Curve.class???
EDIT
Curve is an object that processes data, constants etc sent from an external machine and contains the processed data in arrays.
The logCat did of course exist I just wasn't looking in the right place here it is
ARN/System.err(10505): java.lang.NullPointerException
WARN/System.err(10505): at android.content.ContextWrapper.sendBroadcast(ContextWrapper.java:271)
WARN/System.err(10505): at pi.droid.Core.Curve.refreshintent(Curve.java:206)
WARN/System.err(10505): at pi.droid.Core.Curve.AddPoint(Curve.java:400)
WARN/System.err(10505): at pi.droid.Core.Comm.CommMeasure$CommMeasurement.AddPoint(CommMeasure.java:363)
WARN/System.err(10505): at pi.droid.Core.Comm.CommMeasure$GenericCommMeasurement.TryProcessData(CommMeasure.java:168)
WARN/System.err(10505): at pi.droid.Core.Comm.CommMeasure$CommMeasurement.ProcessData(CommMeasure.java:302)
WARN/System.err(10505):at pi.droid.Core.Comm.ClientConnection$timer_tick.run(ClientConnection.java:164)
WARN/System.err(10505): at java.util.Timer$TimerImpl.run(Timer.java:289)
You can also see the chain of the 2 other worker classes I use. The constructor of Curve is called after the bind from CommMeasure.
So this is my problem.
Do I need to totally change how my program is set up or is there another way around this?
FINAL EDIT
This code is brought from c# and Curve used eventhandlers to pass data around. I got rid of all them(java listeners) and used android Handler and broadcastreceiver.
It has been suggested that I should pass the CurveService around but this will be problematic as Curve has multiple constructors. The no parameter 1 for the service and then 1 like this
public Curve(Unit XUnit, Unit YUnit)
{ this.Title = "Curve";
this.finished = false;
this.XUnit = XUnit;
this.YUnit = YUnit;
this.YDirection = CurveDirection.Unspecified;
}
so surely instantiating that would be a problem with CurveService, which has to be like this: public Curve(){} ??
Anyway many thanks for all your help and advice.
Final Edit +1..lol
The UI creates a new instance of ClientConnection, that in turns creates a new instance of CommMeasure and finally CommMeasure creates a new instance of Curve to access Curve.addpoint.
I think this thread and the other linked 1 goes beyond a simple android problem and highlights the difficulties of code porting. Any .Net developer for example reading this will learn some peculiarities of android a lot quicker than I did. There is also good working code in there.
Thanks to all who helped especially Justin Breitfeller
The best thing for you to do is follow the example from the Android APIDemos.
A service to be used like you want to use it:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html
Look at the Binding class inside of this file to see a class that does binding like you should:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.html
Finally, if your constructor is being called twice on your service, you aren't binding to your service properly, or perhaps you are unbinding from it and binding to it again unexpectedly.
EDIT
From the stack trace, it appears that CommMeasure needs to have a reference to the instance of Curve that you receive in onServiceConnected.
EDIT 2
If you really want to make your life simple, pass getApplicationContext() to your CommMeasure class and just appContext.sendBroadcast() from that class to send out your point. This will prevent you from requiring a reference to the service in your long-running task.

Categories

Resources