I have a service which plays music and an activity which provides the GUI for interacting with the service.The activity opens on list item click(I have a list of recordings) and it binds the service(and create it) at onCreate() method.
When onDestroy() is called, I unbind the service (this will destroy the service) - this should be OK since I do not want the service to run if the activity is exited, but the problem appear on orientation change because it re-creates the activity again and the service too(and the track is stopped and played again from the beginning when rotating the device).
I know about some flags (orientationChange) that might be useful, but is not a good practice for me since I want a different layout on landscape.
Also I could make the music player service to run as long as my app runs, but isn't a good idea since the user may not want to open the player, but want just to record, so the player service isn't necessarily here.
Here are some code snippets:
#Override
protected void onCreate(Bundle savedInstanceState) {
LocalBroadcastManager.getInstance(this).registerReceiver(mLocalReceiver, new IntentFilter(PlayerBroadcastReceiver.ACTION_PLAYER_SERVICE_STARTED));
setContentView(R.layout.media_player_screen);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
AudioPlayerServiceBridge.getInstance().addCallback(this);
AudioPlayerServiceBridge.getInstance().doBindService(this);
init(savedInstanceState);
super.onCreate(savedInstanceState);
}
#Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalReceiver);
mLocalReceiver.removeCallback();
Log.d(AudioPlayerActivity.class.getName(), "onDestroy() -> "+AudioPlayerActivity.class.getName());
AudioPlayerServiceBridge.getInstance().doUnbindService(this);
AudioPlayerServiceBridge.getInstance().removeCallback(this);
super.onDestroy();
}
and the service connection manager:
public void doBindService(Context context) {
// Establish a connection with the service. We use an explicit
// class name because there is no reason to be able to let other
// applications replace our component.
if(!mIsBound){
context.bindService(new Intent(context,
AudioPlayerService.class), serviceConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
}
public void doUnbindService(Context context) {
if (mIsBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mServiceMessenger != null) {
Message msg = Message.obtain(null, AudioPlayerService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mServiceMessenger.send(msg);
}
// Detach our existing connection.
context.unbindService(serviceConnection);
mIsBound = false;
}
}
Please show me if possible a good practice to handle this problem.
The answer is:
I should start the service with : startService(new Intent(this, service.class)) AND START BINDING after that. This method prevent the service to be killed when doUnbind() is called. So the onCreate() method is changed now in:
#Override
protected void onCreate(Bundle savedInstanceState) {
LocalBroadcastManager.getInstance(this).registerReceiver(mLocalReceiver, new IntentFilter(PlayerBroadcastReceiver.ACTION_PLAYER_SERVICE_STARTED));
setContentView(R.layout.media_player_screen);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if(savedInstanceState == null)
startService(new Intent(this, AudioPlayerService.class));
AudioPlayerServiceBridge.getInstance().addCallback(this);
AudioPlayerServiceBridge.getInstance().doBindService(this);
init(savedInstanceState);
super.onCreate(savedInstanceState);
}
onDestroy() method:
#Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalReceiver);
mLocalReceiver.removeCallback();
Log.d(AudioPlayerActivity.class.getName(), "onDestroy() -> "+AudioPlayerActivity.class.getName());
AudioPlayerServiceBridge.getInstance().doUnbindService(this);
AudioPlayerServiceBridge.getInstance().removeCallback(this);
super.onDestroy();
}
and stop the service(if you want) in onBackPressed():
#Override
public void onBackPressed() {
Log.d(AudioPlayerActivity.class.getName(), "onBackPressed() -> "+AudioPlayerActivity.class.getName());
isPaused = true;
Log.d(AudioPlayerActivity.class.getName(), "Sending message to player service: MSG_RELEASE_PLAYER");
AudioPlayerServiceBridge.getInstance().sendAsyncCall(AudioPlayerService.MSG_RELEASE_PLAYER);
if(mSeekBarChanger != null){
mSeekBarChanger.stopThread();
}
AudioPlayerServiceBridge.getInstance().doUnbindService(this);
stopService(new Intent(this, AudioPlayerService.class));
super.onBackPressed();
}
Related
I need to make status of user offline. When I press home button onStop() is called, that's fine. When I press back button onDestroy() is invoked. But when I close the app from recent apps by swiping it, onStop() or onDestroy() isn't called.
I need to know when the app is closed from recent apps to do something (e.g make user offline).
Make a service :
public class MyService extends Service {
private DefaultBinder mBinder;
private AlarmManager alarmManager ;
private PendingIntent alarmIntent;
private void setAlarmIntent(PendingIntent alarmIntent){
this.alarmIntent=alarmIntent;
}
public void onCreate() {
alarmManager (AlarmManager)getSystemService(Context.ALARM_SERVICE);
mBinder = new DefaultBinder(this);
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public void onTaskRemoved (Intent rootIntent){
alarmManager.cancel(alarmIntent);
this.stopSelf();
}
}
Make a custom class :
public class DefaultBinder extends Binder {
MyService s;
public DefaultBinder( MyService s) {
this.s = s;
}
public MyService getService() {
return s;
}
}
Add to your activity :
MyService service;
protected ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
service = ((DefaultBinder) binder).getService();
service.setAlarmIntent(pIntent);
}
public void onServiceDisconnected(ComponentName className) {
service = null;
}
};
protected void onResume() {
super.onResume();
bindService(new Intent(this, MainService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
if (mConnection != null) {
try {
unbindService(mConnection);
} catch (Exception e) {}
}
}
But when I close the app from recent apps by swiping it, onStop() or onDestroy() isn't called.
Methods of Activity lifecycle that are to be called when Activity is no longer visible are not guaranteed to be called when removed from the recent tasks (treat it as "soft" version of killing an app by the system due to low memory).
I need to know when the app is closed from recent apps to do something (e.g make user offline)
I suggest one of the following:
(If applicable) use Activity's onResume()/onPause() to "make user online/offline";
use Service that sticks to the application meaning that if the app is killed after Service's onStartCommand() returns, the service will be recreated and onStartCommand() will be called again. At this point you could "make user offline". The chain of lifecycle method calls would be:
Activity's onStop() -> onDestroy()* ->
Service's onTaskRemoved()* ->
Application's onCreate() -> Service's onCreate() ->
Service's onStartCommand()
The Intent passed to the method will help you recognize which component triggered the start request:
Intent != null, meaning the request has been received from a running Activity instance
Intent = null, meaning the request has been sent by the (newly created) Application instance
* Not guaranteed to be called
No, there is no clean way to get the application termination time. But I could suggest you a dirty trick to use a service to update your application (offline functionalities) for every after n minutes.
When OS kill your application, it will remove all associated services and broadcast receiver along with it.
I'm using bound service so that I am able to communicate between an activity and a service.
I'm binding to a service in onStart:
#Override
protected void onStart() {
super.onStart();
Intent bindIntent = new Intent(this, MusicService.class);
bindService(bindIntent, this, BIND_AUTO_CREATE);
}
waiting for service to bind:
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMusicService = ((MusicService.LocalBinder) service).getService();
mMusicService.setCallback(this);
}
handling disconnect from service:
#Override
public void onServiceDisconnected(ComponentName name) {
mMusicService = null;
}
unbinding from service in onDestroy:
#Override
protected void onDestroy() {
super.onDestroy();
if (mMusicService != null && isFinishing()) {
mMusicService.setCallback(null);
unbindService(this);
}
}
My problem is that when app is minimized, onDestroy gets called immediately and then onUnbind in Service gets called and music is stopped.
Here is onUnbind method (where mPlayer is MediaPlayer):
#Override
public boolean onUnbind(Intent intent) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
return super.onUnbind(intent);
}
If I don't implement onUnbind music continues to play (sometimes and sometimes it stops after some time) and when I open the app again (from minimized applications) I am able to play another song and then those two song play at same time.
I've red couple articles about music players and services on android and I thought that this was correct approach (thinking that onDestroy will be called when OS is out of memory).
Any ideas how I can re-implement my app workflow so that I will work as expected?
EDIT
At first I thought that "Don't keep activities" under developer options is a problem, but problem is still there even if I uncheck it.
And if some code from my service is needed please say I will edit my question (there's a lot of code and I'm not sure which part is important for this issue)
SOLUTION:
startForeground(<notification id>, <notification>);
to run service even if app gets killed. And when user dismisses the notification:
stopForeground(true);
stopSelf();
More about startForeground here.
It's not obvious, but you should start a Thread that runs in background and use the service to control it's state.
A service isn't a thread that hold some run state like a thread. Unless it's an IntentService. (Correct me if I'm wrong)
While activity can (and probably will) get destroyed, your app will keep running.
Activity:
#Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, BackgroundService.class);
startService(intent);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
finishOnPause = true;
}
#Override
protected void onStop() {
super.onStop();
unbindService(mServiceConnection);
}
Service:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
ensureServiceThread();
if (intent != null) {
}
return START_NOT_STICKY;
}
private void ensureServiceThread() {
if (service_thread == null) {
service_thread = new ServiceThread(this);
service_thread.start();
}
}
#Override
public void onDestroy() {
stopServiceThread();
super.onDestroy();
}
private void stopServiceThread() {
if (service_thread != null) {
service_thread.interrupt();
service_thread = null;
}
}
And you should do your work inside the Thread.
If you need context, it's your Service.
If you need to do something on Main thread - create a Handler in Service.OnCreate and do a handler.postRunnable inside a worker thread safely.
What I would do is:
- Create a service
- Create a thread
- Create a media player inside a thread (if possible, otherwise on Service creation and pass it to thread)
- Inside a thread - continuously poll media player state
- On song finished send an intent to service that change track
- stop thread/service if needed.
On Android, I have an Activity called FirstActivity which starts a Service named MyService to do networking stuff in the background. The Activity and the Service communicate with each other all the time by calling methods.
Now when the user navigates from FirstActivity to SecondActivity, the background service should not be killed or re-created, but kept alive and passed to SecondActivity which will now be the one communicating with the service.
In other words, the Service shall be running as long as one of the two Activitys is running, and it should not stop while the user navigates between the two Activitys.
One of the Activitys will always be in the foreground and during this time, the service should (optimally) never get killed. I think this should not be a problem because one of those two Activitys is always active and thus Android knows the service is important and not something that must be killed.
(If there was no way to prevent Android from killing and re-creating the service from time to time, I would need a way to restore the full state of the service gracefully.)
To sum up, the Service should have the same lifespan as the two Activitys "combined". It should start with the first of them and stop not before both of them have been destroyed.
So is the following code correct for that setup and goals?
public class MyService extends Service {
public class LocalBinder extends Binder {
public MyService getService() {
return MyService.this;
}
}
...
}
public class FirstActivity extends Activity {
private MyService mMyService;
private ServiceConnection mMainServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
MyService mainService = ((LocalBinder) service).getService();
mMyService = mainService;
mMyService.setCallback(FirstActivity.this);
}
#Override
public void onServiceDisconnected(ComponentName className) {
mMyService = null;
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
...
startService(new Intent(FirstActivity.this, MyService.class));
}
#Override
protected void onResume() {
super.onResume();
bindService(new Intent(FirstActivity.this, MyService.class), mMainServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onPause() {
super.onPause();
if (mMainServiceConnection != null) {
unbindService(mMainServiceConnection);
}
if (mMyService != null) {
mMyService.setCallback(null);
}
if (!isUserMovingToSecondActivity) {
stopService(new Intent(FirstActivity.this, MyService.class));
}
}
#Override
public void onBackPressed() {
stopService(new Intent(FirstActivity.this, MyService.class));
super.onBackPressed();
}
...
}
public class SecondActivity extends Activity {
private MyService mMyService;
private ServiceConnection mMainServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
MyService mainService = ((LocalBinder) service).getService();
mMyService = mainService;
mMyService.setCallback(SecondActivity.this);
}
#Override
public void onServiceDisconnected(ComponentName className) {
mMyService = null;
}
};
#Override
protected void onResume() {
super.onResume();
bindService(new Intent(SecondActivity.this, MyService.class), mMainServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onPause() {
super.onPause();
if (mMainServiceConnection != null) {
unbindService(mMainServiceConnection);
}
}
#Override
protected void onDestroy() {
...
stopService(new Intent(SecondActivity.this, MyService.class));
}
...
}
Is this the best way to guarantee a long-lasting service in the background of the Activitys that will not be killed or re-created?
What about Context.BIND_AUTO_CREATE? Is it correct to have this flag set here? What about Context.BIND_ADJUST_WITH_ACTIVITY and Context.BIND_WAIVE_PRIORITY -- do I need these?
(Many thanks to #corsair992 for his useful pointers!)
If the activities are always called in that order (i.e. FirstActivity starts SecondActivity, and never the other way around, then you should, basically, attempt to "tie" the Service's life-cycle to FirstActivity's lifecycle.
In general (see caveats later), this means:
Call startService() in FirstActivity.onCreate().
Call stopService() in FirstActivity.onDestroy().
Call bindService()/unbindService() in the onStart()/onStop() methods of both Activities (to get access to the Binder object, and be able to call methods on it).
A service started this way will be alive until stopService() is called and every client unbinds, see Managing the Lifecycle of a Service:
These two paths are not entirely separate. That is, you can bind to a
service that was already started with startService(). (...) In cases like this, stopService() or
stopSelf() does not actually stop the service until all clients
unbind.
and:
When the last client unbinds from the service, the system destroys the
service (unless the service was also started by startService()).
With this basic strategy, the Service will live as long as FirstActivity is around (i.e. it is not destroyed). However, an important point still remains: in the event of a configuration change (e.g. a screen rotation) that is not handled explicitly will cause the activity to restart itself, and the service will be destroyed (since we're calling stopService() in onDestroy()).
To prevent this, you can check isChangingConfigurations() before actually stopping the service (since an onDestroy() callback occurring due to this reason means that although this particular instance of the Activity is being destroyed, it will be recreated afterwards.
Hence, the full solution would be something like:
public class FirstActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startService(new Intent(this, MyService.class));
}
private ServiceConnection mServiceConnection = new ServiceConnection() { ... }
#Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, MyService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
unbindService(mServiceConnection);
super.onStop();
}
#Override
protected void onDestroy() {
if (!isChangingConfigurations())
stopService(new Intent(this, MyService.class));
super.onDestroy();
}
While SecondActivity would only implement the onStart()/onStop() methods (in the same way).
A couple of notes about your particular implementation:
It's not necessary to override onBackPressed(), since if the activity is destroyed the necessary lifecycle methods will be called (plus, it could be finished without pressing the back button, for example if calling finish() on it).
Stopping the service in onDestroy() instead of onPause() saves you from having to check for isUserMovingToSecondActivity.
I have an Android application where I implement a Service which interacts with some hardware over a Bluetooth serial connection. The setup of this connection is slow, so I decided to keep the service in the foreground, so if/when you want to view another application, the connection is ready to go (pseudocode follows):
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
start();
return (START_STICKY);
}
#Override
public void onDestroy() {
stop();
}
start() and stop() are private methods which start communication with the hardware, and in start's case, creates a Notification for use in startForeground() My Activity will call
#Override
public void onStart() {
super.onStart();
// Start the service
Intent intent = new Intent(getApplicationContext(), MyService.class);
ComponentName theService = startService(intent);
//this is to register the functions I need to handle functions my Activity calls
// to the service
bindService(intent, svcConn, BIND_AUTO_CREATE);
}
#Override
public void onStop() {
super.onStop();
if (theService != null) {
unbindService(svcConn);
theService = null;
if (isFinishing()) {
stopService(new Intent(getApplicationContext(), MyService.class));
}
}
}
I've had to add a "Quit" menu item to make sure that the Service shuts down. Worse, if my app crashes, I have to go in and manually kill the Service. Is there a way to elegantly kill the Service if things go horribly wrong, or am I abusing the purpose of a Service, and should find an alternative method of doing what I'd like to do?
Perhaps you can add a hook for your application's main thread(UI thread) for crash, see below:
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
//Kill the service.
}
});
throw new RuntimeException("Uncaught Exception");
I am currently using 2 services in my app:
1: LocationService, basically trying to localize the user, and aims to stay alive only when the app is on foreground.
2: XmppService, which init the connection with the xmpp server, receive messages, send it, logout ... and aims to stay alive until the user logout.
I've been reading quite a lot of documentation, but I just can't make it clear.
I'm having Leaks when I try to store reference of LocationServiceBinder, which is used to call my service functions (using AIDL interfaces). Same for Xmpp. When I unbind, I get sometimes ANR (which look like to be linked with the fact that my bind/unbind are weirdly done, onResume, onRestart ...).
All the system is working, but I'm sure it is not the right way to do it, and please I would love to follow experienced people to come back in the right side of the force ! :)
Cheers
UPDATE
My Location Service is bind at the app launch to get as fast as possible the user's position :
if(callConnectService == null) {
callConnectService = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder binder) {
locationServiceBinder = LocationServiceBinder.Stub.asInterface(binder);
try {
global.setLocationBinder(locationServiceBinder);
global.getLocationBinder().startLocationListener();
} catch (Exception e){
Log.e(TAG, "Service binder ERROR");
}
}
public void onServiceDisconnected(ComponentName name) {
locationServiceBinder = null;
}
};
}
/* Launch Service */
aimConServ = new Intent(this, LocationService.class);
boolean bound = bindService(aimConServ,callConnectService,BIND_AUTO_CREATE);
My Xmpp Service is launched when the user log in :
callConnectService = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder binder) {
try {
Log.d(TAG, "[XMPP_INIT] Complete.");
global.setServiceBinder(ConnectionServiceBinder.Stub.asInterface(binder));
//Connect to XMPP chat
global.getServiceBinder().connect();
} catch (Exception e){
Log.e(TAG, "Service binder ERROR ");
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "Service binder disconnection ");
}
};
/* Launch Service */
Intent aimConServ = new Intent(MMWelcomeProfile.this, XmppService.class);
bound = bindService(aimConServ,callConnectService,Context.BIND_AUTO_CREATE);
and unbind on each Activity :
if (callConnectService != null){
unbindService(callConnectService);
callConnectService = null;
}
It hasn't been well-documented in Google's official dev guide, Context.bindService() is actually an asynchronous call. This is the reason why ServiceConnection.onServiceConnected() is used as a callback method, means not happened immediately.
public class MyActivity extends Activity {
private MyServiceBinder myServiceBinder;
protected ServiceConnection myServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
myServiceBinder = (MyServiceBinderImpl) service;
}
... ...
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// bindService() is an asynchronous call. myServiceBinder is resoloved in onServiceConnected()
bindService(new Intent(this, MyService.class),myServiceConnection, Context.BIND_AUTO_CREATE);
// You will get a null point reference here, if you try to use MyServiceBinder immediately.
MyServiceBinder.doSomething(); // <-- not yet resolved so Null point reference here
}
}
A workaround is call MyServiceBinder.doSomething() in myServiceConnection.onServiceConnected(), or perform MyServiceBinder.doSomething() by some user interaction (e.g. button click), as the lag after you call bindService() and before system get a reference of myServiceBinder is quite soon. as long as you are not using it immediately, you should be just fine.
Check out this SO question CommonsWare's answer for more details.
this thread is quite old, but I just discovered it.
Actually there is only one way for your service to go on living if it is bound : it has to be also started. The documentation is not quite clear about that but a service can be both started and bound.
In that case, the service will not get destroyed when you unbind from it, it will get destroyed when :
you stop it and there is no one bound to it
you unbind from it and it has been stopped before.
I made a small Service Lifecycle demo app on GitHub and it's also available on Google Play.
Hope that helps ;)
if you bind to a service in an Activity, you need to unbind it too:
#Override
protected void onResume() {
Log.d("activity", "onResume");
if (locationServiceBinder == null) {
doBindLocationService();
}
super.onResume();
}
#Override
protected void onPause() {
Log.d("activity", "onPause");
if (locationServiceBinder != null) {
unbindService(callConnectService);
locationServiceBinder = null;
}
super.onPause();
}
where doBindLocationService():
public void doBindLocationService() {
Log.d("doBindService","called");
aimConServ = new Intent(this, LocationService.class);
// Create a new Messenger for the communication back
// From the Service to the Activity
bindService(aimConServ, callConnectService, Context.BIND_AUTO_CREATE);
}
You need to do this practise for your XmppService as well