Properly unbind service from activity - android

I have a simple android app with an activity that binds a service. The basic code is like this:
public class MyActivity extends AppCompatActivity {
private MyService service;
private boolean serviceIsBound;
private final ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
service = ((MyService.LocalBinder) binder).getService();
serviceIsBound = true;
}
#Override
public void onServiceDisconnected(ComponentName name) {
serviceIsBound = false;
}
};
// Service gets bound via intent in onCreate()
#Override
protected void onDestroy() {
super.onDestroy();
if (serviceIsBound) {
service.unbindService(serviceConnection);
}
}
}
This produces an error java.lang.IllegalArgumentException: Service not registered in the call service.unbindService(...) when I close the activity.
I tried onStop() instead of onDestroy() --> same error.
I tried removing the onDestroy() --> I get an error android.app.ServiceConnectionLeaked. This error of course makes sense -- after all you should clean up your service connections. I just don't know how.

Call unbindService() on the same Context that you call bindService() on. Presumably, given the structure of your sample, you are calling bindService() on the MyActivity instance; if so, call unbindService() on that MyActivity instance as well.
Note that you probably should not be doing it this way. On a configuration change (e.g., screen rotation), your MyActivity instance will be destroyed and recreated. This means that you will unbind from the service, then bind to it again. If nothing else is bound to that service, and that service is not started, the service will be destroyed (when it is unbound) and then recreated (when the new activity instance binds again).
It is very likely that you do not need a bound service, particularly if the service is in the same process as the rest of your app. If you are certain that you need a bound service, bind and unbind from something that survives configuration changes, such as an AndroidViewModel. There, you would use the Application as your Context for the bind/unbind calls. Or, if you are using a dependency inversion (DI) framework (e.g., Dagger/Hilt, Koin), you might get a Context from it. Or, if appropriate, bind and unbind from some DI-managed singleton, again using the Application as your `Context.
FWIW, this sample app contains a bound service. It is used by this client app, which binds and unbinds from a ViewModel.

Related

Why do Android bound services load after Fragments?

My question is exactly as it says on the title. Why is it that Android bound services always load after a whole fragment lifecycle is complete? Bound services are meant to be loaded on the activity containing the fragment, so why is it that the service is only available after the fragment is created? Sometimes I want to use the service to populate things in the fragment and needed to recur to "hacks" to get to use the service.
I load the service connections in the onCreate() method in the activity and start the service in OnStart() as described by the documentation https://developer.android.com/guide/components/bound-services but then I have to create a "loading" method in the fragment to load stuff once the service finished loading.
private void loadServiceConnections()
{
metronomeConnection = new ServiceConnection()
{
#Override
public void onServiceConnected(ComponentName name, IBinder service)
{
MetronomeService.LocalBinder localBinder = (MetronomeService.LocalBinder) service;
metronomeService = localBinder.getService();
metronomeService.setLinkManager(notificationsMetronomeLinkManager);
metronomeService.setListener(myFragment);
metronomeFragment.onServiceLoading(); //this is the method that executes inside the fragment once the service is available.
metronomeServiceIsBound = true;
if (loaded && metronomeService.isMetronomePlaying())
metronomeRunning = true;
}
#Override
public void onServiceDisconnected(ComponentName name)
{
metronomeServiceIsBound = false;
}
};
}

Proper way to access variable from activity within Service

In my activity, there's a variable (objectList) which I would like to access from a Service (TestService):
public class MainActivity extends AppCompatActivity
{
List<MyObject> objectList;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService( new Intent( getBaseContext(), TestService.class )
);
}
And I have a skeleton for the Service:
public class TestService extends Service
{
#Override
public IBinder onBind(Intent intent)
{
return null;
}
#Override
public void onCreate()
{
super.onCreate();
}
#Override
public int onStartCommand( Intent intent, int flags, int startId )
{
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy()
{
super.onDestroy();
}
}
My goal is to loop through every item in the objectList from the TestService every x seconds, process some data, and then update this particular item with new data.
The MyObject class has a lot of properties which I need to update. What is the proper way to pass the objectList from mainActivity to the TestService so I can work with the object list directly? Thanks!
By maintaining a reference to an Activity in a Service, you introduce a memory leak since you prevent the system from garbage collecting the Activity when the view is destroyed as a result of the Activity progressing through its lifecycle.
Instead, you should communicate the changes made by the Service to the Activity using a BroadcastReceiver as explained by #NongthonbamTonthoi in the comment. Basically the Activity should instantiate a BroadcastReceiver that listens for a specific type of broadcasts (identified by a unique key defined by you) which are sent by the Service whenever it performs an update.
Furthermore, I suggest that you move the list so that it is stored in the Service and then make the Activity retrieve the list from the Service by binding to the Service and then invoking a method defined in your IBinder implementation (an instance of which should be returned from onBind(Intent)). This way you can confine all code that makes changes to your model to the Service and keep the Activity as a (dumb) view that simply renders the model. Morover, with this design, you can make your list outlast the Activity by also starting the Service (note: in addition to binding to it) so that you can retain the state of your list even if your Activity is destroyed (e.g., as a result of your application being put to the background). If you choose this design, the broadcast sent by the Service can simply be a notification that the list has changed, and the Activity can then retrieve the updated list by invoking the getList method specified in your IBinder implementation.

binding to running Service (after finish() ) / callback Handler

Again a question about LocalServices. How do I (re-)bind to an existing Service, after onDestroy()?
The Problem:
I'm binding to a Service and Starting the service from an Activity. I'm Posting runnable Objects to the Binder, for a callback (updating a progressbar) on the UI. When I close this Activity, the OS could end the lifecycle and Destroy the Activity, calling onDestroy(), right? I simulate this, calling finish() in onPause() method. So once I restart the Activity, how to I bind to the SAME Service again? I thought that Services are Singelton, but when I'm trying to re-bind, I get another binder reference. So binder.callbackHandler.post(binder.progressHandler); still has the reference to the old binder/callback/progressHandler, not to my new one.
Even the Constructor of the Service is called again!
Is there any solution to have a progressbar, getting updated by callback objects from the service (working). Closing/onDestroy() the Activity. Come back, and continue the progressbar?
My code is quite large, but recreated the Szenario:
public class MyService extends Service {
private final LocalBinder binder = new LocalBinder();
public class LocalBinder extends Binder implements TestRunServiceBinder {
private Handler callbackHandler;
private ServiceStartActivity.RunOnServiceProgress onProgress;
#Override
public void setActivityCallbackHandler(Handler messageHandler) {
callbackHandler = messageHandler;
}
#Override
public void setServiceProgressHandler(RunOnServiceProgress runnable) {
onProgress = runnable;
}
public void doSomething(){
_doSomething();
};
private void _doSomething(){
while(...){
//do this a couple of times (could take up to 10min)
binder.callbackHandler.post(binder.progressHandler);
wait()
}
}
}
_
public class ServiceStartActivity{
private final Handler messageHandler = new Handler();
private ServiceConnection mTestServiceConnection = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
testRunBinder = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
testRunBinder = (TestRunServiceBinder) service;
testRunBinder.setActivityCallbackHandler(messageHandler);
testRunBinder.setServiceProgressHandler(new RunOnServiceProgress());
}
};
#Override
protected void onStart() {
super.onStart();
// bind to the Service
final Intent serviceIntent = new Intent(ServiceStartActivity.this,
MyService.class);
getApplicationContext().bindService(serviceIntent,
mTestServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
getApplicationContext().unbindService(mTestServiceConnection);
}
public class RunOnServiceProgress implements Runnable {
#Override
public void run() {
//do something on the UI!
}
}
}
I got it now. The solution is to explicit call startService(serviceIntent); before you bind to the Service using getApplicationContext().bindService(serviceIntent,mTestServiceConnection, Context.BIND_AUTO_CREATE);
Reason: When you start a Service with bindService(), it becomes a Bound Service an
runs only as long as another application component is bound to it.
If you start a Service with startService() it can
can run in the background indefinitely,
So if you have e.g. a progessbar on the UI, and you want it to continue updating it, you should start your Service, and bind and undbind it in onResume() / onPause(). But be carfull: Since you started the Service manually, You should also stop it manually. The simplest way to do this is call stopSelf() once the Service did it's work.
This soultion covers a proper binding from an Activity with e.g. an progresss bar to the same Service even after the activity is destroyed or after an orientation change.

Cleanly binding/unbinding to a Service in an Application

I have an Android application that is binding to a persistent service (once started with startService()).
The service is an integral part of the application and thus is used in almost every Activity. Hence I want to bind to the service just once (instead of binding/unbinding in every Activity) and keep the binding during the lifetime of my application.
I've extended from Application and bind to the service in Application#onCreate(). However I now have the problem that I don't know when my application exists since Application#onTerminate() is never called, see JavaDoc:
This method is for use in emulated process environments. It will never
be called on a production Android device, where processes are removed
by simply killing them; no user code (including this callback) is
executed when doing so.
So how do I cleanly unbind from a service bound in Application?
I solved this problem by counting the references to the service binding in the Application. Every Activity has to call acquireBinding() in their onCreate() methods and call releaseBinding() in onDestroy(). If the reference counter reaches zero the binding is released.
Here's an example:
class MyApp extends Application {
private final AtomicInteger refCount = new AtomicInteger();
private Binding binding;
#Override
public void onCreate() {
// create service binding here
}
public Binding acquireBinding() {
refCount.incrementAndGet();
return binding;
}
public void releaseBinding() {
if (refCount.get() == 0 || refCount.decrementAndGet() == 0) {
// release binding
}
}
}
// Base Activity for all other Activities
abstract class MyBaseActivity extend Activity {
protected MyApp app;
protected Binding binding;
#Override
public void onCreate(Bundle savedBundleState) {
super.onCreate(savedBundleState);
this.app = (MyApp) getApplication();
this.binding = this.app.acquireBinding();
}
#Override
public void onDestroy() {
super.onDestroy();
this.app.releaseBinding();
}
}
From Sven's answer:
I solved this problem by counting the references to the service
binding in the Application. Every Activity has to call
acquireBinding() in their onCreate() methods and call releaseBinding()
in onDestroy(). If the reference counter reaches zero the binding is
released.
I agree, BUT you shouldn't do it in onDestroy - that will often not get called.
Instead I suggest the following (based on your code sample)...
// Base Activity for all other Activities
abstract class MyBaseActivity extend Activity {
protected MyApp app;
protected Binding binding;
#Override
public void onCreate(Bundle savedBundleState) {
super.onCreate(savedBundleState);
this.app = (MyApp) getApplication();
this.binding = this.app.acquireBinding();
}
#Override
protected void onPause() {
super.onPause();
// Pre-HC, activity is killable after this.
if ((11 > Build.VERSION.SDK_INT) && (isFinishing()))
onFinishing();
}
#Override
protected void onStop() {
super.onStop();
if ((10 < Build.VERSION.SDK_INT) && (isFinishing()))
onFinishing();
}
protected void onFinishing() {
// Do all activity clean-up here.
this.app.releaseBinding();
}
}
BUT, my use of isFinishing() is just a thought - I'm not certain that it is reliable. Perhaps onPause/onStop get called with isFinishing() false, but then the activity gets killed - and your releaseBinding() never gets called.
If you get rid of the isFinishing check I think you need to move the acquireBinding() call from onCreate to onStart/onResume (depending on sdk version), to ensure that your ref count doesn't get messed up.
Who knew that releasing your app's service would be so complicated!
Is unbinding necessary at all in this case? The application gets killed anyway. I tried implementing a sample application doing this without unbinding and it seems to work properly.

Android, start service using thread and warn activity when it's done

I have a service which has a method that downloads an image from an URL and returns an Uri.
That service will get more complex when it has all the intended features. Therefore,
I'm invoking its methods within a thread.
My problem is how to warn the activity that the service has done it's work.
I could change a class isFinished variable but the activity had to be constantly checking
for its value.
I just want the service to tell the activity that it's work is done and the resources are
available for use.
I thought something in the lines of the service calling stopSelf() and the activity was
warned through "onServiceDisconnected" but that didn't seem very "political correct".
Thanks in advance
There are two ways to do it.
1. You can start your activity using by firing an intent.
2. You can Broadcast an intent and write receiver for it in your app when your receiver receives intent and onreceive method is called in this method you can start your activity using intent.
cheers...
public class MyActivity extends Activity{
public MyActivity() {
...
MyThread thread = new MyThread(this);
thread.start();
}
public void onFinishedThread(...) {
}
}
class MyThread extends Thread {
MyActivity activity;
public MyThread(MyActivity activity) {
this.activity = activity;
}
public void run() {
// do work
...
this.activity.onFinishedThread(...);
}
}

Categories

Resources