【问题标题】:Android how do I wait until a service is actually connected?Android如何等到服务实际连接?
【发布时间】:2011-03-04 14:12:43
【问题描述】:

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

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

在 Downloader.onCreate(Bundle) 我尝试绑定服务

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 仍在运行时?

【问题讨论】:

标签: android binding service serviceconnection


【解决方案1】:

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

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

是所有的事件处理程序: Activity.onCreate,任意 View.onClickListener.onClick, ServiceConnection.onServiceConnected, 等等实际上是同一个 线程

是的。

具体是什么时候 ServiceConnection.onServiceConnected 实际上会被调用吗?之上 完成 Activity.onCreate 或 什么时候 A.oC 还在运行?

在您离开onCreate() 之前,您的绑定请求可能甚至不会开始。因此,onServiceConnected() 将在您离开 onCreate() 后的某个时间调用。

【讨论】:

  • 感谢您提供此信息。希望 Android 文档能像这样把事情弄清楚。
  • 各种Service API demo都有例子;具体参见本地服务绑定和远程服务绑定:developer.android.com/resources/samples/ApiDemos/src/com/… 服务 java 文档也有本地服务绑定的示例代码:developer.android.com/reference/android/app/…
  • 虽然我可以理解 onCreate() 必须在 onServiceConnected() 开始之前完成(我猜它在主线程 Looper 中挂起),但我注意到 onStart() 和 onResume() 也之前运行onServiceConnected()!这不是成为危险的比赛条件吗?查看依赖于 Service 的组件已准备就绪,但 Service 尚未连接。
  • @jfritz42 这不是问题所在。为什么Android不给我们一个同步版本的bind?
  • @eric 你不能指望任何特定的时间。这就是调用是异步的原因。
【解决方案2】:

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

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

【讨论】:

    【解决方案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 类的方法中创建的可运行对象也是如此。

    ServiceConnection回调are called on the UI thread真是太好了:如果我们要从主线程调用服务方法,我们不需要关心同步。

    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 是我班上的一个字段;在一个Activity中,可以定义为Context context=this;

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

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 感谢您的快速回复。因此,在广播接收器中,您将绑定到服务以调用 RPC 方法,还是在您的情况下不调用它们?
      • 你不能从广播接收器绑定到服务(因为广播接收一旦从onReceive返回就完成了)。
      【解决方案5】:

      我想补充一些你应该或不应该做的事情:

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

      【讨论】:

        【解决方案6】:

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

            /**
             * 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" />
        

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

            【解决方案8】:

            *基本思路与@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
                    // ...
                }
            

            这不起作用,因为onCreate()/onStart()onServiceConnected() 中的bindService() 都在同一个主线程 上调用。 onServiceConnected() 在等待结束之前永远不会被调用。

            [替代方案]

            代替“等待”,定义自己的Runnable在Service Connected之后调用,并在Service Connected之后执行这个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();
                        // ...
                    }
                });
            }
            

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2015-11-08
              • 1970-01-01
              • 1970-01-01
              • 2018-06-01
              • 1970-01-01
              • 2018-01-14
              • 1970-01-01
              相关资源
              最近更新 更多