アプリケーションを開発していますが、アプリケーションを終了するときに(onPause())、バンドルを使用してアプリケーションのデータを保存し、サービスにメッセージを送信する必要があります。それは素晴らしい働きをします。私の問題は、アプリケーションの起動時にメソッドonCreate()を実行することです。このメソッドでは、次のコードを使用して、アプリケーションとサービスの間の接続を確立します。
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();
}
問題は、メッセージを送信しても接続が確立されないため、NullPointerExceptionがスローされることです。そして、私はこれをどのように管理するのかわかりません。明確にするために、私のアプリは単なるタイムトラッカーであり、ユーザーがアプリケーションを終了するときに、サービスのバンドルに時間を節約したいと考えています。接続を確立した直後にメッセージを送信する方法はありますか?
「コールバックServiceConnectionのメソッドonServiceConnected(ComponentName className、IBinder service)で接続を確立した後、メッセージを送信するだけです」と言う人もいます。しかし、問題は、サービスのAPI実装からアクティビティを分離したことです。これが私のクラスの完全なコードです:
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(私の具体的なサービス)
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(私のサービスAPI実装)
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(私のトラッカーアプリのアクティビティ)
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);
}
onResumeメソッドのMainActivity.javaで、アプリとサービス間のバインディングを実行していることがわかります。これにより、ServiceClient.javaのdoBindService(Intent、Activity)メソッドが呼び出され、これが実際のバインディングを実行します。
クラスMethodのインスタンス内のサービスにメッセージを送信するコードのブロックを保存し、それをAPIに送信して、onServiceConnectedメソッドで実行することを考えていましたが、もっと良い方法があるはずです。 。
任意の提案をいただければ幸いです。