简体   繁体   English

Android 如何等到服务实际连接?

[英]Android how do I wait until a service is actually connected?

I have an Activity calling a Service defined in IDownloaderService.aidl:我有一个活动调用 IDownloaderService.aidl 中定义的服务:

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

In Downloader.onCreate(Bundle) I tried to bindService在 Downloader.onCreate(Bundle) 我试图 bindService

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

and within the ServiceConnection object sc I did this在 ServiceConnection 对象 sc 中我做了这个

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

By adding all kinds of Log.xx I found that the code after if(bindService(...)) actually goes BEFORE ServiceConnection.onServiceConnected is being called - that is, when downloader is still null - which gets me into trouble.通过添加各种 Log.xx,我发现 if(bindService(...)) 之后的代码实际上是在调用 ServiceConnection.onServiceConnected 之前 - 也就是说,当下载器仍然为空时 - 这让我陷入困境。 All the samples in ApiDemos avoid this timing problem by only calling services when triggered by user actions. ApiDemos 中的所有示例都通过仅在用户操作触发时调用服务来避免此计时问题。 But what should I do to right use this service after bindService succeeds?但是在 bindService 成功后我应该怎么做才能正确使用这个服务? How can I wait for ServiceConnection.onServiceConnected being called reliably?如何等待 ServiceConnection.onServiceConnected 被可靠地调用?

Another question related.另一个问题相关。 Are all the event handlers: Activity.onCreate, any View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. actually called in the same thread (mentioned in the doc as the "main thread")?是否所有事件处理程序:Activity.onCreate、任何 View.onClickListener.onClick、ServiceConnection.onServiceConnected 等实际上都在同一个线程中调用(在文档中称为“主线程”)? Are there interleaves between them, or Android would schedule all events come into being handled one-by-one?它们之间是否存在交错,或者Android会安排所有事件一一处理? Or, When exactly is ServiceConnection.onServiceConnected actually going to be called?或者, ServiceConnection.onServiceConnected 究竟何时被调用? Upon completion of Activity.onCreate or sometime when A.oC is still running? Activity.onCreate 完成后还是 A.oC 仍在运行时?

How can I wait for ServiceConnection.onServiceConnected being called reliably?如何等待 ServiceConnection.onServiceConnected 被可靠地调用?

You don't.你没有。 You exit out of onCreate() (or wherever you are binding) and you put you "needs the connection established" code in onServiceConnected() .您退出onCreate() (或您绑定的任何地方),并将“需要建立连接”代码放入onServiceConnected()

Are all the event handlers: Activity.onCreate, any View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. actually called in the same thread是否所有事件处理程序:Activity.onCreate、任何 View.onClickListener.onClick、ServiceConnection.onServiceConnected 等都在同一个线程中实际调用

Yes.是的。

When exactly is ServiceConnection.onServiceConnected actually going to be called? ServiceConnection.onServiceConnected 究竟何时被调用? Upon completion of Activity.onCreate or sometime when A.oC is still running? Activity.onCreate 完成后还是 A.oC 仍在运行时?

Your bind request probably is not even going to start until after you leave onCreate() .在您离开onCreate()之前,您的绑定请求甚至可能不会开始 Hence, onServiceConnected() will called sometime after you leave onCreate() .因此, onServiceConnected()将在您离开onCreate()后的某个时间调用。

I had the same problem.我有同样的问题。 I didn't want to put my bound service dependent code in onServiceConnected , though, because I wanted to bind/unbind with onStart and onStop, but I didn't want the code to run again every time the activity came back to the front.不过,我不想将绑定的服务相关代码放在onServiceConnected ,因为我想使用onStartonStop,绑定/解除绑定onStop,但我不希望每次活动回到前面时代码都再次运行。 I only wanted it to run when the activity was first created.我只希望它在首次创建活动时运行。

I finally got over my onStart() tunnel vision and used a Boolean to indicate whether this was the first onServiceConnected run or not.我终于onServiceConnected了我的onStart()隧道视野,并使用布尔值来指示这是否是第一次onServiceConnected运行。 That way, I can unbindService in onStop and bindService again in onStart without running all the start up stuff each time.这样,我可以在onStop取消绑定服务并在onStart再次绑定服务,而无需每次都运行所有启动内容。

I ended up with something like this:我最终得到了这样的结果:

1) to give the auxiliary stuff some scope, I created an internal class. 1)为了给辅助的东西一些范围,我创建了一个内部类。 At least, the ugly internals are separated from the rest of the code.至少,丑陋的内部结构与代码的其余部分是分开的。 I needed a remote service doing something , therefore the word Something in class name我需要一个远程服务做某事,因此类名中的“ Something ”一词

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

2) there are two things necessary to invoke a remote service method: the IBinder and the code to execute. 2) 调用远程服务方法需要两件事:IBinder 和要执行的代码。 Since we don't know which one becomes known first, we store them:由于我们不知道哪个先被知道,我们存储它们:

private ISomethingService mISomethingService;
private Runnable mActionRunnable;

Each time we write to one of these fileds, we invoke _startActionIfPossible() :每次我们写入这些文件之一时,我们都会调用_startActionIfPossible()

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

This, of course, assumes that the Runnable has access to mISomethingService, but this is true for runnables created within the methods of the RemoteSomethingHelper class.当然,这假设 Runnable 可以访问 mISomethingService,但对于在RemoteSomethingHelper类的方法中创建的 runnable 也是如此。

It is really good that the ServiceConnection callbacks are called on the UI thread : if we are going to invoke the service methods from the main thread, we do not need to care about synchronization. 在 UI 线程上调用ServiceConnection回调真的很好:如果我们要从主线程调用服务方法,我们不需要关心同步。

ISomethingService is, of course, defined via AIDL. ISomethingService当然是通过 AIDL 定义的。

3) Instead of just passing arguments to methods, we create a Runnable that will invoke the method with these arguments later, when invocation is possible: 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) finally, we get: 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 is a field in my class; context是我班上的一个字段; in an Activity, you can define it as Context context=this;在 Activity 中,您可以将其定义为Context context=this;

I did not need queuing actions;我不需要排队操作; if you do, you can implement it.如果你这样做,你可以实施它。

You likely will need a result callback in startSomething();您可能需要在 startSomething() 中进行结果回调; I did, but this is not shown in this code.我做了,但这在这段代码中没有显示。

I did something similar before, the only different is I was not binding to service, but just starting it.我之前做过类似的事情,唯一不同的是我没有绑定到服务,只是启动它。

I would broadcast an intent from the service to notify the caller/activity about it is started.我会从服务广播一个意图,以通知调用者/活动它已启动。

I wanted to add some things you should or should not do:我想添加一些你应该或不应该做的事情:

  1. bind the service not on create but onResume and unbind it onPause.不是在创建时绑定服务,而是在重启时绑定服务,并在暂停时解除绑定。 Your app can go into pause (background) at any time by user interaction or OS-Screens.您的应用程序可以随时通过用户交互或操作系统屏幕进入暂停(后台)状态。 Use a distinct try/catch for each and every service unbinding, receiver unregistering etc in onPause so if one is not bound or registered the exception doesn't prevent the others from being destroyed too.在 onPause 中为每个服务取消绑定、接收者取消注册等使用不同的 try/catch,因此如果一个服务未绑定或注册,则异常也不会阻止其他服务被销毁。

  2. I usually capsule binding in a public MyServiceBinder getService() Method.我通常在公共 MyServiceBinder getService() 方法中封装绑定。 I also always use a blocking boolean variable so I don't have to keep an eye on all those calls using the servie in the activity.我也总是使用阻塞布尔变量,所以我不必密切关注活动中使用 servie 的所有调用。

Example:例子:

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

I figured out that these workarounds are only worth the effort and the wait only if your bound services are running in a different process than your application's main process.我发现只有当您的绑定服务在与应用程序主进程不同的进程中运行时,这些解决方法才值得付出努力和等待。

For accessing data and methods in the same process (or application), I ended up implementing singleton classes.为了访问同一进程(或应用程序)中的数据和方法,我最终实现了单例类。 If the classes need a context for some methods, I leak the application context to the singleton classes.如果类需要某些方法的上下文,我会将应用程序上下文泄漏给单例类。 There is, of course, a bad consequence of it as it breaks the "instant run".当然,它有一个不好的后果,因为它打破了“即时运行”。 But that is an overall better compromise, I think.但我认为,这是一个总体上更好的妥协。

Android 10 has introduced a new bindService method signature when binding to a service to provide an Executor (which can be created from the Executors ). 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.");
    }

This allows to bind to the service in a thread and wait until it is connected.这允许在线程中绑定到服务并等待它连接。 Eg stub:例如存根:


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

It is also necessary to run the service in a separate process:还需要在单独的进程中运行服务:

        <service
            android:name=".MyServiceClass"
            android:process=":service"
            android:enabled="true"
            android:exported="true" />

*The basic idea is same with @18446744073709551615, but I will share my code as well. *基本思想与@18446744073709551615相同,但我也会分享我的代码。

As a answer of main question,作为主要问题的答案,

But what should I do to right use this service after bindService succeeds?但是在 bindService 成功后我应该怎么做才能正确使用这个服务?

[Original expectation (but not work)] [最初的期望(但不起作用)]

wait until service connected like below等到服务连接如下

    @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
        // ...
    }

It won't work because both bindService() in onCreate()/onStart() and onServiceConnected() is called at same main thread .它不会工作,因为这两个bindService()onCreate()/onStart()onServiceConnected()被调用在相同的主线程 onServiceConnected() is never called before wait finishes.在等待完成之前永远不会调用onServiceConnected()

[Alternative solution] [替代解决方案]

Instead of "wait", define own Runnable to be called after Service Connected and execute this runnable after service connected.而不是“等待”,定义自己的 Runnable 在服务连接后调用,并在服务连接后执行这个 runnable。

Implement custom class of ServiceConnection as follows.如下实现 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);
        }
    }
}

And then use it in the following way (in your Activity class or etc),然后按以下方式使用它(在您的 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();
            // ...
        }
    });
}

It worked for me.它对我有用。 But there may be more better way.但可能有更多更好的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM