Proper way to access variable from activity within Service - android

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.

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.

Android - Display notification when application is in background

I am using AlarmManager to periodically check for new content at some endpoint, validate if the results coming from the endpoint are the same as the ones I already have on my app, and if its not the same create a notification for each item.
What i need to know is how should i make the alarms to start only when the application is paused or stopped and cancel the alarms when de application is started or resumed.
where should i start the alarms and where should i cancel them?
In Android Notifications Guideline it says (on chapter: When not to display a notification):
Don't create a notification if the relevant new information is currently on screen. Instead, use the UI of the application itself to notify the user of new information directly in context. For instance, a chat application should not create system notifications while the user is actively chatting with another user.
If I have the application open i just want to disable alarms, when the application is closed/paused i want to cancel everything.
You need to create a Custom Application with global state and implement your own onPause and onResume at Application Level.
Create your own subclass of Application like this:
public class MyApplication extends Application {
private static MyApplication sInstance;
public MyApplication getInstance(){
return sInstance;
}
#Override
public void onCreate() {
super.onCreate();
sInstance = this;
}
public void onStart() {
// TODO: Stop your notification.
}
public void onStop() {
// TODO: Start your notification.
}
}
Specify its name in your AndroidManifest.xml's tag:
<application
android:icon="#drawable/icon"
android:label="#string/app_name"
android:name="MyApplication">
Create a class to hold the counts of activities:
public class ActiveActivitiesTracker {
private static int sActiveActivities = 0;
public static void activityStarted()
{
if (sActiveActivities == 0) {
// TODO: Here is presumably "application level" resume
MyApplication.getInstance().onStart();
}
sActiveActivities++;
}
public static void activityStopped()
{
sActiveActivities--;
if (sActiveActivities == 0) {
// TODO: Here is presumably "application level" pause
MyApplication.getInstance().onStop();
}
}
}
Then create a base activity (or do that in every activity), simply call the activityStarted() and activityStopped() methods:
#Override
public void onStart() {
super.onStart();
ActiveActivitiesTracker.activityStarted();
}
#Override
public void onStop() {
super.onStop();
ActiveActivitiesTracker.activityStopped();
}
For more details about Custom Application, see this.
For more details about Android Application Level Pause and Resume, see this.
Hope this helps.
you can try using a service and override in it , the onTrimMemory method and show the notificaton when "level" is equal to TRIM_MEMORY_UI_HIDDEN
#Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
break;
}
}
check the documentation for more info
http://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_UI_HIDDEN
I am not sure that this will be feasible within your project or if it will achieve what you hope to, however you could extend all of your activities from one base activity. In that base activity's onPause/onStop/onDestroy methods start the alarms and in that base activities onCreate/onStart methods cancel the alarms with the pending intent.
This will provide you with a set location from which you can handle your alarms if you have multiple activities from which the app may close.
You can learn more about the life cycle of activities here.

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.

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