Background: I have a few services (open for other apps to use) which run in the same process. The RPC is implemented using AIDL and therefore the services have to be open to multiple threads.
This leads me to the question: can a client still use a binder to make RPC calls even after the client unbound from the service. If so then:
Issue: I am worried that a client (either intentionally or accidentally) will bind to one of the services, unbind (possibly destroying the service because there are no other clients bound to it), then still use the binder to make remote calls. But, because the service is destroyed (resources have been released) the calls may cause exceptions. While some exceptions are implicitly "passed" back to the client (e.g. NullPointerException and IllegalStateException), most aren't and will propagate all the way back and crash the process, which may contain other alive services.
EDIT: a client can use a binder after unbinding (example code below). Now, what is the best way for a destroyed service to handle/respond to these calls (keeping in mind that the calls happen on a different thread than the thread which call onDestroy)?
public class MyService extends Service {
private volatile boolean destroyed = false;
private final IMyServiceInterface.Stub binder = new IMyServiceInterface.Stub() {
#Override
public boolean isDestroyed() {
// even worse, what if... `if (destroyed) { throw new RuntimeException(); }`
return destroyed;
}
};
#Override
public IBinder onBind(Intent intent) { return binder; }
#Override
public void onDestroy() { destroyed = true; }
}
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener((view) ->
bindService(new Intent(this, MyService.class), serviceConnection, Context.BIND_AUTO_CREATE));
}
private final ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
final IMyServiceInterface service = IMyServiceInterface.Stub.asInterface(binder);
attemptExperiment(service);
}
#Override
public void onServiceDisconnected(ComponentName name) {
throw new AssertionError("service wasn't supposed to crash...");
}
};
private void attemptExperiment(IMyServiceInterface service) {
new Thread(() -> {
Toasts.show(this, "unbinding service and sleeping for 5 seconds...");
unbindService(serviceConnection);
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
} catch (InterruptedException e) {
return;
}
final boolean destroyed;
try {
destroyed = service.isDestroyed();
} catch (RemoteException e) {
Toasts.show(this, "service remote exception");
return;
}
Toasts.show(this, "service destroyed: " + destroyed);
}).start();
}
}
I've read the documentation about services and many examples on the web. However, most examples just include the same code and I still don't understand the life cyle of a background service completely.
Here is what I'm trying to do:
Start Activity
Activity starts a service, receiving location data
Exit Activity
Service keeps collecting
Start Activity
Bind to service and do some stuff, e.g. display some results
And this is what I've done to achive it:
Implemented a service (not an IntentService)
tried to start it in two ways:
Start the service by binding to it:
bindService(intent, myLocationService, Context.BIND_AUTO_CREATE);
This results in the service beeing created when the activity starts and beeing destroyed, when I call unbindService(...), e.g. in the onStop() method of the activity.
Start the service by creating it, explicitly:
Intent intent = new Intent(this, MyLocationService.class);
startService(intent);
This results in the service beeing created when the activity starts. When exiting the activity, the services crashes (although, I've implemented all cleaning up) and gets started again. When starting the activity again, the services starts once more, too.
So, how can I start, bind and unbind a serivce gracefully, without all that crashing and restarting behaviour?
These are the relevant code lines of the service:
public class MyLocationService extends Service {
final IBinder myServiceBinder = new MyServiceBinder();
public static class MyServiceConnection implements ServiceConnection {
MyLocationService service;
boolean bound = false;
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
MyServiceBinder binder = (MyServiceBinder)iBinder;
service = binder.getService();
bound = true;
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
bound = false;
}
public MyLocationService getService() {
return service;
}
public boolean isBound() {
return bound;
}
}
/**
* Binder for GPSService
*/
public class MyServiceBinder extends Binder {
MyLocationService getService() {
return MyLocationService.this;
}
}
public MyLocationService() {
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return myServiceBinder;
}
}
These are the relevant code lines of the activity:
public class MyMainActivity extends AppCompatActivity {
MyLocationService.MyServiceConnection myServiceConnection;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MyLocationService.class);
startService(intent);
}
#Override
protected void onStart() {
super.onStart();
}
#Override
protected void onResume() {
super.onResume();
Intent intent = new Intent(this, MyLocationService.class);
bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onPause() {
super.onPause();
unbindService(myServiceConnection);
}
#Override
protected void onStop() {
super.onStop();
}
}
https://developer.android.com/guide/components/activities/activity-lifecycle.html
Depending on what your requirements are, should be onPause() onResume() or onStart() and onStop().
If you want the service to run in the background you could use
/**
* Called when all clients have disconnected from a particular interface
* published by the service. The default implementation does nothing and
* returns false.
*
* #param intent The Intent that was used to bind to this service,
* as given to {#link android.content.Context#bindService
* Context.bindService}. Note that any extras that were included with
* the Intent at that point will <em>not</em> be seen here.
*
* #return Return true if you would like to have the service's
* {#link #onRebind} method later called when new clients bind to it.
*/ public boolean onUnbind(Intent intent) {
return false; }
onRebind() to do something.
I'm making an Mp3 player that uses a service to be able to play music when the phone is closed or the app minimized. However, now when I go into an add playlist activity and try to get back to the main activity, I get the following error message:
java.lang.RuntimeException: Unable to destroy activity
{dv606.mp3player/dv606.mp3player.MP3Player}:
java.lang.IllegalArgumentException: Service not registered:
dv606.mp3player.MP3Player$6#6ea6ce6
Which confuses me since it says that the main activity itself is the service which it is not. The "MusicService" is a separate class.
I use the following way to close the main activity:
#Override
protected void onDestroy() {
super.onDestroy();
appTerminated(isBound, connection);
}
appTerminated:
public static void appTerminated(Boolean b, ServiceConnection c) {
if (b && c != null && musicservice != null) {
musicservice.unbindService(c);
}
}
connection is obtained as follows:
public static ServiceConnection connection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
musicservice = ((MusicService.MusicBinder) service).getService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
musicservice = null;
}
};
isBound like this:
isBound = bindService(intentt, connection, Context.BIND_AUTO_CREATE);
In my music service class i have these two methods as well:
#Override
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
MP3Player.appTerminated(MP3Player.isBound, MP3Player.connection);
}
#Override
public void onDestroy() {
super.onDestroy();
MP3Player.appTerminated(MP3Player.isBound, MP3Player.connection);
}
I am totally at a loss here, both because of the error message and also because I thought that this was the way to implement, bind and unbind a service from a main activity.
I guess I was wrong somewhere.
You have to make sure your musicservice is not null when you unbind it.
I am stuck with the problem of Activity + Service in that I have following number of Activities and Services.
Activities:
LoginActivity => OrderListActivity => AddOrderActivity => ConfirmOrderActivity
Services:
ReceivingOrderService - Receiving New Data From Server
SendingOrderService - Sending new Data to Server
Above both Service Calling from another Separate Service on duration of some interval.
CheckAutoSyncReceivingOrder - To call ReceivingOrderService (Interval 15Mins)
CheckAutoSyncSendingOrder - To call SendingOrderService (Interval 3Mins)
CheckAutoSyncReceivingOrder:
public class CheckAutoSyncReceivingOrder extends Service {
Timer timer;
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
if(timer != null) {
timer.cancel();
Log.i(TAG, "RECEIVING OLD TIMER CANCELLED>>>");
}
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if(InternetConnection.checkConnection(getApplicationContext())) {
if(getDatabasePath(DatabaseHelper.DATABASE_NAME).exists())
startService(new Intent(CheckAutoSyncReceivingOrder.this, ReceivingOrderService.class));
} else {
Log.d(TAG, "Connection not available");
}
}
}, 0, 60000); // 1000*60*15 = 9,00,000 = 15 minutes
}
#Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if(timer != null)
timer.cancel();
Log.d(TAG, "Stopping Receiving...");
}
}
CheckAutoSyncSendingOrder:
public class CheckAutoSyncSendingOrder extends Service {
Timer timer;
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
if(timer != null) {
timer.cancel();
Log.i(TAG, "OLD TIMER CANCELLED>>>");
}
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
Log.i(TAG, ">>>>>>>> SENDING AUTO SYNC SERVICE >>>>>>>>");
if(InternetConnection.checkConnection(getApplicationContext())) {
if(getDatabasePath(DatabaseHelper.DATABASE_NAME).exists())
startService(new Intent(CheckAutoSyncSendingOrder.this, SendingOrderService.class));
} else {
Log.d(TAG, "connection not available");
}
}
}, 0, 120000); // 1000*120*15 = 1,800,000 = 15 minutes
}
#Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if(timer != null)
timer.cancel();
Log.d(TAG, "Stopping Sending...");
}
}
ConfirmOrderActivity#Final Task which i have called for Insert Data:
new AsyncTask<Void, Void, Integer>() {
ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
progressDialog = new ProgressDialog(
ConfirmOrderProductActivity.this);
progressDialog.setMessage("Inserting "
+ (isInquiry ? "Inquiry" : "Order") + "...");
progressDialog.setCancelable(false);
progressDialog
.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.show();
}
#Override
protected Integer doInBackground(Void... params) {
// TODO Auto-generated method stub
int account_id = context.getSharedPreferences(PREF_DATA,
MODE_APPEND).getInt(DATA_ACCOUNT_ID, 0);
/**
* Check Whether isInquiry or not...
*/
product_type = isWeight ? 1 : 0;
if (isInquiry) {
/*
* INSERTING DATA IN INQUIRY TABLE
*/
return m_inquiry_id;
} else {
/*
* INSERTING DATA IN ORDER TABLE
*/
return m_order_id;
}
}
#Override
protected void onPostExecute(Integer m_order_id) {
// TODO Auto-generated method stub
super.onPostExecute(m_order_id);
progressDialog.dismiss();
if (dbHelper.db.isOpen())
dbHelper.close();
String title = "Retry";
String message = "There is some problem, Go Back and Try Again";
AlertDialog.Builder alert = new AlertDialog.Builder(
ConfirmOrderProductActivity.this);
if (m_order_id != -1) {
title = isInquiry ? "New Inquiry" : "New Order";
message = isInquiry ? "Your Inquiry Send Successfully." : "Your Order Saved Successfully.";
alert.setIcon(R.drawable.success).setCancelable(false);
} else {
alert.setIcon(R.drawable.fail).setCancelable(false);
}
alert.setTitle(title).setMessage(message)
.setPositiveButton("OK", new OnClickListener() {
#Override
public void onClick(DialogInterface dialog,
int which) {
// TODO Auto-generated method stub
dialog.dismiss();
startActivity(new Intent(
ConfirmOrderProductActivity.this,
FragmentChangeActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
/* Opening Left to Right Animation */
overridePendingTransition(R.anim.right_out,
R.anim.right_in);
}
});
AlertDialog alertDialog = alert.create();
alertDialog.show();
}
}.execute();
Everything is working fine as per flow of inserting records in database.
After Adding Inquiry:
Destroying Activity and Getting following Logcat:
Main Problem:
When I placed order successfully from ConfirmOrderActivity, It is displaying AlertDialog of Success Message which is cancellable false. When I Stop application from this Activity, Its calling both CheckAutoSyncReceivingOrder and CheckAutoSyncSendingOrder automatically.
Edited:
I am calling both Service from LoginActivity only, after that it
will called automatically after given intervals But Problem occurs
when I destroy ConfirmOrderActivity when dialog is shown.
I didn't know why it happens that Why its running automatically when I stop Activity Directly.
I have tried onStartCommand() with START_NON_STICKY in Service but not working. (as START_STICKY is default.)
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
Is there any solution?
You need to either run your service in the foreground so when the activity is destroyed so will the service or use a bound service and manage the binding with the activity lifecycle, so it is not continually restarted when the activity is destroyed.
From this android docs tutorial Bound Services
You need to do this for each service.
public class CheckAutoSyncReceivingOrder extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
CheckAutoSyncReceivingOrder getService() {
return CheckAutoSyncReceivingOrder.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
From your activity that creates and calls the service, that when it is destroyed you want your service destroyed.
public class BindingActivity extends Activity {
CheckAutoSyncReceivingOr mService;
boolean mBound = false;
#Override
protected void onStart() {
super.onStart();
// Bind to CheckAutoSyncReceivingOr
Intent intent = new Intent(this, CheckAutoSyncReceivingOr.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to CheckAutoSyncReceivingOr, cast the IBinder and get CheckAutoSyncReceivingOr instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
And manage the service lifecycle. Restart the same service with your timer, do not create a new service.
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
#Override
public void onCreate() {
// The service is being created
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
#Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
#Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
#Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
#Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
Note START_NOT_STICKY will only prevent the service from restarting if the device is low on memory.
Be mindful that you where you are starting services, just start it once and allow the service to maintain it's own lifecycle until you destroy it with your activity.
This is in reply to your original unedited question, when the app was mysteriously crashing:
You need to destroy the dialog before the context window the dialog is attached to. That will cause a problem. So this is where program flow and the order of closing and cleaning up resources is important. They, frequently have to be destroyed in the reverse order they were created if they are dependent upon parent windows (which is often in the form of a particular activity).
It's difficult to trace your code, so this is a generic answer.
Make use of onPause and onDestroy in your activities.
In all your activities, manage any resources you have created within that activity and with a null check, close them down. Like you have in your service class. If you want to override the parent onDestroy, place your custom code before super.onDestroy.
protected void onDestroy() {
if(timer != null)
timer.cancel();
Log.d(TAG, "Stopping Sending...");
super.onDestroy();
}
(1)For Your Dialog:
The solution is to call dismiss() on the Dialog you created before exiting the Activity, e.g. in onDestroy(). All Windows & Dialog should be closed before leaving an Activity.
(2)For Your service autostart:
you have to look at the value the service returns from its onStartCommand method. The default value is START_STICKY which will restart the service after it is destroyed. Take a look at the onStartCommand documentation for more details:
If the process that runs your service gets killed, the Android system will restart it automatically it is default behavior.
This behavior is defined by the return value of onStartCommand() in your Service implementation. The constant START_NOT_STICKY tells Android not to restart the service if it s running while the process is "killed".
You need to Override method onStartCommand() in your service class and move all your code from onStart() method to onStartCommand() method.
According to the Android Documentation:
For started services, there are two additional major modes of
operation they can decide to run in, depending on the value they
return from onStartCommand(): START_STICKY is used for services that
are explicitly started and stopped as needed, while START_NOT_STICKY
or START_REDELIVER_INTENT are used for services that should only
remain running while processing any commands sent to them
onStart() method calls each time when service is restarted but onStartCommand() method will not called if you return START_NON_STICKY.
Don't use onStart() anymore, it's deprecated.
I hope it helps you.
Services got killed when application got killed (add logs in service onStartCommand() and onDestroy() function and try clearing app from recent list and you will see onDestroy() is called. Android will re-start service if you have returned START_STICKY intent in onStartCommand()).
There are two approaches to fix your problem.
Either make your two services as foreground service.
Instead of using CheckAutoSyncReceivingOrder and CheckAutoSyncSendingOrder to schedule start of another services, you should use AlarmManager to schedule your task.
I have IntentService that should use reference from another service via binding:
public class BaseIntentService extends IntentService implements ServiceConnection {
protected NetworkApi network;
public BaseIntentService() {
super("BaseIntentService");
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
network = ((NetworkApiBinder) service).getApi();
// never be invoked
}
#Override
public void onServiceDisconnected(ComponentName name) {
}
#Override
public void onCreate() {
super.onCreate();
bindService(new Intent(this, NetworkApi.impl), this, BIND_AUTO_CREATE);
}
#Override
public void onDestroy() {
super.onDestroy();
unbindService(this);
}
#Override
protected void onHandleIntent(Intent intent) {
// network always null!!!
}
}
But when I'm using binding like this onServiceConnected never be invoked. I know that IntentService not designed for the binding pattern, but may be there is a common solution for such tasks?
Thanks!
But when I'm using binding like this onServiceConnected never be invoked
That is because your IntentService is destroyed before the binding request even begins. An IntentService is automatically destroyed when onHandleIntent() completes all outstanding commands.
but may be there is a common solution for such tasks
Don't have two services. Get rid of the IntentService and move its logic into the other service.