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

前端 未结 7 1891
遇见更好的自我
遇见更好的自我 2020-12-02 12:50

I have an Activity calling a Service defined in IDownloaderService.aidl:

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


        
相关标签:
7条回答
  • 2020-12-02 13:29

    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.

    0 讨论(0)
  • 2020-12-02 13:30

    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. 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. That way, I can unbindService in onStop and bindService again in onStart without running all the start up stuff each time.

    0 讨论(0)
  • 2020-12-02 13:35

    How can I wait for ServiceConnection.onServiceConnected being called reliably?

    You don't. You exit out of onCreate() (or wherever you are binding) and you put you "needs the connection established" code in onServiceConnected().

    Are all the event handlers: Activity.onCreate, any View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. actually called in the same thread

    Yes.

    When exactly is ServiceConnection.onServiceConnected actually going to be called? Upon completion of Activity.onCreate or sometime when A.oC is still running?

    Your bind request probably is not even going to start until after you leave onCreate(). Hence, onServiceConnected() will called sometime after you leave onCreate().

    0 讨论(0)
  • 2020-12-02 13:38

    *The basic idea is same with @18446744073709551615, but I will share my code as well.

    As a answer of main question,

    But what should I do to right use this service after bindService succeeds?

    [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. onServiceConnected() is never called before wait finishes.

    [Alternative solution]

    Instead of "wait", define own Runnable to be called after Service Connected and execute this runnable after service connected.

    Implement custom class of ServiceConnection as follows.

    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),

    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.

    0 讨论(0)
  • 2020-12-02 13:41

    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.

    2. I usually capsule binding in a public MyServiceBinder getService() Method. 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.

    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;
    }
    
    0 讨论(0)
  • 2020-12-02 13:48

    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.

    0 讨论(0)
提交回复
热议问题