Good practices for services on Android - android

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

Related

Which function is called when application is removed from task manager

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.

Memory leak - Service + thread

I would like to know if I am doing the right thing. I am clearly getting memory leaks, but I can not pin down where - I have submitted a simplified version of where I think the problem lies . . . is there a potential for leak in the following code?
public class MainActivity extends Activity {
filterService mServer;
private void startService() {
Intent mIntent = new Intent(getApplicationContext(), filterService.class);
startService(mIntent);
bindService(mIntent, mConnection, BIND_AUTO_CREATE);
}
private void stopService() {
stopService(new Intent(getApplicationContext(), filterService.class));
unbindService(mConnection);
mConnection = null;
}
ServiceConnection mConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName name) {
mServer = null;
}
public void onServiceConnected(ComponentName name, IBinder service) {
LocalBinder mLocalBinder = (LocalBinder)service;
mServer = mLocalBinder.getServerInstance();
}
};
public void onDestroy() {
super.onDestroy();
stopService();
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService();
}
}
Any comments would be most valuable - thank you.
Better unbind from your service in 'onStop()', because 'onDestroy()' may not be called.
If you use 'startService()' to make your service do whatever it is supposed to do and return with 'START_STICKY' from the 'onStartCommand()' method of the service, then it will not be destroyed.
See the documentation about bound services (The Basics):
When the last client unbinds from the service, the system destroys the service (unless the service was also started by startService()).
This way, you can keep your service alive even though the activity is stopped/ destroyed. As soon as it is finished, it can call 'stopSelf()'.
Another source for memory leaks could be a Handler used for communication with the bound service, but I can't judge that from your code.

Android Service with MediaPlayer gets recreated or destroyed

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.

Android service not launched by JUnit test

I have this test class to test a remote service:
public class CoreServiceBasicTest extends ServiceTestCase<CoreService> implements ServiceConnection {
/** Tag for logging */
private final static String TAG = CoreServiceBasicTest.class.getName();
/** Receive incoming messages */
private final Messenger inMessenger = new Messenger(new IncomingHandler());
/** Communicate with the service */
private Messenger outMessenger = null;
/** Handler of incoming messages from service */
private static class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
Log.d(TAG, "Incoming message");
}
}
/** Constructor for service test */
public CoreServiceBasicTest() {
super(CoreService.class);
}
/** Start the service */
#Override
public void setUp() {
// Mandatory
try {
super.setUp();
} catch (Exception e) {
e.printStackTrace();
}
// Start the service
Intent service = new Intent();
service.setClass(this.getContext(), CoreService.class);
startService(service);
Log.d(TAG, "Service started");
}
public void onServiceConnected(ComponentName className, IBinder service) {
outMessenger = new Messenger(service);
Log.d(TAG, "Service attached");
}
public void onServiceDisconnected(ComponentName className) {
// TODO Auto-generated method stub
}
#SmallTest
public void testBindService() {
// Bind to the service
Intent service = new Intent();
service.setClass(getContext(), CoreService.class);
boolean isBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE);
assertTrue(isBound);
}
}
The problem is that startService(service) in the setUp() method does not launch the service correctly. This is what the AVD shows:
As you can see, the process is launched but the service is not. Then on testBindService(), assertTrue(isBound) fails.
This doesn't happen if I launch the service from an Activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Start the Core service
Intent service = new Intent();
service.setClass(this, CoreService.class);
if (startService(service) == null) {
Toast.makeText(this, "Error starting service!", Toast.LENGTH_LONG).show();
Log.e(TAG, "Error starting service");
} else {
Toast.makeText(this, "Service started sucessfully", Toast.LENGTH_LONG).show();
}
// Die
finish();
}
Here the service is started correctly, as shown below.
How can I start and bind to a remote service that uses Messenger to communicate with activities from an Android Test Project?
The whole point of Android Test Project (test.apk) is to instrument the Application Project (app.apk) and unit-test the Android components (Activity, Service and etc) which are associated with Application Project, in another word, unit-testing Activity and Service that is created and manipulated inside app.apk.
You should not write your MessengerService implementation partially (Messenger, IncomingHandler and etc) second time inside ServiceTestCase implementation under Test project. MessengerService implementation only need to be written once in your Application project's CoreService.java file.
ServiceConnection is used for inter-communication between Activity and Service, as we use ServiceTestCase here (means unit-test service, communication with other components is out-of-scope hence not considered), we don't need a ServiceConnection implementation. The only thing ServiceConnection does is initialize a solid Messenger object so that we could use later, once service is properly created:
public void onServiceConnected(ComponentName className, IBinder service) {
// This is what we want, we will call this manually in our TestCase, after calling
// ServiceTestCase.bindService() and return the IBinder, check out code sample below.
mService = new Messenger(service);
}
Also note that you don't need to call ServiceTestCase.startService() in this case, as ServiceTestCase.bindService() will properly start the service (if it is not started yet) and return a IBinder object we need to use to initialize Messenger object later.
Say if your IncomingHandler.handleMessage() impelementation in CoreService.java look like this:
... ...
switch (msg.what) {
case MSG_SAY_HELLO:
msgReceived = true;
break;
... ...
To test send message functions in ServiceTestCase:
... ...
IBinder messengerBinder = null;
#Override
public void setUp() throws Exception {
super.setUp();
// Bind the service and get a IBinder:
messengerBinder = bindService(new Intent(this.getContext(), CoreService.class));
//log service starting
Log.d(TAG, "Service started and bound");
}
public void testSendMessage() {
// Use IBinder create your Messenger, exactly what we did in ServiceConnection callback method:
Messenger messenger = new Messenger(messengerBinder);
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
messenger.send(msg);
// Do some assert stuff here
... ...
}
... ...
If your want to test communication between Activity and Service, then ServiceTestCase is not suitable in this case. Consider using ActivityInstrumentationTestCase2 test the actual Activity (which bound to your CoreService, which gives you ability to indirectly test your Service functions.
Just looking at the documentation for ServiceTestCase it says that the test framework delays starting the service until one of your test methods calls ServiceTestCase.startService() or ServiceTestCase.bindService().
Looking at your code you call ServiceTestCase.startService() in your setUp() method, not in a test method. This doesn't start the service yet. It is waiting for one of your test methods to call ServiceTestCase.startService() or ServiceTestCase.bindService().
Your test method testBindService() isn't calling ServiceTestCase.bindService(), instead it is calling Context.bindService() and failing. The test framework is still waiting, so that's why the service isn't started.
Have another look at the Lifecycle support discussion in the linked developer docs.

Communication with android Service

I'm developing an android application which creates an android service which refresh the location of the mobile device using gps. It works with a timer that in 'x' time, it refresh the position.
The problem is that I would like to comunicate with that local android service from one activity of the application, because I would like to change the refresh time ('x') when I want. So, How do I can do it?
One possible solution is to stop the service and then start again every time the time refresh is changed, but I think it is not the optimal solution.
Any suggerence, help, pls?
If you are within the same Process-Space (Same Application/.apk) you can just establish a simple Service Connection
In your activity, include something like the following:
private ServiceConnection _svcConnection = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName arg0) {
_myService = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
_myService = ((MyService.LocalBinder) service).getService();
if(!_myService.IsRunning())
_myService.Start();
}
};
#Override
protected void onResume() {
bindService(new Intent(Main.this, MyService.class), _svcConnection
BIND_AUTO_CREATE);
startService(new Intent(Main.this, MyService.class));
super.onResume();
}
#Override
protected void onPause() {
unbindService(_svcConnection);
super.onPause();
}
Your Service needs a binder:
private final IBinder _Binder = new LocalBinder();
#Override
public IBinder onBind(Intent arg0) {
return _Binder;
}
/**
* Class for clients to access. Because we know this service always runs in
* the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public MyService getService() {
return MyService.this;
}
}
Then you can work with the _MyService object and call any Method on it. (E. g. Registering a Callback or request Location Updates), but be aware that if the service connection fails, the _MyService variable will be null!
_MyService.SetInterval(4);
If you need to access this service from another Application (Another Process), you have to deal with IPC.
It will help you, You do this in service, it will refresh your location in this method
Timer timer = new Timer("Refresh Time");
timer.schedule(RefreshTask, 1000L, 60 * 1000L);
private TimerTask refreshTask = new TimerTask() {
#Override
public void run() {
Log.i(TAG, "Update time here");
}
};

Categories

Resources