I have some boubts about Android bound service. The guide: http://developer.android.com/guide/components/bound-services.html
,about bindService(), says:
The `bindService()` method returns immediately without a value
But this does not seems to be correct, since here the signature of the method is
public abstract boolean bindService (Intent service, ServiceConnection conn, int flags)
where the returned boolean value is described as below:
If you have successfully bound to the service, true is returned; false is returned if the connection is not made so you will not receive the service object.
So the question is: why the documentation says that the method returns immediately without a value? Moreover, here, the bind is done in this way:
void doBindService() {
bindService(new Intent(Binding.this,
LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
and I don't understand the sense of mIsBound = true, since the javadoc says that bindService() can also return false, if the bounding to service fail. So it should be:
void doBindService() {
mIsBound = bindService(new Intent(Binding.this,
LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
}
Am I wrong?
The documentation is incorrect. When returned boolean is false this means that no further attempts to establish connection are made. When true is returned this means that system tries to establish a connection and this can succeed or fail.
Look at answer for this question: "in what case does bindservice return false".
Basically bindservice returns false when it does not find a service to even attempt to bind to.
Ok, i finally completed learning all the nuances of binding services in android, and that is the ServiceBindHelper class, that can be treated as "ultimate truth" (excuse my immodesty).
https://gist.github.com/attacco/987c55556a2275f62a16
Usage example:
class MyActivity extends Activity {
private ServiceBindHelper<MyService> myServiceHelper;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myServiceHelper = new ServiceBindHelper<MyService>(this) {
#Override
protected Intent createBindIntent() {
return new Intent(MyActivity.this, MyService.class);
}
#Override
protected MyService onServiceConnected(ComponentName name, IBinder service) {
// assume, that MyService is just a simple local service
return (MyService) service;
}
};
myServiceHelper.bind();
}
protected void onDestroy() {
super.onDestroy();
myServiceHelper.unbind();
}
protected void onStart() {
super.onStart();
if (myServiceHelper.getService() != null) {
myServiceHelper.getService().doSmth();
}
}
}
Related
I have an service App that defined a bound service, and another client App that one of its activity binds to the bound service. How can I write test case to test the bind service process?
The code of the client App binding to the service is similar to what the Android official doc has:
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();
intent.setComponent(new ComponentName(SERVICE_APP_PACKAGE_NAME,
SERVICE_NAME));
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
unbindService(connection);
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 connection = 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;
}
};
}
What kind of test case can test the setIntent() & bindService() or unbindService() method in the activity's onStart() and onStop() method?
You don't want to test onBind. You know that works, that's tested as part of the Google framework. What you want to test is two things:
1)That your ServiceConnection functions properly set mBound and mService.
2)That your onStart calls onBind to bind it.
The best way to do this is actually a refactor. This code isn't as testable as it could be. Bring mService and mBound into the ServiceConnection class, and make it a full class (rather than an anonymous class). Then you can easily test (1) using mocks for the input. To test (2) I would actually subclass the Activity, override bindService to just set a variable to true, and ensure after calling onStart the variable was set to true.
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.
I have a setup that looks something like this:
class MyFragment implements SomeEventListener {
Application mAppContext;
boolean mBound;
boolean mDidCallUnbind;
MyIBinder mBinder;
ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBound = true;
mBinder = (MyIBinder) service;
mBinder.getThings();...
}
#Override
public void onServiceDisconnected(ComponentName name) {
mDidCallUnbind = false;
mBound = false;
mBinder = null;
}
};
...
#Override
public void onSomeEvent() {
mAppContext.bindService(...);
}
void unbindService() {
if (mBound && !mDidCallUnbind) {
mDidCallUnbind = true;
mAppContext.unbindService(mConnection);
}
}
#Override
public void onPause() {
unbindService();
super.onPause();
}
}
However, I am still seeing the error in the title from time to time: java.lang.IllegalArgumentException: Service not registered being generated when unbindService() is called. Am I missing something silly, or is there more going on? I should note that there may be more than one of this same fragment in existence.
Edit
Since no one actually seems to be reading the code, let me explain. unbindService() does not call Context.unbindService(ServiceConnection) unless the service is bound (mBound) and it had not previously been called before the onServiceDisconnected(...) callback was hit from a possible previous call to unbindService().
That in mind, are there any cases where Android will unbind your service for you such that the service would become unbound but onServiceDisconnected would not be called thus leaving me in a stale state?
Also, I am using my Application context to do the initial binding. Assume something like:
#Override
public void onCreate() {
mApplication = getContext().getApplicationContext();
}
Use mIsBound inside doBindService() and doUnbindService() instead of in the ServiceConnection instance.
ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = (MyIBinder) service;
}
#Override
public void onServiceDisconnected(ComponentName name) {
mBinder = null;
}
};
...
public void doBindService() {
mIsBound =bindService(new Intent(this, MyService.class),
mConnection, Context.BIND_AUTO_CREATE);
}
public void doUnbindService() {
if (mIsBound) {
unbindService(mConnection);
mIsBound = false;
}
}
This is how it's done in http://developer.android.com/reference/android/app/Service.html
I realize this question has already been answered. But I think there is reason to go into why people are making this mistake.
The issue is really with the training docs. http://developer.android.com/reference/android/app/Service.html shows a correct implementation while https://developer.android.com/guide/components/bound-services.html in the 'ActivityMessenger' shows a Very INCORRECT implementation.
In the 'ActivityMessenger' example onStop() could potentially be called before the service has actually been bound.
The reason for this confusion is they are using the bound service boolean to mean different things in different examples. (mainly, was bindService() called OR is the Service actually connected)
In the correct examples where unbind() is done based on the value of the bound boolean, the bound boolean indicates that the bindService() was called. Since it's queued up for main thread execution, then unbindService() needs to be called (so queued to be executed), regardless of when (if ever) onServiceConnected() happens.
In other examples, such as the one in http://developer.android.com/reference/android/app/Service.html. The bound indicates that the Services is Actually bound so that you can use it and not get a NullPointerException. Note that in this example, the unbindService() call is still made and the bound boolean doesn't determine whether to unbind or not.
Another possible reason for this exception might be that unbindService is called by the wrong Context. Because services can be bound not only by Activities, but also by other instances inherited by Context (with the exception of BroadcastReceivers), even by other Services, be sure that unbindService is called by the context that has bound the Service and not by the bound Service itself. This would yield directly the above exception "Service not registered".
java.lang.IllegalArgumentException: Service not registered means that you weren't bound to service when unbindService() was called.
So in your case, onSomeEvent() was never called before call to unbindService() in onPause()
Reason?
If in your Activity, unbindService() gets called before bindService() then you will get this IllegalArgumentException.
How to avoid it?
It's simple. You would not need a boolean flag if you bind and unbind service in this order.
Solution 1:
Bind in onStart() and unbind in onStop()
Your Activity {
#Override
public void onStart()
{
super.onStart();
bindService(intent, mConnection , Context.BIND_AUTO_CREATE);
}
#Override
public void onStop()
{
super.onStop();
unbindService(mConnection);
}
}
Solution 2:
Bind in onCreate() and unbind in onDestroy()
Your Activity {
#Override
public void onCreate(Bindle sis)
{
super.onCreate(sis);
....
bindService(intent, mConnection , Context.BIND_AUTO_CREATE);
}
#Override
public void onDestroy()
{
super.onDestroy();
unbindService(mConnection);
}
}
Relevant Link:
Android official documentation suggests that
If you need to interact with the service only while your activity is visible then go with Solution1.
If you want your activity to receive responses even while it is stopped in the background then go with Solution2.
In my case, I called bindService using Activity Context and trying to unbind it using ApplicationContext. Don't do it.
I have the exact same issue with my application. Every now and then I get IllegalArgumentException. I guess the special case is caused when the service is unbound and the onPause is called before onServiceDisconnected. So I would try Synchronized things to ensure correct execution.
class ServiceBindManager<T>(val context: Context, clazz: Class<T>) {
val TAG: String = "ServiceBindManager"
private val isBound: AtomicBoolean = AtomicBoolean(false)
private var intent: Intent = Intent(context, clazz)
private val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d(TAG, "onServiceConnected: $context")
isBound.set(true)
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.d(TAG, "onServiceDisconnected: $context")
isBound.set(false)
}
override fun onBindingDied(name: ComponentName?) {
isBound.set(false)
}
override fun onNullBinding(name: ComponentName?) {
isBound.set(false)
}
}
fun bindService() {
Log.e(TAG, "bindService: $context")
isBound.set(context.bindService(intent, connection, BIND_AUTO_CREATE))
}
fun unbindService() {
Log.e(TAG, "unbindService: $context")
if (isBound.get()) {
isBound.set(false)
context.unbindService(connection)
}
}
}
Usage:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
serviceBindManager = ServiceBindManager(this#MyActivity, MyService::class.java)
}
override fun onStart() {
super.onStart()
serviceBindManager.bindService()
}
override fun onStop() {
super.onStop()
serviceBindManager.unbindService()
}
i have a problem with binding a service to an activity in Android. The problem occurs in the activity:
public class ServiceTestActivity extends Activity {
private static final String TAG = "ServiceTestAct";
boolean isBound = false;
TestService mService;
public void onStopButtonClick(View v) {
if (isBound) {
mService.stopPlaying();
}
}
public void onPlayButtonClick(View v) throws IllegalArgumentException, IllegalStateException, IOException, InterruptedException {
if (isBound) {
Log.d(TAG, "onButtonClick");
mService.playPause();
} else {
Log.d(TAG, "unbound else");
Intent intent = new Intent(this, TestService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
isBound = true;
}
};
}
isBound tells if the service (called TestService) is already bound to the activity.
mService is the reference to the service.
Now if i call "onPlayButton(..)" the first time, with the service not beeing bound, bindService(..) is called and isBound switches from false to true. Then if i call "onPlayButton(..)" again, it calls "playPause()" on the service object. To here everything works fine.
But i want "playPause()" to be called right after the service has been bound, so i changed my code to this:
public void onPlayButtonClick(View v) throws IllegalArgumentException, IllegalStateException, IOException, InterruptedException {
if (isBound) {
Log.d(TAG, "onButtonClick");
mService.playPause();
} else {
Log.d(TAG, "unbound else");
Intent intent = new Intent(this, TestService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
mService.playPause();
}
}
From this point on I get a NullPointerException, because mService doesn't have a reference to the bound service, it's still null. I checked that by logging the value of mService at different positions in the code.
Any tips on what I am doing wrong here? I am pretty new to programming (especially binding) services in android, but I still don't see where the major differences between my to versions are.
One solution is to call playPause() in onServiceConnected(). Another solution is to call startService() with a custom intent that will tell the service to play. I think you might want to think about a redesign. I would try to design the service so that you can start and bind to the service when the activity starts and stop the service when the activity stops. If you need a service that will stay active past the lifetime of the activity, extend the Application class and you can start the service in the onCreate() method.
The binding of the service occurs asynchronously, i.e. the service may not be bound if bindService() returns but when onServiceConnected() has completed. Because of that mService is still null and the exception is thrown.
One solution would be to disable the button by default (in XML or onCreate()) and enable the button in onServiceConnected().
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