Tuesday, November 23, 2010

Activity Result: The Secret of Passing Information From a Child to a Parent

When an Activity closes or "stops" in Android, how do we pass information back to the parent that started this Activity? The trick is to use Activity Result following this 3-step design pattern:

Step 1: In the parent activity, start the child activity with startActivityForResult()
public class Parent extends Activity {
   private final void startChildOnClick() {
      Intent intent = new Intent(this, Child.class);
      intent.setAction(MyConstants.ACTION_START_CHILD);
      intent.putExtra("Parent Uid", Process.myUid());
      startActivityForResult(intent, 10);
   }
}

Step 2: In the parent activity, override onActivityResult():
public class Parent extends Activity {
   private final void startChildOnClick() {
      Intent intent = new Intent(this, Child.class);
      intent.setAction(MyConstants.ACTION_START_CHILD);
      intent.putExtra("Parent Uid", Process.myUid());
      startActivityForResult(intent, 10);
   }

   @Override
   protected final void onActivityResult(final int requestCode, 
                                         final int resultCode, 
                                         final Intent data) {
      // !!! Called before onRestart() if the parent was 
      // not destroyed!!!
      super.onActivityResult(requestCode, resultCode, data);
      if (requestCode == 10 && resultCode == 20) {
         if (data != null) {
            int cUid = data.getString("Child Uid");
            // Be careful to when to use the data passed 
            // from child
         }
      }
   }
}
It is very important to understand when onActivityResult() is called in the Activity lifecyle. If the parent activity was not destroyed before it is brought back to the foreground again, the calling sequence will be:
onStop() --> onActivityResult() --> onRestart() --> onStart() --> onResume()
Here, a good design pattern for processing data passed from the child is to delay processing to after onStart() by handing off the data to a Handler.

Step 3: In the child activity, override finish():
public class Child extends Activity {
   @Override
   public final void finish() {
      Intent intent = new intent(this, Parent.class);
      intent.putExtra("Child Uid", Process.myUid());
      // Android will pass the result set here to the parent
      setResult(20, intent);
      super.finish(); // Must be called last!!
   }
}
Note that the child must call call super.finish() as the very last statement in the block.
That's it. Happy hacking!

* Updated on December 01, 2010.

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.