Revised: Would like to move class LocalBinder (extends Binder implements ILocalService) from within LocalService (extends Service) to LocalBinder.java
My LocalService.java contains a class LocalBinder
that defines the methods that are declared in ILocalService.java whose definitions clutter up my LocalService.java. I would like to move it to its own file LocalBinder.java. Here's my LocalBinder
:
public class LocalBinder extends Binder implements ILocalService {
@Override
public LocalService getService() {
return LocalService.this;
}
@Override
public int getStatusCode() {
return statusCode;
}
@Override
public void startRcvThread(Handler handler) {
Thread thread = passHandlerToThread(handler);
thread.start();
}
}
I've tried a number of times and failed. Am I just better off leaving LocalBinder
in LocalService
?
Below is all five of my source code files marked off by header comments. Please have a look at the title of my question. It explains what I want to do. I want to unclutter my LocalService.java by removing "public class LocalBinder" from LocalService.java and put it in its own file: LocalBinder.java. Any help is greatly appreciated. @Commonsware has already provided significant help but it's a bit much for me to comprehend.
/**************************************************************************************************
* Filename: Controller.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains the primary activity for this application
**************************************************************************************************/
package com.marie.localservicesample;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/*
* Example of explicitly starting and stopping the local service.
* This demonstrates the implementation of a service that runs in the same
* process as the rest of the application, which is explicitly started and stopped
* as desired.
*/
//public static class Controller extends Activity {
public class Controller extends Activity {
// Message types sent from the BluetoothReadService Handler
public static final int MESSAGE_SONG = 1;
public static final int MESSAGE_NOTHING = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.local_service_controller);
// Watch for button clicks.
Button button = (Button)findViewById(R.id.start);
button.setOnClickListener(mStartListener);
button = (Button)findViewById(R.id.stop);
button.setOnClickListener(mStopListener);
}
private OnClickListener mStartListener = new OnClickListener() {
public void onClick(View v) {
// Make sure the service is started. It will continue running
// until someone calls stopService(). The Intent we use to find
// the service explicitly specifies our service component, because
// we want it running in our own process and don't want other
// applications to replace it.
//startService(new Intent(Controller.this, LocalService.class));
Intent startSvc = new Intent(Controller.this, LocalService.class);
startSvc.putExtra(LocalService.EXTRA_MESSENGER, new Messenger(ctrlHandler));
startSvc.putExtra(LocalService.EXTRA_SONG, 7);
startService(startSvc);
Intent binding = new Intent(Controller.this, Binding.class);
startActivity(binding);
}
};
private OnClickListener mStopListener = new OnClickListener() {
public void onClick(View v) {
// Cancel a previous call to startService(). Note that the
// service will not actually stop at this point if there are
// still bound clients.
stopService(new Intent(Controller.this,
LocalService.class));
}
};
/*
* Here's a way to send a message to a handler - haven't tried it yet:
* Give the new state to the Handler so the UI Activity can update
* mHandler.obtainMessage(BlueSentry.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
* But for now let's msg.what to our handler
*/
private Handler ctrlHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case MESSAGE_SONG:
String songObj = (String) msg.obj;
int songArg1 = msg.arg1;
Log.i("ctrlHandler", "MESSAGE_SONG: " + songObj + " " + songArg1);
break;
case MESSAGE_NOTHING:
String nothingObj = (String) msg.obj;
int nothingArg1 = msg.arg1;
Log.i("ctrlHandler", "MESSAGE_NOTHING: " + nothingObj + " " + nothingArg1);
break;
default:
break;
}
}
};
}
/**************************************************************************************************
* Filename: LocalService.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains a local service
**************************************************************************************************/
package com.marie.localservicesample;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;
public class LocalService extends Service {
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
//private int NOTIFICATION = R.string.local_service_started;
private int NOTIFICATION = R.string.local_service_started;
private int statusCode = 99;
private int emptyMsg = 549;
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder implements ILocalService {
@Override
public LocalService getService() {
return LocalService.this;
}
@Override
public int getStatusCode() {
return statusCode;
}
@Override
public void startRcvThread(Handler handler) {
Thread thread = passHandlerToThread(handler);
thread.start();
}
}
public static final String EXTRA_MESSENGER = "com.marie.localservicesample.EXTRA_MESSENGER";
private Messenger messenger;
public static final String EXTRA_SONG = "com.marie.localservicesample.EXTRA_SONG";
private int song;
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting. We put an icon in the status bar.
showNotification();
/* Move this stuff to afterStartCommand()
Thread thr = new Thread(null, new ServiceWorker(), "LocalService");
thr.start();
*/
}
// Call this at the end of onStartCommand()
public void afterStartCommand() {
Thread thr = new Thread(null, new ServiceWorker(), "LocalService");
thr.start();
}
/*
* see http://developer.android.com/reference/java/lang/Thread.html
*/
class threadClass extends Thread {
private Handler mHandler;
private Message mMsg;
// constructor
public threadClass(Handler handler, Message msg) {
// do something like save the Handler reference
mHandler = handler;
mMsg = msg;
}
@Override
public void run() {
// do some background processing, call the Handler?
mHandler.sendMessage(mMsg);
}
}
public Thread passHandlerToThread(Handler handler) {
handler.sendEmptyMessage(emptyMsg);
Message msg = Message.obtain();
msg.what = emptyMsg;
Thread thread = new threadClass(handler, msg);
return thread;
}
/*
* This is the ServiceWorker thread that passes messages to the handler defined in
* the Controller activity.
*/
class ServiceWorker implements Runnable
{
public void run() {
// do background processing here... something simple
while (messenger == null) {
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
// send a message to the handler
try {
Message msg = Message.obtain();
msg.what = Controller.MESSAGE_SONG;
msg.obj = "Song";
msg.arg1 = song;
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
try {
Message msg = Message.obtain();
msg.what = Controller.MESSAGE_NOTHING;
msg.obj = "Nothing";
msg.arg1 = 0;
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
// stop the service when done...
// LocalService.this.stopSelf();
// Or use the unbindBtn in the MainActivity class.
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("LocalService", "Received start id " + startId + ": " + intent);
Bundle extras = intent.getExtras();
messenger = (Messenger)extras.get(EXTRA_MESSENGER);
try {
song = (Integer) extras.get(EXTRA_SONG);
} catch (NullPointer开发者_高级运维Exception e) {
e.printStackTrace();
song = 0;
}
afterStartCommand();
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(NOTIFICATION);
// Tell the user we stopped.
Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
}
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.local_service_started);
// Set the icon, scrolling text and timestamp
Notification notification = new Notification(R.drawable.stat_sample, text,
System.currentTimeMillis());
// The PendingIntent to launch our activity if the user selects this notification
//PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, LocalServiceActivities.Controller.class), 0);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Controller.class), 0);
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(this, getText(R.string.local_service_label),
text, contentIntent);
// Send the notification.
mNM.notify(NOTIFICATION, notification);
}
}
/**************************************************************************************************
* Filename: Binding.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains the binding for this application
**************************************************************************************************/
package com.marie.localservicesample;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
/*
* Example of binding and unbinding to the local service.
* This demonstrates the implementation of a service which the client will
* bind to, receiving an object through which it can communicate with the service.
*/
public class Binding extends Activity {
private boolean mIsBound;
private ILocalService mBoundService = null;
private LocalService mBoundService1;
private LocalService mBoundService2;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService1 = ((LocalService.LocalBinder)service).getService();
ILocalService localSvc = (ILocalService)service;
mBoundService2 = localSvc.getService();
if (mBoundService1 == mBoundService2) {
Log.d("mBoundeService 1 and 2 ", "are equal");
mBoundService = (ILocalService) service;
}
int statusCode = localSvc.getStatusCode();
Log.d("Binding.java","called onServiceConnected. statusCode: " + statusCode);
Toast.makeText(Binding.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService1 = null;
mBoundService2 = null;
Log.d("Binding", "called onServiceDisconnected");
Toast.makeText(Binding.this, R.string.local_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(Binding.this,
LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
LocalService service = mBoundService.getService();
if (service != null) Log.d("doUnbindService", "service: " + service);
int statusCode = mBoundService.getStatusCode();
if (statusCode != 0) Log.d("doUnbindService", "statusCode: " + statusCode);
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
private OnClickListener mBindListener = new OnClickListener() {
public void onClick(View v) {
doBindService();
Intent rcvMsg = new Intent(Binding.this, RcvMessages.class);
startActivity(rcvMsg);
}
};
private OnClickListener mUnbindListener = new OnClickListener() {
public void onClick(View v) {
doUnbindService();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.local_service_binding);
// Watch for button clicks.
Button button = (Button)findViewById(R.id.bind);
button.setOnClickListener(mBindListener);
button = (Button)findViewById(R.id.unbind);
button.setOnClickListener(mUnbindListener);
}
}
/**************************************************************************************************
* Filename: RcvMessages.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains stub code that displays a test message in an EditText.
**************************************************************************************************/
package com.marie.localservicesample;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.text.InputType;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
/*
* Class: RcvMessages
* Purpose: RcvMessages is stub code that I want to extend in some way to receive messages from
* the background Service.
*/
public class RcvMessages extends Activity {
//private LocalService mBoundService;
private boolean mIsBound;
private ILocalService mBoundService;
//public static final String EXTRA_SONG = "com.marie.mainactivity.EXTRA_SONG";
EditText myText;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//mBoundService = ((LocalService.LocalBinder)service).getService();
mBoundService = (ILocalService)service;
ILocalService localSvc = (ILocalService)service;
int statusCode = localSvc.getStatusCode();
Log.d("RcvMessages.java","called onServiceConnected. statusCode: " + statusCode);
//myText.setText("statusCode: " + statusCode);
}
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(RcvMessages.this, LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.messages);
myText = (EditText)findViewById(R.id.my_text);
myText.setSingleLine();
myText.setInputType(InputType.TYPE_NULL);
// Display a simple test message for now.
// myText.setText("RcvMessages here");
Button button = (Button)findViewById(R.id.display);
button.setOnClickListener(mDisplayListener);
// I'm expecting this to call mConnection()'s onServiceConnected
// bindService(new Intent(RcvMessages.this, LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
// IBinder binder = mBoundService.onBind(new Intent(RcvMessages.this, LocalService.class));
doBindService();
}
private OnClickListener mDisplayListener = new OnClickListener() {
public void onClick(View v) {
// Make sure the service is started. It will continue running
// until someone calls stopService(). The Intent we use to find
// the service explicitly specifies our service component, because
// we want it running in our own process and don't want other
// applications to replace it.
//startService(new Intent(Controller.this, LocalService.class));
mBoundService.startRcvThread(rcvHandler);
}
};
/*
@Override
public void onStart(){
super.onStart();
Bundle extras = getIntent().getExtras();
int song = (Integer) extras.get(EXTRA_SONG);
//myText.setText("song " + song);
// Start the background Service for sending canned messages to the handler as a test.
Log.d("RcvMessages", "starting service from rcvmessages");
Intent localSvc = new Intent(RcvMessages.this, LocalService.class);
localSvc.putExtra(LocalService.EXTRA_MESSENGER, new Messenger(handler));
localSvc.putExtra(EXTRA_SONG, song);
startService(localSvc);
}
*/
/*
* This is a handler to be passed to the local Service.
*/
private Handler rcvHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
int what = msg.what;
Log.i("rcvHandler", "rcvWhat: " + what);
myText.setText("rcvWhat: " + what);
}
};
}
/**************************************************************************************************
* Filename: ILocalService.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains an example interface for LocalService
**************************************************************************************************/
package com.marie.localservicesample;
import android.os.Handler;
public interface ILocalService {
public LocalService getService();
public int getStatusCode();
public void startRcvThread(Handler handler);
}
I have revised my solution to moving my LocalBinder to its own file as follows:
public class LocalBinder extends Binder implements ILocalService {
@Override
public int getStatusCode() {
return LocalService.statusCode;
}
@Override
public void startRcvThread(Handler handler) {
Thread thread = passRcvHandlerToRcvThread(handler);
thread.start();
}
public Thread passRcvHandlerToRcvThread(Handler handler) {
Message msg = Message.obtain();
msg.what = LocalService.emptyMsg;
Thread thread = new RcvThread(handler, msg);
return thread;
}
}
As you can see I've deleted the getService() method completely because I don't need it. To use the methods defined in LocalBinder above, an activity must define onServiceConnected() and its unimplemented methods onServiceConnected() and onServiceDisconnected() in a manner similar to this:
private ILocalService mBoundService;
private boolean mIsBound;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = (ILocalService)service;
int statusCode = mBoundService.getStatusCode();
Log.d("Binding.java","called onServiceConnected. statusCode: " + statusCode);
Toast.makeText(Binding.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
Log.d("Binding", "called onServiceDisconnected");
Toast.makeText(Binding.this, R.string.local_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
With mBoundService declared and defined as above I now have an IBinder with which to invoke methods defined in LocalBinder above.
And as before there are some things to note that I'm not sure about but seem to work:
In LocalBinder "getStatusCode()" returns a static "LocalService.statusCode."
In LocalBinder "startRcvThread()" invokes "pasRcvHandlerToRcvHandler()" which needs a static "LocalService.emptyMsg."
I'm always concerned about using statics but logcat doesn't seem to be complaining about any leaks. I know that does not mean there aren't any. So I hope someone finds this useful.
精彩评论