Android:如何安全地解除绑定服务

48

我有一个服务,它像这样绑定到应用程序上下文:

getApplicationContext().bindService(
                    new Intent(this, ServiceUI.class),
                    serviceConnection,
                    Context.BIND_AUTO_CREATE
            );

protected void onDestroy() {
            super.onDestroy();                  
            getApplicationContext().unbindService(serviceConnection);
        }

由于某些原因,应用程序上下文有时无法正确绑定(我无法修复这部分),但是在onDestroy()中执行unbindservice会引发错误。

java.lang.IllegalArgumentException: Service not registered: tools.cdevice.Devices$mainServiceConnection.

我的问题是:有没有一种安全的方法可以调用 unbindservice 或者在解除绑定之前检查它是否已经绑定到服务?

提前致谢。


请参考以下链接,了解如何在Android中将服务绑定到活动:https://dev59.com/bnI-5IYBdhLWcg3wTWn4 - Sunil Kumar Sahoo
我之前确实看过这个,但那并不能解决我的问题。假设我调用了UnBindService而没有调用BindService(),你如何处理错误而不让应用程序崩溃? - 2ndlife
你不能只捕获异常然后继续执行吗? - dragonroot
1
@Yury的回答是正确的答案。 - VansFannel
这是因为您正在使用getApplicationContext()绑定服务,然后使用当前活动上下文取消绑定。这就像把硬币给X,然后从Y那里要求它。这将抛出IllegarlArgumentException :-) 我希望这是问题的答案,使用相同的上下文进行绑定和解绑定 :-) - AZ_
8个回答

71

试试这个:

boolean isBound = false;
...
isBound = getApplicationContext().bindService( new Intent(getApplicationContext(), ServiceUI.class), serviceConnection, Context.BIND_AUTO_CREATE );
...
if (isBound)
    getApplicationContext().unbindService(serviceConnection);

注意:

绑定服务和解绑服务应该使用相同的context。如果你使用getApplicationContext()去绑定服务,那么你也应该使用getApplicationContext.unbindService(..)去解绑服务。


1
我按照你的回答做了,但仍然出现相同的错误。@Yury的回答解决了这个问题。 - VansFannel
在我看来,这个解决方案似乎会泄漏绑定的服务,因为我刚刚发现必须在同一上下文中调用取消绑定。因此,在不同的上下文中进行测试可能会产生奇怪的结果,实际上已经将特定服务绑定了两次(如果可能的话?) - 3c71
我明白为什么要阅读这个:https://dev59.com/Imgv5IYBdhLWcg3wYv2i - fersarr
3
我的问题似乎源于使用活动上下文而不是应用程序上下文。为了防止混淆正在使用的上下文,请确保在所有绑定/解绑操作中使用应用程序上下文。 - ian.shaun.thomas
返回值可能不可信,请参阅https://dev59.com/ToPba4cB1Zd3GeqPyN1a - Kevin Lee
显示剩余5条评论

8

这里有一个很好的解释和源代码,介绍如何使用绑定服务。在您的情况下,您应该覆盖ServiceConnection对象的方法(onServiceConnectedonServiceDisconnected)。然后,您可以在代码中检查mBound变量。


很抱歉,所提供的示例是针对本地服务而非远程进程服务的。 - Surya Wijaya Madjid
5
与本地绑定不同,绑定远程服务会在目标服务的进程死亡时给我们“onServiceDisconnected()”回调,而当其重新启动时,“onServiceConnected()”将再次被传递。如果您依赖这些回调来检查“mBound”标志,如果在“onServiceDisconnected()”和“onServiceConnected()”之间尝试解除绑定,此时“mBound”将为“false”,那么您将会出现服务连接泄漏问题。 - Surya Wijaya Madjid
为了解决这个问题,在onServiceDisconnected中不要将mBound设置为false。如果需要跟踪连接和断开状态,可以引入另一个变量mConnected。 - Kevin Lee
2
404。应该鼓励回答发布链接而不是摘录。 - mr5

5
尝试按照 Andrey Novikov 建议的方法并没有奏效。我只是替换了:

getApplicationContext().unbindService(serviceConnection);

随着:

unbindService(serviceConnection);

3
serviceConnection 是什么?serviceConnection(服务连接)是一种 Android 平台上的接口,用于在应用程序组件和后台服务之间建立通信通道。它允许应用程序组件与运行在单独进程中的服务进行交互和通信,从而支持应用程序的多进程架构。 - IgorGanapolsky

5

我发现有两个问题。一是尝试绑定多次,二是尝试解除绑定多次。

解决方案:

public class ServiceConnectionManager implements ServiceConnection {

    private final Context context;
    private final Class<? extends Service> service;
    private boolean attemptingToBind = false;
    private boolean bound = false;

    public ServiceConnectionManager(Context context, Class<? extends Service> service) {
        this.context = context;
        this.service = service;
    }

    public void bindToService() {
        if (!attemptingToBind) {
            attemptingToBind = true;
            context.bindService(new Intent(context, service), this, Context.BIND_AUTO_CREATE);
        }
    }

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        attemptingToBind = false;
        bound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        bound = false;
    }

    public void unbindFromService() {
        attemptingToBind = false;
        if (bound) {
            context.unbindService(this);
            bound = false;
        }
    }

}

2
这个可以运行,但是你必须手动维护“bound”,这似乎非常笨拙 - 它应该是一个可访问的内部变量。 - David Doria
@DavidDoria 这可能看起来很别扭,但这实际上是Android展示如何做到这一点的方法:http://developer.android.com/guide/components/bound-services.html - ForceMagic
这个答案对我有用,因为我意识到我愚蠢地调用了 myService.unbindService(myServiceConnectionManager),而不是调用 context.unbindService(...)。 - Chris Sprague
也许还需要处理另一个回调:public void onBindingDied(ComponentName name)。 - Adam

3

为什么会出现这个错误?

当您尝试注销未注册的服务时。

有哪些常见例子?

  • 使用不同上下文进行绑定和解绑服务。
  • 调用 unBind(mserviceConnection) 次数超过 bind(...)

第一个要点是不言自明的。让我们更深入地探讨第二个错误源。调试绑定(bind)和解绑(unbind)调用。如果您看到这些调用顺序,则应用程序最终会得到 IllegalArgumentException

enter image description here

如何避免这种情况?
有两种方式需要考虑在Activity中绑定和解绑服务。Android文档建议:

  • 如果您只想在Activity可见时与服务交互,则

onStart() 中使用 bindService(),在 onStop() 中使用 unbindService()

Your Activity {

    @Override
    public void onStart(){
       super.onStart();
       bindService(intent, mConnection , Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onStop(){
       super.onStop();
       unbindService(mConnection);
    }

} 
  • 如果您想与一个服务进行交互,即使Activity在后台运行,则:

onCreate() 中调用 bindService() ,在 onDestroy() 中调用 unbindService()。

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);
    }         

}

0

0

我认为指南并不完全正确,就像Surya Wijaya Madjid在这里提到的一样。当绑定服务被销毁且尚未重新连接时,可能会发生内存泄漏。

我认为需要采取以下方法:

Service mService;

private final ServiceConnection mServiceConnection = new ServiceConnection()
{
    boolean bound = false;

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        mService = null;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        mService = ((MyService.ServiceBinder) service).getService();

        if (!bound)
        {
            // do some action - service is bound for the first time
            bound = true;
        }
    }
};

@Override
public void onDestroy()
{
    if (mService != null)
    {
        // do some finalization with mService
    }

    if (mServiceConnection.bound)
    {
        mServiceConnection.bound = false;
        unbindService(mServiceConnection);
    }
    super.onDestroy();
}

public void someMethod()
{
    if (mService != null)
    {
        // to test whether Service is available, I have to test mService, not     mServiceConnection.bound
    }
}

-1

对于以上所有答案都不确定,它们似乎过于复杂,而且没有一个适合我遇到的问题。

每次只绑定/解绑一次,并且在unbind()调用时服务肯定已经绑定。不想泄漏任何东西,所以我只是确保我在bind()和unbind()调用中使用了相同的上下文,这永久地解决了它!像这样做:

any_context.getApplicationContext().bind(...);

...

another_context.getApplicationContext().unbind(...);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接