Tuesday, November 2, 2010

Service Binding Workaround in Tab Activity

Calling bindService() from an embedded tab activity can generate an error like this:
    W/ActivityManager( 61): Binding with unknown activity: android.os.BinderProxy@43f4a2d0

A quick googling turned up two bug reports on this problem:
http://code.google.com/p/android/issues/detail?id=2665
http://code.google.com/p/android/issues/detail?id=2483

Apparently, bindService() can not be performed from inside an embedded activity such as a Tab.  There are three workarounds:
  1. Call getParent().bindService()
  2. Call getApplicationContext().bindService()
  3. Call bindService() in the TabHost activity and then passes the binder to other tabs.
Application characteristics should determine which workaround is the best.  But whenver we touch the context object, we should always keep these memory leak prevention tips in mind.

Updated on November 6, 2010:

Because a service connection is context specific, the workaround #1 and #2 can have unintended threading consequences when you use TabActivity and TabHost to manage embedded tabs.  For example, when getApplicationContext().bindService() is used, the ServiceConnection.onServiceConnected() will be called from a thread different from the thread running the current tab.  Therefore, access to the service object must be synchronized.  This is demonstrated below:

public class MyTab extends Activity {
   private static final TAG = MyTab.this.getName();
   private RemoteService remoteService = null;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       if (getApplicationContext().bindService()) {
          // Unsynchronized access!!
          Log.v(TAG, "Service bound ? " + remoteService != null ? "true" : "false";
       }
   }

   private class MyServiceConnection implements ServiceConnection {
      @Override
      public void onServiceConnected (final ComponentName name, 
                                      final IBinder service) {
         // Unsynchronized access!!
         remoteService = RemoteService.stub.asInterface(service);
      }

      @Override
      public void onServiceDisconnected (final ComponentName name)
      {
         // Unsynchronized access!!
         remoteService = null;
      }
   }
}
A straight-forward way to fix the synchronization problem is to introduce a lock variable:
public class MyTab extends Activity {
   private static final TAG = MyTab.this.getName();
   private RemoteService remoteService = null;
   private final Object() lock = new Object();

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       if (getApplicationContext().bindService()) {
          // Synchronized access
          synchroized (lock) {
             try {
                while (remoteService == null) {
                   lock.wait();
                }
             } catch (InterruptedException e) {
                // Recheck condition
             }
          }
          Log.v(TAG, "Service bound ? " + remoteService != null ? "true" : "false";
       }
   }

   private class MyServiceConnection implements ServiceConnection {
      @Override
      public void onServiceConnected (final ComponentName name, 
                                      final IBinder service) {
         // Synchronized access
         synchronized (lock) {
            remoteService = RemoteService.stub.asInterface(service);
            lock.notifyAll();
         }
      }

      @Override
      public void onServiceDisconnected (final ComponentName name)
      {
         // Synchronized access
         synchronized (lock) {
            remoteService = null;
            lock.notifyAll();
         }
      }
   }
}
But we are not all done yet. We must use the same lock variable to pretect EVERY read/write access to the remoteService variable in this class. Why? Because the ServiceConnection.onServiceDisconnected() may be called from the main looper thread when the process hosting the background service has terminated or stopped unexpectedly. This can happen even though a service would normally be kept alive as long as there is a binding to it. Therefore, we need to surround every access to the remoteService variable with a lock to pretect ourself from reading stale object reference.

1 comment:

  1. Locksmith Uxbridge

    Locksmiths are called to unlock doors of our cars or homes. Have you ever wondered how they do their magic? If you’ve ever had to call one, you may probably have seen the locksmith tools that they use to save the day.

    ReplyDelete