I've read the documentation about services and many examples on the web. However, most examples just include the same code and I still don't understand the life cyle of a background service completely.
Here is what I'm trying to do:
Start Activity
Activity starts a service, receiving location data
Exit Activity
Service keeps collecting
Start Activity
Bind to service and do some stuff, e.g. display some results
And this is what I've done to achive it:
Implemented a service (not an IntentService)
tried to start it in two ways:
Start the service by binding to it:
bindService(intent, myLocationService, Context.BIND_AUTO_CREATE);
This results in the service beeing created when the activity starts and beeing destroyed, when I call unbindService(...), e.g. in the onStop() method of the activity.
Start the service by creating it, explicitly:
Intent intent = new Intent(this, MyLocationService.class);
startService(intent);
This results in the service beeing created when the activity starts. When exiting the activity, the services crashes (although, I've implemented all cleaning up) and gets started again. When starting the activity again, the services starts once more, too.
So, how can I start, bind and unbind a serivce gracefully, without all that crashing and restarting behaviour?
These are the relevant code lines of the service:
public class MyLocationService extends Service {
final IBinder myServiceBinder = new MyServiceBinder();
public static class MyServiceConnection implements ServiceConnection {
MyLocationService service;
boolean bound = false;
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
MyServiceBinder binder = (MyServiceBinder)iBinder;
service = binder.getService();
bound = true;
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
bound = false;
}
public MyLocationService getService() {
return service;
}
public boolean isBound() {
return bound;
}
}
/**
* Binder for GPSService
*/
public class MyServiceBinder extends Binder {
MyLocationService getService() {
return MyLocationService.this;
}
}
public MyLocationService() {
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return myServiceBinder;
}
}
These are the relevant code lines of the activity:
public class MyMainActivity extends AppCompatActivity {
MyLocationService.MyServiceConnection myServiceConnection;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MyLocationService.class);
startService(intent);
}
#Override
protected void onStart() {
super.onStart();
}
#Override
protected void onResume() {
super.onResume();
Intent intent = new Intent(this, MyLocationService.class);
bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onPause() {
super.onPause();
unbindService(myServiceConnection);
}
#Override
protected void onStop() {
super.onStop();
}
}
https://developer.android.com/guide/components/activities/activity-lifecycle.html
Depending on what your requirements are, should be onPause() onResume() or onStart() and onStop().
If you want the service to run in the background you could use
/**
* Called when all clients have disconnected from a particular interface
* published by the service. The default implementation does nothing and
* returns false.
*
* #param intent The Intent that was used to bind to this service,
* as given to {#link android.content.Context#bindService
* Context.bindService}. Note that any extras that were included with
* the Intent at that point will <em>not</em> be seen here.
*
* #return Return true if you would like to have the service's
* {#link #onRebind} method later called when new clients bind to it.
*/ public boolean onUnbind(Intent intent) {
return false; }
onRebind() to do something.
Related
My app polls data from server after it was notified by cloud messaging.
The data is fetched in GcmListenerService. My current approach is to use an Intent to transfer the data to the relevant activity via a broadcast receiver. This only works if the activity is in foreground.
How to store the data fetched by GcmListenerService such that the activity will update itself according to the fetched data as soon as it is resumed?
You could use service binding.
Declare a Binder implementation as part of your service definition:
public class MyService extends Service {
private final IBinder mBinder = new MyBinder();
// Set this field whenever you receive data from the cloud.
private ArrayList<MyDataType> latestCloudData;
public class MyBinder extends Binder {
public ArrayList<MyDataType> getLatestCloudData() {
return MyService.this.latestCloudData;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
Bind and unbind to your service in your activity's onStart and onStop, respectively, by providing an implementation of ServiceConnection:
public class MyActivity extends Activity {
// Provides an interface for communicating with the service.
MyBinder mMyBinder = null;
boolean mBound = false;
#Override
protected void onStart() {
super.onStart();
// Bind to service
Intent intent = new Intent(this, MyService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder binder) {
// Successfully bound, cast the IBinder to a MyBinder.
MyBinder myBinder = (MyBinder) binder;
// Can now use the MyBinder instance to communicate with the service.
MyActivity.this.mMyBinder = myBinder;
mBound = true;
// Use the MyBinder to get the latest cloud data from the service and update your view.
ArrayList<MyDataType> cloudData = MyActivity.this.mMyBinder.getLatestCloudData();
MyActivity.this.updateViewWithCloudData(cloudData);
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
private void updateViewWithCloudData(ArrayList<MyDataType> data) {
// Use the data to update your view here...
}
}
See the Android Developer Guide for more information about Bound Services (the example above is a modified version of the example found there).
Also note that this will only help you when your activity moves from the background to the foreground (or is recreated). If you also want to receive updates while the activity is in the foreground, you should also perform a broadcast which your activity should listen for. However, you do not need to bundle the cloud data as part of this broadcast. The broadcast can simply be a simple notification prompting the activity to query the service for the new data using MyBinder.getLatestCloudData().
I have background service which started on device boot . I want to get some data from that service in my activity .
I want the data, only when my activity start. so the basic requirement is that when my activity start it make a connection with the background service and get the data from this service and when activity stop then disconnect from the service.
You need to use bindService() to bind with running service and communicate with it.
Reference : http://developer.android.com/guide/components/bound-services.html
For example (from Android Docs),
public class BindingActivity extends Activity {
YourService mService;
boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onStart() {
super.onStart();
// Bind to Your Service
Intent intent = new Intent(this, YourService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from your Service.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to the running Service, cast the IBinder and get instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
In your service,
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. 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 {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
Basically, you should use bindService() in your activity onStart() and unbindService() in onStop()
I am trying to start a Service I have created called MyService from a BroadcastReceiver. My BroadcastReceiver is a inner class of a Fragment. So my code looks like this:
public class MyFragment extends Fragment {
/*
* Lots of code taken out
*/
private class ServiceListener extends BroadcastReceiver{
private Context parentContext = null;
public ServiceListener(Context parentContext){
this.parentContext = parentContext;
IntentFilter intentFilter = new IntentFilter("com.example.serverListener");
this.parentContext.registerReceiver(this, intentFilter);
}
#Override
public void onReceive(Context arg0, Intent arg1) {
Intent i = new Intent(arg0, MyService.class);
MyFragment.this.getActivity().startService(i);
MyFragment.this.getActivity().bindService(i, mConnection, Service.BIND_AUTO_CREATE);
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
//code taken out
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
//more code taken out
}
};
}
I can see the call going into the onReceive() method in ServiceListener, but after it finishes going over the lines to start and bind the service, I never hit breakpoints on the onBind() method inside MyService, or on my onServiceConnected() either.
Does anyone have an idea for what could be wrong?
Ps. As a side note, the Broadcast is coming from the FragmentActivity which holds a TabHost. The TabHost is what creates the Fragments, ie. MyFragment. If there is an easier way to call down into a Fragment from an FragmentActivity to trigger the Service to start and bind, I am all ears!
Thanks!
Edit:
I didn't have my service defined in my Manifest.xml... It's all working now! </silly>
I start my service in onCreate() (if onCreate is called the first time) and call bindService in onStart(). The service probaply works, but after calling bindService my local instance of the service is still null. Furthermore, is seems to be that getService() is not called.?
Here is some code:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
if(savedInstanceState == null){
final Intent i = new Intent(this, HostService.class);
startService(i);
}
}
protected void onStart(){
super.onStart();
bindService(new Intent(this, StartGameActivity.class), connection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection(){
#Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
HostBinder binder = (HostBinder) arg1;
hostService = binder.getService();
isBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
and in HostService:
...
private HostBinder binder = new HostBinder();
...
public class HostBinder extends Binder{
HostService getService(){
Log.d(TAG, "getService");
return HostService.this;
}
}
#Override
public IBinder onBind(Intent arg0) {
return binder;
}
Why is hostService still null, after onStart() is called and why is getService is not getting called ("getService" is not print in LogCat)?
thx & regards
You don't need to call startService(); it will be started by bindService().
In your onStart() method, you're creating an intent to launch StartGameActivity.class. Was that what you wanted, or did you mean to launch HostService.class?
If neither of those are your problem, we need more to go on. Can you put a logging statement inside your onServiceConnected() and onBind() methods so you can be sure they're called?
Any logcat messages that look interesting?
Are you talking to your service via the binder, or via messaging?
Ok, I'm new to android development and am trying to bind to a service so that I can call methods on the service once it's been started. The Activity and Service described below are both part of the same application so there shouldn't be any problems there, but everytime I run my app I get the following error:
java.lang.ClassCastException: android.os.BinderProxy
The line this happens on is:
LocalBinder binder = (LocalBinder) service;
My Activity code (simplified is):
public class Main extends Activity {
boolean gpsBound = false;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
/** Called whenever the activity is started. */
#Override
protected void onStart() {
super.onStart();
// Bind to GPSService
Intent i = new Intent(this, GPSService.class);
startService(i);
bindService(i, connection, Context.BIND_AUTO_CREATE);
}
/** service binding */
private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// After binding to GPSService get the instance of it returned by IBinder
LocalBinder binder = (LocalBinder) service;
gpsBound = true;
}
public void onServiceDisconnected(ComponentName className) {
gpsBound = false;
}
};
}
Service:
public class GPSService extends Service {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public IBinder onBind(Intent i) {
// TODO Auto-generated method stub
return new LocalBinder<GPSService>(this);
}
/**
* Our implementation of LocationListener that handles updates given to us
* by the LocationManager.
*/
public class CustomLocationListener implements LocationListener {
DBHelper db;
CustomLocationListener() {
super();
}
// Overridden methods here...
}
}
And finally my LocalBinder:
/**
* A generic implementation of Binder to be used for local services
* #author Geoff Bruckner 12th December 2009
*
* #param <S> The type of the service being bound
*/
public class LocalBinder<S> extends Binder {
private String TAG = "LocalGPSBinder";
private WeakReference<S> mService;
public LocalBinder(S service){
mService = new WeakReference<S>(service);
}
public S getService() {
return mService.get();
}
}
I understand the meaning of the ClassCast Exception but cannot understand what to do! I've followed the example in the google documentation but it's still not working. Can anyone shed any light on what might be causing this?
Thanks in advance!
Delete attribute process in your AndroidManifest.xml of your service.
Had same error. I had added the android:process=":process_description" attribute in the manifest. When you add it, your service is created as separate process and hence you get instance of binderProxy (Hence the class cast exception)
If you are trying to bind to a local service than yes, you can just cast it. However if you are trying to bind to a remote (separate process) service you must use the AIDL method as prescribed in this article.
http://developer.android.com/guide/components/aidl.html
the LocalBinder passed in onServiceConnected has a generic type argument, while your local variable LocalBinder binder does not have one.
Resolve this one way or another, either by removing the generic type from the definition of LocalBinder, or by adding one to your declaration of your local variable binder in onServiceConnected
class MyBoundService extends Service{
private final IBinder mBinder = new MyBinder();
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class MyBinder extends Binder{
public void doStuff(){
//Stuff
}
//More Binder Methods
}
}
class MyActivity extends Activity{
private MyBinder mBinder;
#Override
protected void onStart(){
Intent intent = new Intent(this, MyBoundService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop(){
unbindService(mConnection);
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
mBinder = (TaskBinder) service;
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
private void doStuff(){
if (mBound)
mBinder.doStuff();
}
}
No real need to fiddle around with weak references and whatnot. just be sure to unbind (I didn't in the sample)
If you want to invoke service methods ASAP, just put calls in onServiceConnected, after you set mBinder. otherwise, just invoke from other callbacks (onClick events and whatnot).