Why do Android bound services load after Fragments? - android

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

Related

Communication between service and fragments

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.

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).

Wait with operation until Fragment's Views are created

I am programming a Music Player and the Music plays in a background Service. When the user kills the Activity which hosts 3 Fragments, and then restarts the Activity again, I send a Broadcast from the Service that contains information about the current playing song, and the list of songs that the user added to his session.
The problem is, every time I want to set the last information into the Fragments nothing happens because their creation takes too long, and the Broadcast doesn't get handled like they should.
How can I let the Service or the Broadcast wait until the Fragments are created so they are handled appropriately?
Here are the relevant code snippets:
//When the activity binds to the service, refresh the fragments
private ServiceConnection conn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
LocalBinder binder = (LocalBinder) service;
myService = binder.getService();
myService.setBound(true);
if(myService.startedOnce) {
myService.refreshFragments();
}
myService.startedOnce = true;
}
}
//This is the broadcast receiver of the Fragment
//it receives the broadcast too soon, and I can't set the
//Views so they are always empty.
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(MusicService.REFRESH_ALL)) {
Song current = intent.getParcelableExtra("song");
setCurrentSong(current);
}
}
The easiest thing to do would simply be hold on to the information until the Fragment is ready to display it. Use the Fragment's setArguments() method to attach the information into the Fragment.
#Override
public void onReceive() {
String action = intent.getAction();
if(action.equals(MusicService.REFRESH_ALL)) {
// Creating a new Bundle so the Fragment can control its own object
Bundle args = new Bundle(intent.getExtras());
Fragment fr = getUsingFragment();
fr.setArguments(fr);
}
}
Then, in the Fragment's onCreateView() simply pull the arguments from getArguments() and build the view with the values.
#Override
public void onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle args = getArguments();
if(args != null) {
// code to set values if arguments are set
} else {
// code to set values if arguments are not set
}
}
Another way to do it would be to use setter methods in which the Fragment itself puts values into a Bundle for setArguments(). That way, you can update the views whenever the View has been created on top of setting the arguments for the possible event when the Fragment's View is destroyed and must be recreated.
Note: You can only call setArguments() before the Fragment has been attached to the Activity. You can however update the Bundle that you pass in by setArguments by retrieving a reference to it from getArguments(), then simply putting in the values. So instead of calling setArguments() from your receiver, do something like this:
public void setCurrentSong(Song extra) {
Bundle args = getArguments();
args.putParcable(KEY_MAP, extra);
if(/* Views are created */) {
// update and invalidate the views
}
}
How I fixed this
As I was using a Service for Media Playback, I wanted to bring up last listened songs from the service so I could directly play it. This was old logic, but I actually built my code around it. Until thusfar I bumped into it.
This was happening
FragmentActivity is created
Service gets started and bound to
Meanwhile the Fragments get created asynchronously
As soon as the Service starts, it sends out a Broadcast with latest information
Because the both the Service and the Fragment creations are asynchronous, the broadcast would be sent from the service, but because the BroadcastReceivers in the Fragments weren't even initialized yet, they would not receive the Intent.
What I did to fix it
I somehow had to use a callback that made sure that
the Service was created and bound to
the fragments created and views are set
So I used the ServiceConnection and to be precise, the onServiceConnected() method. There I got the preferences in which the last song was saved, and then send out the Broadcast and the Fragments received it and the Views were appropiately set. This also worked for orientation changes.
The code
//This is the code in the FragmentActivity
private ServiceConnection conn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
LocalBinder binder = (LocalBinder) service;
myService = binder.getService();
myService.setBound(true);
if (myService.startedOnce) {
myService.refreshFragments();
} else {
sendLastSavedSong();
}
myService.startedOnce = true;
}
public void onServiceDisconnected(ComponentName className) {
myService.setBound(false);
}
};
Can't you do something in the fragment like
Song current=null;
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(MusicService.REFRESH_ALL)) {
current = intent.getParcelableExtra("song");
}
}
#Override
public void onResume()
{
if(current!=null) setCurrentSong(current);
super.onResume();
}
my solution was just creating your own callback interfaces. At the very end of onCreateView method of ur fragment just call your callback method, which tells ur mainactivity that the creation is done.
It worked for me, hope helps u too.

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