Communication between service and fragments - android

I have a PageView with multiple fragments and a bind Service. I connect to the service from each fragment the following way:
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
...
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
...
}
};
The problem is: since it takes a short interval to connect to the service each time the PageView is scrolled, there's a slight lag.
Ideally I would like to bind the Activity to the service and pass the service reference to the fragments.
How can I access the Service reference from fragment through activity? Thank you.

Related

Properly unbind service from activity

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.

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;
}
};
}

How do you call a service method from a class?

I have a service, and within this service I have created a class Client that implements Runnable. I call this client in the onCreate of my service using
clientThread = new Thread(new Client());
clientThread.start();
In the client class, I have a long running operation, and have some data that I would like to print to my Activity. In my service, I have a method (sendToUI) that sends data to the Activity, and the Activity uses a handler to receive the data.
Now my question is, how can my Client class use the method(sendToUI), which is in my service, to feed it's data into my Activity?
Thanks in advance for any help.
Update: I did some reading, and found a simple way (in my view) that solved my problem. Here is the process I used.
I added a global variable to my Client class, which I updated constantly in the run() method. I then added a method (getValue) to my Client class, which returned the global variable.
I changed my code to
Client clientthread = new Client();
new Thread(clientthread).start();
in order to start the thread. I then used
int value = clientthread.getValue();
in order to retrieve the current value of the global variable in my Client class. I then called my sendToUi method with the value as its parameter.
You can bind to that service from your activity with a binder that returns the instance of the service. then you can simply invoke whatever method you want with that service instance.
The only problem with this approach is that the binding is done asynchronously, and for a lot of use cases thats going to be a pain! unfourtunately i am not aware of any better approach.
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);
}
}
public class BindingActivity extends Activity {
LocalService 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 LocalService
Intent intent = new Intent(this, LocalService.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 the LocalService.
// 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 LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
see this for more info.
You need to call the UI Thread, using the method runOnUiThread like following:
runOnUiThread(new Runnable() {
public void run() {
// ... Excecute the code for the Activity here
}
});
Put this code into your class.
I hope it's useful for you.
You should notify your Activity class that some data has to be updated on the UI. You shouldn't update the UI from that class. You may create more problems than solving.
You can either use a Broascast Receiver or, a much simplier solution, use EventBus framework that easily allows you to notify and send data from one thread to another.
Using EventBus takes four simple steps:
Implement any number of event handling methods in the subscriber:
public void onEvent(AnyEventType event) {}
Register subscribers:
eventBus.register(this);
Post events to the bus:
eventBus.post(event);
Unregister subscriber:
eventBus.unregister(this);

How to use threads and services. Android

I have three classes. "actone", "acttwo" and "actthree". I have a button in "actone". When I click that button, I want to be able to run "acttwo" on a different thread in the background, while my UI takes me to "actthree" and I can do whatever I want there while the code in "acttwo" keeps executing(I'll be doing uploading to a server in "acttwo" that is why I want it to keep running in the background).
if(v.getId() == R.id.button1){
//Start "acttwo" in background on another thread.
Intent i= new Intent(actone.this, actthree.class);
startActivity(i);
}
How do I do that? Do I use a service? If yes, then what's the procedure? How to do that? I'm a newbie at Android. Please help. Thanks!
There are two ways to do this, use a Singleton or use a Service (as you mentioned). Personally I don't like the singleton patterns very much and a service follows the Android patter much better. You will want to use a bound Service which is bound to your Applications context (actone.getActivityContext()). I have written a similar answer to this question however you will want to do something like:
public class BoundService extends Service {
private final BackgroundBinder _binder = new BackgroundBinder();
//Binding to the Application context means that it will be destroyed (unbound) with the app
public IBinder onBind(Intent intent) {
return _binder;
}
//TODO: create your methods that you need here (or link actTwo)
// Making sure to call it on a separate thread with AsyncTask or Thread
public class BackgroundBinder extends Binder {
public BoundService getService() {
return BoundService.this;
}
}
}
Then from your actone (I'm assuming Activity)
public class actone extends Activity {
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Intent intent = new Intent(this, BoundService.class);
bindService(intent, _serviceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection _serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
BoundService.BackgroundBinder binder = (BoundService.BackgroundBinder)service;
_boundService = binder.getService();
_isBound = true;
//Any other setup you want to call. ex.
//_boundService.methodName();
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
_isBound = false;
}
};
}
Then from ActOne and ActThree (Activities?) you can get the bound service and call methods from actTwo.
You can use a AsyncTask for that. Services are not really useful (much more to code).

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.

Categories

Resources