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.
Related
In my application I am using an IntentService to download a file from a cloud. And showing the progress in NotificationManager. I need to show the status (Downloading/Completed or Failed) in the Activity which stared the IntentService too.
My problem is once I closed the app and open it back, I want to get the status of downloading from IntentService.
Which is the best way to do this?
You can let your Activity bind to your Service, by calling bindService() in your Activity. As per the documentation:
A service is "bound" when an application component binds to it by
calling bindService(). A bound service offers a client-server
interface that allows components to interact with the service, send
requests, get results, and even do so across processes with
interprocess communication (IPC). A bound service runs only as long as
another application component is bound to it. Multiple components can
bind to the service at once, but when all of them unbind, the service
is destroyed.
Also:
You should create a bound service when you want to interact with the
service from activities and other components in your application or to
expose some of your application's functionality to other applications,
through interprocess communication (IPC).
The documentation provides a fully functional example of this. Below is taken from the provided link.
Service class:
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. 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 {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
Activity class:
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** 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 LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
In your Service, you can define public methods that your Activity can call, such as polling for your download progress. Please refer to the documentation for explanation in detail.
There are couple of ways to have communication connection between Service and Activity. I would suggest these 2
First, you can use the great library Otto. With Otto, you can also have #Produce annotated method. With this method you will return the latest information about the download. When you #Subscribe in your Activity you will get the latest info immediately. https://github.com/square/otto
If you are using Android built-in DownloadManager it returns the updates and results with a Broadcast, you can register to that Broadcast both in your Service and Activity. This way you will be able to update both of them. I suggest you to use DownloadManager, it is awesome.
http://developer.android.com/reference/android/app/DownloadManager.html
I'm trying to bind a service, but onBind() always returns false.
This is the code for the ServiceConnection-
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with our service has been established,
// giving us the service object we can use to interact with our service.
mBoundService = ((ScheduleService.ServiceBinder) service).getService();
}
public void onServiceDisconnected(ComponentName className) {
mBoundService = null;
}
};
This is call to bindService() -
boolean test = getApplicationContext().bindService(new Intent(this, ScheduleService.class), mConnection, Context.BIND_AUTO_CREATE);
This is the declaration of the service in the Manifest -
<service android:name=".Notifications.ScheduleService" android:enabled="true"/>
I've read previous questions regarding the subject and couldn't find an answer(tried switching the Activity context with the Application context, but it didn't help).
I'm using using Frgaments and ActionBarSherlock, and my Activity extends SlidingFragmentActivity (That's why i'm using the application context, which doesn't help).
Edit - This is the code of the service i'm trying to start -
public class ScheduleService extends Service {
/**
* Class for clients to access
*/
public class ServiceBinder extends Binder {
public ScheduleService getService() {
return ScheduleService.this;
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("ScheduleService", "Received start id " + startId + ": " + intent);
// We want this service to continue running until it is explicitly stopped, so return sticky.
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// This is the object that receives interactions from clients. See
private final IBinder mBinder = new ServiceBinder();
/**
* Show an alarm for a certain date when the alarm is called it will pop up a notification
*/
public void setAlarm(Calendar c) {
// This starts a new thread to set the alarm
// You want to push off your tasks onto a new thread to free up the UI to carry on responding
new AlarmTask(this, c).run();
}
}
Any help will be appreciated . Thanks.
What is the fully qualified class-name of ScheduleService (i.e. including the full package-name)?
I'm asking this, because in your AndroidManifest.xml file, your service's name is .Notifications.ScheduleService, which seems a bit odd:
This tells me that either
The (last part of the) package-name contains a upper-case
character... not so good.
I would expect .notifications.ScheduleService instead, if this is the case.
The ScheduleService is defined within a file called Notifications.java.
I would expect .Notifications$ScheduleService instead, if this is the case (dollar sign instead of period).
Do you mean bindService() returns false? onBind() returns IBinder type.
Keep in mind that service binding takes some time. If you want to perform some action after binding is done you can perform it in the onServiceConnected() method.
public void onServiceConnected(ComponentName className, IBinder service) {
mBoundService = ((ScheduleService.ServiceBinder) service).getService();
Calendar c = new Calendar();
mBoundService.setAlarm(c);
}
If you need more guidance on this you need to show us your Activity code.
Why do you use the application context to bind the service?
The method bindService is called through the ContextWrapper. It might not be the issue but I'd share contexts across the place where you bind the service and where you have the connection.
In your case instead of
boolean test = getApplicationContext().bindService(new Intent(this, ScheduleService.class), mConnection, Context.BIND_AUTO_CREATE);
I'd do the following
boolean test = bindService(new Intent(this, ScheduleService.class), mConnection, Context.BIND_AUTO_CREATE);
Or if you want to keep a global context within the application, move everything to your Application file and call it similarly the same way suggested above.
The issue can also be on the package name of your app and the declaration of your service in your manifest. If you are unsure make sure to give the global route to your service in the manifest.
I'm implementing service binding into my application. However when i start my activity which binds to the service, the application force closes. Ive pin pointed that its due to the getApplicationContext() ... Heres my code and where it is called and used...
All help is appreciated.
Thanks
private LocalService mBoundService;
private boolean mIsBound;
Context context = getApplicationContext();
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. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((LocalService.LocalBinder)service).getService();
// Tell the user about this for our demo.
Context context = getApplicationContext();
Toast.makeText(context, "serviceconnected",
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
Toast.makeText(context, "serviceDisconnected",
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(context,
LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
#Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
in order to bind service with activity,instead of using getApplicationContext(), you should use getBaseContext() or this keyword
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
I'm building a Bluetooth application. I want to periodically scan for nearby Bluetooth devices. This program should start when the device starts and continue discovering devices based on a schedule (every 10 mins for example). I've been looking over the Android example of "BlueTooth Chat" in the API documentation, and I don't kow why it never uses the "Service" class. Should I use Service or Activity?
Furthermore, I understand that Services are supposed to be used for "long running tasks," but I also at some point want to provide some kind of GUI notification to the users via this class that discovers Bluetooth devices.
So, can someone please explain which one to use? What is the advantage?
You should use service if you want your scheduling running.Because android will eventually destroy your activity.
Definitely use a Service. In your MainActivity bind to the Service using bindService providing a ServiceConnection object. In this ServiceConnection object send a Message to the service with a reference to a local Messenger object (as part of a replyTo) that the Service can then use to later on send a Message back to your MainActivity. This will enable you to update your MainActivity GUI based on the results of your bluetooth scan.
In short, in your MainActivity, start and bind to your service with:
Intent i = new Intent(this, NetworkService.class);
startService(i);
bindService(i, networkServiceConnection, Context.BIND_AUTO_CREATE);
Define a messenger to respond to messages from the service like:
Messenger messenger = new Messenger(new IncomingHandler());
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case NetworkService.MSG_SOMETHING:
// do something here
break;
default:
super.handleMessage(msg);
}
}
}
And then write your service connection code like:
private ServiceConnection networkServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
networkService = new Messenger(service);
try {
Message msg = Message.obtain(null, NetworkService.MSG_REGISTER_CLIENT);
msg.replyTo = messenger;
networkService.send(msg);
log.debug("Connected to service");
} catch (RemoteException e) {
// Here, the service has crashed even before we were able to connect
}
}
Note that the replyTo is the messenger we just created.
In your NetworkService, keep track of connected clients with:
ArrayList<Messenger> clients = new ArrayList<Messenger>();
and create your handler like:
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
log.debug("Adding client: " + msg.replyTo);
clients.add(msg.replyTo);
break;
default:
super.handleMessage(msg);
break;
}
}
}
Then, when you want to send a message back to your MainActivity, just do something like the following:
for (int i = 0; i < clients.size(); i++) {
try {
clients.get(i).send(Message.obtain(null, MSG_SOMETHING));
} catch (RemoteException e) {
// If we get here, the client is dead, and we should remove it from the list
log.debug("Removing client: " + clients.get(i));
clients.remove(i);
}
}