2

I am developing an application and I need that when I leave the application (onPause ()) send a message to the service, using a bundle to save some data of my application. It works great. My problem is that when the application starts, it executes the method onCreate (), in this method I use the following code to establish the connection between the application and the service:

MyActivity.java

// This takes care of make the connection
this.getApplicationContext().startService(intent);
this.bindService(intent, mConnectionCallback, Context.BIND_AUTO_CREATE);
/** I need restore the bundle that is in the service, thus i'm sending a message */
Message msg = Message.obtain();
try {
    Bundle bundle = new Bundle();
    msg.setData(bundle);
    msg.what = MyService.REQUEST_BUNDLE;
    msg.replyTo = mMessageDispatcher;
    mServiceConnection.send(msg);
} catch (RemoteException e) {
    e.printStackTrace();
}

The problem is that when you send the message, the connection will still not be established and thus this will throw a NullPointerException. And i dont know how manage this. To be clear my app is just a simple time-tracker and when the user exits the application, I want the time to be saved in a bundle in the service. Is there a way to send the message, right after establishing the connection?

Some of you will say: - "Just send the message after establishing the connection in method onServiceConnected (ComponentName className, IBinder service) of your callback ServiceConnection". But the problem is that I have separated the activity from the API implementation of the service. Here is the complete code of my classes:

ServiceManager.java

public class ServiceManager extends Service {
private NotificationManager mNM;

protected HashMap<Integer, Method> magicSwitch = new HashMap<Integer, Method>();

public ServiceManager() {
    try {
        for (Method method : Class
                .forName(getClass().getCanonicalName())
                .getMethods()) {
            if (method.isAnnotationPresent(ExecutesWhen.class)) {
                try {
                        ExecutesWhen a = method.getAnnotation(ExecutesWhen.class);
                        magicSwitch.put(a.what(), method);
                        Log.d("AkrasiaService","AkrasiaService now knows how handle a "+method.getName()+" with id="+a.what());
                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
        }
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

class ServiceHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        Log.d("AkrasiaService","The service is going to manage a message from the client with what="+msg.what);
        try {
            Method met = magicSwitch.get(msg.what);
            if (met == null) {
                throw new NonExistingWhatException();
            } else {
                met.invoke(ServiceManager.this, msg);
            }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

/**
 * Target we publish for clients to send messages to IncomingHandler.
 */
final Messenger mMessageInBox = new Messenger(new ServiceHandler());

/**
 * Sends a message to the replyTo client.
 * @param replyTo: The <code>Messenger</code> to reply the message.
 * @param what: The what (subject).
 * @param bundle: A data bundle that will go attached to the message.
 */
protected void sendMessageToClient(Messenger replyTo, int what, Bundle bundle) {
    try {
        Message msg = Message.obtain(null,
                what, 0, 0);
        msg.setData(bundle);
        replyTo.send(msg);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

@Override
public IBinder onBind(Intent intent) {
    return mMessageInBox.getBinder();
}

@Override
public void onCreate() {
    mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
    showNotification();
}

/**
 * Show a notification while this service is running.
 */
private void showNotification() {
    //Here we immplement the notification
}

}

AkrasiaService.java (my concrete service)

public class AkrasiaService extends ServiceManager {
public final static int TRACKER_APP_BACKUP_REFRESH = 0;
public final static int TRACKER_APP_BACKUP_RETRIVE = 1;

public Bundle mTrackerBackupBundle;

public AkrasiaService() {
    super();
}

/** This are the handlers of the request from the client application. The
 *  annotation ExecuteWhen specifies which method must handle one determined request. 
 *  It uses the "what" annotation attribute like discriminative. */

@ExecutesWhen(what = AkrasiaService.TRACKER_APP_BACKUP_REFRESH)
public void trackerBundleRefresh(Message msg) {
    mTrackerBackupBundle = msg.getData();
}

@ExecutesWhen(what = AkrasiaService.TRACKER_APP_BACKUP_RETRIVE)
public void trackerBundleRetrive(Message msg) {
    sendMessageToClient(msg.replyTo, AkrasiaService.TRACKER_APP_BACKUP_RETRIVE, mTrackerBackupBundle);
}

//Test
@ExecutesWhen(what = AkrasiaService.FOO)
public void fooResponse(Message msg) {
    Bundle bundle = new Bundle();
    bundle.putString("test", "Test value");
    sendMessageToClient(msg.replyTo, AkrasiaService.FOO, bundle);
}

}

AnnotatedHandler.java

public class AnnotatedHandler extends Handler {

protected HashMap<Integer, Method> magicSwitch = new HashMap<Integer, Method>();

public AnnotatedHandler() {
    try {
        for (Method method : this.getClass().getMethods() ) {
            if (method.isAnnotationPresent(ExecutesWhen.class)) {
                try {
                    ExecutesWhen a = method
                            .getAnnotation(ExecutesWhen.class);
                    magicSwitch.put(a.what(), method);
                    Log.d("AnnotatedHandler","AnnotatedHandler now knows how handle a "+method.getName()+" and id"+a.what());
                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
        }
    } catch (SecurityException e) {
        e.printStackTrace();
    }
}

@Override
public void handleMessage(Message msg) {
    try {
        Log.d("AnnotatedHandler","The service is going to manage a message from the client with what="+msg.what);
        Method met = magicSwitch.get(msg.what);
        if (met == null) {
            throw new NonExistingWhatException();
        } else {
            met.invoke(AnnotatedHandler.this, msg);
        }
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

}

ServiceClient.java (My service api implementation)

public class ServiceClient {
/** Messenger for communicating with service. */
Messenger mServiceConnection = null;

/** Flag indicating whether we have called bind on the service. */
private boolean mIsBound = false;

private Messenger mMessageDispatcher;

/**
 * Class for interacting with the main interface of the service. This callback
 * takes care of setup <code>mServiceConnection</code> and therefore, start to 
 * talk with the service.
 */
private ServiceConnection mConnectionCallback = new ServiceConnection() {
    public void onServiceConnected(ComponentName className,
            IBinder service) {
        mServiceConnection = new Messenger(service);
        Log.d("AkrasiaService","The application is connected to the service now!");
        mIsBound = true;
    }

    public void onServiceDisconnected(ComponentName className) {
        mServiceConnection = null;
    }
};

/**
 * This method makes the binding between the service and the client application.
 * @param intent: An intent of the concrete implementation of the <code>ServiceManager</code>
 * @param activity: The Activity thats want communicate with the service.
 */ 
public void doBindService(Intent intent, Activity activity) { 
    Log.d("AkrasiaService","The application is trying to bind to the service...");
    activity.getApplicationContext().startService(intent);
    activity.bindService(intent, mConnectionCallback, Context.BIND_AUTO_CREATE);
}



public void doUnbindService(Activity activity) {
    if (mIsBound) {
        activity.unbindService(mConnectionCallback);
        mIsBound = false;
    }
}


/**
 * This method sends a single message to the service.
 * @param what: The what (subject) of the message.
 */
public void sendMessage(int what) {
    Message msg = Message.obtain();
    try {
      Bundle bundle = new Bundle();
      msg.setData(bundle);
      msg.what = what;
      msg.replyTo = mMessageDispatcher;
      Log.d("AkrasiaService","The application is going to send a message to the service");
      mServiceConnection.send(msg);
    } catch (RemoteException e) {
      e.printStackTrace();
    }
}

/**
 * This method sends a message to the service with a bundle attached.
 * @param what: The what (subject) of the message.
 * @param bundle: The data bundle attached to the message.
 */
public void sendMessage(int what, Bundle bundle) {
    Message msg = Message.obtain();
    try {
      msg.setData(bundle);
      msg.what = what;
      msg.replyTo = mMessageDispatcher;
      mServiceConnection.send(msg);
    } catch (RemoteException e) {
      e.printStackTrace();
    }
}

public void setIncomingHandler(Handler handler) {
    mMessageDispatcher = new Messenger(handler);
}

public boolean isConnected() {
    return mIsBound;
}

}

MainActivity.java (the Activity of my Tracker app)

public class MainActivity extends SherlockFragmentActivity {
private ActionBar mActionBar;

private Tab mStatTab;

private Tab mTrackerTab;

private ServiceClient mClientServiceAPI;

/**
 * Handler of incoming messages from service.
 */
public class  MyHandler  extends AnnotatedHandler {
    @ExecutesWhen(what = AkrasiaService.TRACKER_APP_BACKUP_RETRIVE)
    public void handleBackupRestore(Message msg) {
        Log.d("Client","Handling a message");
        Bundle bundle = msg.getData();
        if ((bundle != null) && (!bundle.isEmpty())) {
            Long timeStamp = bundle.getLong("last-time");
            Long chrometerTime = bundle.getLong("chrometer-time");
            Chronometer chrometer = (Chronometer) findViewById(R.id.chronometer);
            //We add the time between the timestamp and now to the chorometer base
            Long now = Calendar.getInstance().getTimeInMillis();
            chrometerTime = now - timeStamp + chrometerTime; 
            chrometer.setBase(chrometerTime);
        }
    }
};

@Override
public void onCreate(Bundle savedInstanceState) {
    setTheme(com.actionbarsherlock.R.style.Sherlock___Theme);
    super.onCreate(savedInstanceState);
    // Notice that setContentView() is not used, because we use the root
    // android.R.id.content as the container for each fragment
    // try {
    // DatabaseFixture.populateDatabase();
    // } catch (NumberFormatException e) {
    // // TODO Auto-generated catch block
    // e.printStackTrace();
    // } catch (ParseException e) {
    // // TODO Auto-generated catch block
    // e.printStackTrace();
    // }
    ApplicationContext.getInstance().setMainActivity(this);
    ApplicationContext.getInstance().setupPreferences();
    sherlockActionBarSetup();
}

private void sherlockActionBarSetup() {
    mActionBar = getSupportActionBar();
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    mActionBar.setDisplayShowTitleEnabled(false);
    mActionBar.setDisplayShowHomeEnabled(false);

    TabListener<TrackerFragment> trackerTabListener = new TabListener<TrackerFragment>(this,
            "tracker", TrackerFragment.class);
    mTrackerTab = mActionBar.newTab().setText("Track").setTabListener(trackerTabListener);
    mActionBar.addTab(mTrackerTab);

    TabListener<StatFragment> statTabListener = new TabListener<StatFragment>(this, "stats",
            StatFragment.class);
    mStatTab = mActionBar.newTab().setText("Stats").setTabListener(statTabListener);
    mActionBar.addTab(mStatTab);
}

public void sendBackupToTheService() {
    Log.d("Client","We are going to make a backup of the chromnometer");
    Chronometer chrometer = (Chronometer) findViewById(R.id.chronometer);
    Bundle bundle = new Bundle();
    bundle.putLong("last-time", Calendar.getInstance().getTimeInMillis());
    bundle.putLong("chrometer-time", chrometer.getBase());
    bundle.putBoolean("deprecated", false);
    mClientServiceAPI.sendMessage(AkrasiaService.TRACKER_APP_BACKUP_REFRESH, bundle);
}

@Override
protected void onPause() {
    super.onPause();
    sendBackupToTheService();
    mClientServiceAPI.doUnbindService(this);
}

@Override
protected void onStop() {
    super.onStop();
    mClientServiceAPI.doUnbindService(this);
}

public void restarBackupFromService(View view) {
    mClientServiceAPI.sendMessage(AkrasiaService.TRACKER_APP_BACKUP_RETRIVE);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    mClientServiceAPI.doUnbindService(this);
}

@Override
public void onResume() {
    super.onResume();
    //Sadly this behavior can't be exported to ServiceClient.
    //This from below used to be in onResume method
    Log.d("Client","We are going to connect to the service");
    mClientServiceAPI = new ServiceClient();
    mClientServiceAPI.setIncomingHandler(new MyHandler());
    Intent intent = new Intent(AkrasiaService.class.getName());

    mClientServiceAPI.doBindService(intent,this);
}

/*
 * Apparently you can't just tie the callback to the fragment from:
 * http://stackoverflow.com/a/6271637/147072
 */
public void triggerClick(View view) {
    TrackerFragment fragment = (TrackerFragment)getSupportFragmentManager().findFragmentByTag(
            "tracker");
    fragment.triggerClick(view);
}

public void saveTimeClick(View view) {
    TrackerFragment fragment = (TrackerFragment)getSupportFragmentManager().findFragmentByTag(
            "tracker");
    try {
        fragment.saveTimeClick(view);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    // We reload the StatFragment this is to refresh the Graph of the
    // StatFragment
    mActionBar.removeTab(mStatTab);
    TabListener<StatFragment> statTabListener = new TabListener<StatFragment>(this, "stats",
            StatFragment.class);
    mStatTab = mActionBar.newTab().setText("Stats").setTabListener(statTabListener);
    mActionBar.addTab(mStatTab);

}

public void discardTimeClick(View view) {
    TrackerFragment fragment = (TrackerFragment)getSupportFragmentManager().findFragmentByTag(
            "tracker");
    fragment.discardTimeClick(view);
}

How you can see in the MainActivity.java in the onResume method i'm doing the binding between the app and the service, this calls the doBindService(Intent,Activity) method of the ServiceClient.java, and this one do the real binding.

I've been thinking about saving the block of code that sends the message to the service within an instance of the class Method, and send it to the api for this run it on your onServiceConnected method, but I think there must be a better way.

Any suggestions will be appreciated.

Donut
  • 110,061
  • 20
  • 134
  • 146
4gus71n
  • 3,717
  • 3
  • 39
  • 66

0 Answers0