67

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();
}
dcow
  • 7,765
  • 3
  • 45
  • 65

8 Answers8

36

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

Angel Koh
  • 12,479
  • 7
  • 64
  • 91
Jacob Phillips
  • 8,841
  • 3
  • 51
  • 66
  • 8
    does not make sense. you are setting mIsBound *before* it is actually bound? I believe bindService is on another thread. – likejudo Oct 18 '16 at 15:29
  • 9
    @likejiujitsu the documentation states for the bindService method: `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. However, you should still call unbindService(ServiceConnection) to release the connection.` In other words the connection is made as soon as the method returns, thus after calling this method you MUST unbind the service. – Rolf ツ Aug 17 '17 at 13:57
  • 1
    I can't see the "however" part on the documentation anymore. Maybe we should not be calling unbind if the bind has returned false – Klitos G. Nov 28 '19 at 16:02
  • @Rolfツ Maybe the documentation has changed then. It now states `true if the system is in the process of bringing up a service that your client has permission to bind to; false if the system couldn't find the service or if your client doesn't have permission to bind to it. If this value is true, you should later call unbindService(ServiceConnection) to release the connection.` - https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int) – now Oct 15 '20 at 11:14
33

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.

aaronvargas
  • 12,189
  • 3
  • 52
  • 52
  • 3
    I knew I'd seen it both ways but couldn't remember where the misinformation was coming from...thanks for making the connection. – dcow Feb 05 '18 at 10:01
  • So, in short, I should use the boolean bindService() returns instead of depending on onServiceConnected(), right? – Aloha Apr 16 '20 at 17:52
  • 1
    @PNDA Yes, that's what the documentation says now: https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int) – now Oct 15 '20 at 11:37
  • onServiceConnected is called but the service is not bounded? what the hell – wukong Nov 23 '20 at 13:21
  • I've been investigating the same `IllegalArgumentException` issue on my end, and finally found the solution after reading through these suggestions and conversations. In my case, I was using a different `Context` instance in the calls to bind and unbind the service, since I am working with Localization in my app, I was using a "Localized" version of my `Context` to bind, and then my "Main Application" context instance to unbind; which always resulted in the `IllegalArgumentException` to be thrown. – HelloImKevo Jul 06 '22 at 16:23
10

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".

8

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()

spaaarky21
  • 6,524
  • 7
  • 52
  • 65
pelotasplus
  • 9,852
  • 1
  • 35
  • 37
6

In my case, I called bindService using Activity Context and trying to unbind it using ApplicationContext. Don't do it.

wukong
  • 2,430
  • 2
  • 26
  • 33
5

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.

Rohit Singh
  • 16,950
  • 7
  • 90
  • 88
0

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.

Tom11
  • 2,419
  • 8
  • 30
  • 56
slott
  • 3,266
  • 1
  • 35
  • 30
  • 1
    But aren't these all called on the same thread? Perhaps I was mistaken in thinking that. – dcow Jul 14 '14 at 17:24
0
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()
}
NickUnuchek
  • 11,794
  • 12
  • 98
  • 138