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

时间:2010-06-16 17:17:21

标签: android binding service serviceconnection

我有一个Activity调用在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之前 - 也就是说,当下载器仍然为null时 - 这会让我遇到麻烦。 ApiDemos中的所有样本都通过仅在用户操作触发时调用服务来避免此时间问题。但是,在bindService成功之后,我该怎么做才能正确使用这个服务?如何可靠地等待ServiceConnection.onServiceConnected被调用?

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

8 个答案:

答案 0 :(得分:48)

  

我该怎么办?   ServiceConnection.onServiceConnected   被可靠地召唤?

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

  

是所有事件处理程序:   Activity.onCreate,任何   View.onClickListener.onClick,   ServiceConnection.onServiceConnected,   实际上也叫同样的   螺纹

  

究竟是什么时候   ServiceConnection.onServiceConnected   实际上会被称为?上   完成Activity.onCreate或   有时候A.oC还在运行吗?

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

我最终得到了类似的东西:

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,这是正确的。

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

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

  1. 不将服务绑定到create上,而是将onResume绑定到onPause上。您的应用可以随时通过用户互动或OS屏幕进入暂停状态(后台)。 对于onPause中的每个服务解除绑定,接收者取消注册等,请使用不同的try / catch,因此,如果未绑定或注册一个服务,则异常不会阻止其他服务也被破坏。

  2. 我通常在公共MyServiceBinder getService()方法中进行胶囊绑定。我还总是使用一个阻塞的布尔变量,所以我不必在活动中使用servie来监视所有这些调用。

示例:

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

答案 5 :(得分:0)

*基本思路与@ 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()onCreate()/onStart()中的onServiceConnected()相同主线处被调用。 在等待完成之前永远不会调用onServiceConnected()

[替代解决方案]

而不是" wait",定义在Service Connected之后调用自己的Runnable并在连接服务后执行此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();
            // ...
        }
    });
}

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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