48

我有一个调用 IDownloaderService.aidl 中定义的服务的活动:

public class Downloader extends Activity {
 IDownloaderService downloader = null;
// ...

在 Downloader.onCreate(Bundle) 我试图 bindService

Intent serviceIntent = new Intent(this, DownloaderService.class);
if (bindService(serviceIntent, sc, BIND_AUTO_CREATE)) {
  // ...

在 ServiceConnection 对象 sc 中我这样做了

public void onServiceConnected(ComponentName name, IBinder service) {
  Log.w("XXX", "onServiceConnected");
  downloader = IDownloaderService.Stub.asInterface(service);
  // ...

通过添加各种 Log.xx 我发现 if(bindService(...)) 之后的代码实际上是在调用 ServiceConnection.onServiceConnected 之前 - 也就是说,当下载器仍然为空时 - 这让我遇到了麻烦。ApiDemos 中的所有示例都通过仅在用户操作触发时调用服务来避免此时间问题。但是在bindService成功后我应该怎么做才能正确使用这个服务呢?如何等待 ServiceConnection.onServiceConnected 被可靠调用?

另一个相关的问题。所有的事件处理程序:Activity.onCreate、任何 View.onClickListener.onClick、ServiceConnection.onServiceConnected 等是否实际上在同一个线程中调用(在文档中称为“主线程”)?它们之间是否存在交错,或者 Android 会安排所有事件一一处理?或者,实际上何时调用 ServiceConnection.onServiceConnected?在 Activity.onCreate 完成后或 A.oC 仍在运行时?

4

8 回答 8

55

如何等待 ServiceConnection.onServiceConnected 被可靠调用?

你没有。您退出onCreate()(或您绑定的任何地方),然后将“需要建立连接”代码放入onServiceConnected().

是否所有事件处理程序:Activity.onCreate、任何 View.onClickListener.onClick、ServiceConnection.onServiceConnected 等实际上都在同一个线程中调用

是的。

ServiceConnection.onServiceConnected 到底什么时候会被调用?在 Activity.onCreate 完成后或 A.oC 仍在运行时?

您的绑定请求可能要等到您离开后才会开始onCreate()。因此,onServiceConnected()将在您离开后的某个时间致电onCreate()

于 2010-06-16T17:35:59.777 回答
3

我有同样的问题。不过,我不想将绑定的服务相关代码放入onServiceConnected,因为我想绑定/取消绑定onStartonStop,但我不希望代码在每次活动回到前面时再次运行。我只希望它在首次创建活动时运行。

我终于克服了我的onStart()狭隘视野并使用布尔值来指示这是否是第一次onServiceConnected运行。这样,我可以取消绑定服务onStop并再次绑定服务,onStart而无需每次都运行所有启动内容。

于 2012-11-09T07:50:29.327 回答
3

我最终得到了这样的结果:

1)为了给辅助的东西一些范围,我创建了一个内部类。至少,丑陋的内部与其余代码是分开的。我需要一个远程服务做某事Something,因此类名中的单词

private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
//...
}

2) 调用远程服务方法需要两件事:IBinder 和要执行的代码。因为我们不知道哪个先被知道,所以我们存储它们:

private ISomethingService mISomethingService;
private Runnable mActionRunnable;

每次我们写入这些文件之一时,我们都会调用_startActionIfPossible()

    private void _startActionIfPossible() {
        if (mActionRunnable != null && mISomethingService != null) {
            mActionRunnable.run();
            mActionRunnable = null;
        }
    }
    private void performAction(Runnable r) {
        mActionRunnable = r;
        _startActionIfPossible();
    }

当然,这假设 Runnable 可以访问 mISomethingService,但对于在RemoteSomethingHelper类的方法中创建的 runnables 也是如此。

在 UI 线程上ServiceConnection调用回调真的很好:如果我们要从主线程调用服务方法,我们不需要关心同步。

ISomethingService当然,是通过 AIDL 定义的。

3) 我们不只是将参数传递给方法,而是创建一个 Runnable,稍后当可以调用时,它将使用这些参数调用该方法:

    private boolean mServiceBound;
    void startSomething(final String arg1) {
        // ... starting the service ...
        final String arg2 = ...;
        performAction(new Runnable() {
            @Override
            public void run() {
                try {
                    // arg1 and arg2 must be final!
                    mISomethingService.startSomething(arg1, arg2);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

4)最后,我们得到:

private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
    private ISomethingService mISomethingService;
    private Runnable mActionRunnable;
    private boolean mServiceBound;
    private void _startActionIfPossible() {
        if (mActionRunnable != null && mISomethingService != null) {
            mActionRunnable.run();
            mActionRunnable = null;
        }
    }
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        // the methods on this class are called from the main thread of your process.
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mISomethingService = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mISomethingService = ISomethingService.Stub.asInterface(service);
            _startActionIfPossible();
        }
    }
    private void performAction(Runnable r) {
        mActionRunnable = r;
        _startActionIfPossible();
    }

    public void startSomething(final String arg1) {
        Intent intent = new Intent(context.getApplicationContext(),SomethingService.class);
        if (!mServiceBound) {
            mServiceBound = context.getApplicationContext().bindService(intent, mServiceConnection, 0);
        }
        ComponentName cn = context.getApplicationContext().startService(intent);
        final String arg2 = ...;
        performAction(new Runnable() {
            @Override
            public void run() {
                try {
                    mISomethingService.startSomething(arg1, arg2);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

context是我班上的一个领域;在一个活动中,您可以将其定义为Context context=this;

我不需要排队操作;如果你这样做,你可以实现它。

您可能需要在 startSomething(); 中进行结果回调;我做到了,但这没有在这段代码中显示。

于 2013-08-15T12:33:53.780 回答
1

我之前做过类似的事情,唯一不同的是我没有绑定到服务,而只是启动它。

我会从服务中广播一个意图,以通知调用者/活动它已启动。

于 2010-06-16T17:20:13.653 回答
1

我想添加一些你应该或不应该做的事情:

  1. 不是在创建时绑定服务,而是在 onResume 上绑定服务,然后在 Pause 上取消绑定。您的应用程序可以随时通过用户交互或操作系统屏幕进入暂停(后台)。在 onPause 中为每个服务取消绑定、接收器取消注册等使用不同的 try/catch,因此如果一个未绑定或注册,则异常不会阻止其他服务也被破坏。

  2. 我通常在一个公共的 MyServiceBinder getService() 方法中封装绑定。我也总是使用阻塞布尔变量,所以我不必关注所有在活动中使用服务的调用。

例子:

boolean isBindingOngoing = false;
MyService.Binder serviceHelp = null;
ServiceConnection myServiceCon = null;

public MyService.Binder getMyService()
{
   if(serviceHelp==null)
   {
       //don't bind multiple times
       //guard against getting null on fist getMyService calls!
       if(isBindingOngoing)return null; 
       isBindingOngoing = true;
       myServiceCon = new ServiceConnection(
           public void onServiceConnected(ComponentName cName, IBinder binder) {
               serviceHelp = (MyService.Binder) binder;
               //or using aidl: serviceHelp = MyService.Stub.AsInterface(binder);
               isServiceBindingOngoing = false;
               continueAfterServiceConnect(); //I use a method like this to continue
           }

           public void onServiceDisconnected(ComponentName className) {
              serviceHelp = null;
           }
       );
       bindService(serviceStartIntent,myServiceCon);
   }
   return serviceHelp;
}
于 2020-04-24T12:48:57.907 回答
1

Android 10在绑定到服务以提供(可以从Executors创建)时引入了新的bindService方法签名。Executor

    /**
     * Same as {@link #bindService(Intent, ServiceConnection, int)} with executor to control
     * ServiceConnection callbacks.
     * @param executor Callbacks on ServiceConnection will be called on executor. Must use same
     *      instance for the same instance of ServiceConnection.
    */
    public boolean bindService(@RequiresPermission @NonNull Intent service,
            @BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
            @NonNull ServiceConnection conn) {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }

这允许在线程中绑定到服务并等待它连接。例如存根:


private final AtomicBoolean connected = new AtomicBoolean()
private final Object lock = new Object();

... 

private void myConnectMethod() {
// bind to service
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    context.bindService(new Intent(context, MyServiceClass.class), Context.BIND_AUTO_CREATE, executorService, new 
   ServiceConnection() {
     @Override
     public void onServiceConnected(ComponentName name, IBinder binder) {
        synchronized (lock) {
            // TODO: store service instance for calls in case of AIDL or local services
            connected.set(true);
            lock.notify();
        }
     });

    synchronized (lock) {
            while (!connected.get()) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException();
                }
            }
        }
}

还需要在单独的进程中运行服务:

        <service
            android:name=".MyServiceClass"
            android:process=":service"
            android:enabled="true"
            android:exported="true" />
于 2021-02-23T04:03:41.243 回答
0

我发现这些变通办法只有在您的绑定服务运行在与应用程序主进程不同的进程中时才值得付出努力和等待。

为了在同一个进程(或应用程序)中访问数据和方法,我最终实现了单例类。如果类需要某些方法的上下文,我会将应用程序上下文泄漏给单例类。当然,它会带来不好的后果,因为它会破坏“即时运行”。但我认为这是一个整体上更好的折衷方案。

于 2016-12-14T11:33:46.943 回答
-1

*基本思想与@18446744073709551615相同,但我也会分享我的代码。

作为主要问题的答案,

但是在bindService成功后我应该怎么做才能正确使用这个服务呢?

[原来的期望(但不工作)]

等到服务连接如下

    @Override
    protected void onStart() {
        bindService(service, mWebServiceConnection, BIND_AUTO_CREATE);
        synchronized (mLock) { mLock.wait(40000); }

        // rest of the code continues here, which uses service stub interface
        // ...
    }

它不起作用,因为bindService()inonCreate()/onStart()和在同一个 main threadonServiceConnected()中 被调用。 在等待完成之前永远不会调用。onServiceConnected()

[替代解决方案]

代替“等待”,定义自己的 Runnable 在服务连接后调用,并在服务连接后执行此可运行。

如下实现 ServiceConnection 的自定义类。

public class MyServiceConnection implements ServiceConnection {

    private static final String TAG = MyServiceConnection.class.getSimpleName();

    private Context mContext = null;
    private IMyService mMyService = null;
    private ArrayList<Runnable> runnableArrayList;
    private Boolean isConnected = false;

    public MyServiceConnection(Context context) {
        mContext = context;
        runnableArrayList = new ArrayList<>();
    }

    public IMyService getInterface() {
        return mMyService;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.v(TAG, "Connected Service: " + name);
        mMyService = MyService.Stub.asInterface(service);

        isConnected = true;
        /* Execute runnables after Service connected */
        for (Runnable action : runnableArrayList) {
            action.run();
        }
        runnableArrayList.clear();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        try {
            mMyService = null;
            mContext.unbindService(this);
            isConnected = false;
            Log.v(TAG, "Disconnected Service: " + name);
        } catch(Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    public void executeAfterServiceConnected(Runnable action) {
        Log.v(TAG, "executeAfterServiceConnected");
        if(isConnected) {
            Log.v(TAG, "Service already connected, execute now");
            action.run();
        } else {
            // this action will be executed at the end of onServiceConnected method
            Log.v(TAG, "Service not connected yet, execute later");
            runnableArrayList.add(action);
        }
    }
}

然后按以下方式使用它(在您的 Activity 类等中),

private MyServiceConnection myServiceConnection = null;

@Override
protected void onStart() {
    Log.d(TAG, "onStart");
    super.onStart();

    Intent serviceIntent = new Intent(getApplicationContext(), MyService.class);
    startService(serviceIntent);
    myServiceConnection = new MyServiceConnection(getApplicationContext());
    bindService(serviceIntent, myServiceConnection, BIND_AUTO_CREATE);

    // Instead of "wait" here, create callback which will be called after service is connected
    myServiceConnection.executeAfterServiceConnected(new Runnable() {
        @Override
        public void run() {
            // Rest of the code comes here.
            // This runnable will be executed after service connected, so we can use service stub interface
            IMyService myService = myServiceConnection.getInterface();
            // ...
        }
    });
}

它对我有用。但可能有更多更好的方法。

于 2016-01-20T12:14:07.800 回答