Using Intent Service in combination with Alarm Manager - java

I have used the Alarm Manager to allow the user to schedule a certain task to repeat at a certain amount of time. In the context of the application, I have an Android game where people are able to schedule when to send their ships. The Alarm Manager is working fine, alarms get kicked off at the right time etc.
What happens when an alarm is fired off (usually every hour and a bit), is that my IntentService will start communicating with the server to send the ships. This action may take 1 minute, but it can last up to 10 minutes. Even this all works fine. Alarms get fired, ships get sent, everyone happy. The problem arises at night, I go to sleep and expect when waking up that my ships have been sent all night long. Nope. Logging & notifications show that the Alarms are fired correctly, but it appears that the IntentService is killed when it's communicating with the server.
Possible cause for this is that I'm not looking at the game every once in a while like I do when I'm awake, and thus keep some form of process running which prevents the IntentService from being garbage collected.
I've already tried a LOT of things to fix this. I used to work with a BroadCastReceiver, I used to spawn ASyncTasks in the IntentService, but I've since refactored my code to not use those things anymore as they're bad practice for Services.
I have also checked this resource: http://techtej.blogspot.com.es/2011/03/android-thread-constructspart-4.html but I'm still not sure if what I'm doing is the correct thing to handle this situation. I have placed some extensive logging for the next night to review in the morning but I'd like you guys' opinion over this.
Oh I'm also requesting a WakeLock for the complete duration of the onHandleIntent function using:
PowerManager pm = (PowerManager) c.getSystemService(Context.POWER_SERVICE);
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "MyApplicationWakeLock");
My IntentService:
public class ScheduledRequestService extends IntentService {
public ScheduledRequestService() {
super("ScheduledRequestService");
}
#Override
public int onStartCommand(Intent intent, int flags, int startID){
Logger.d("OnStartCommand!");
return super.onStartCommand(intent, flags, startID);
}
#Override
protected void onHandleIntent(Intent intent) {
ScheduledRequest request = (ScheduledRequest) intent.getExtras().getSerializable("request");
// This function will start a lot of client <-> server communication
request.onExecute(this, intent);
}
#Override
public void onDestroy(){
Logger.d("OnDestroy!");
super.onDestroy();
}
}
So again, my question is; am I structuring this correctly? Should I use a normal Service instead of an IntentService ? Can my IntentService get garbage collected while it is handling an Intent (I think I read that this is possible)?

Can my IntentService get garbage collected while it is handling an Intent (I think I read that this is possible)?
No, but your process can be terminated.
am I structuring this correctly?
Not if you are trying to use a _WAKEUP alarm. You need to set things up more carefully in that case, and I would recommend either WakefulBroadcastReceiver or my WakefulIntentService to handle that pattern.
Should I use a normal Service instead of an IntentService ?
No, an IntentService will be fine. You may need to consider making it a foreground service using startForeground().

Turns out the error was somewhere else than the garbage collector of Android. I was using cache which eventually led to me 'running out of ships' because on send time, it deducted the sent amount of ships from the pool. When they returned however, they were never added back to the cache. During the day I probably manually refreshed the cache or forced Android to clear it by using my phone otherwise which didn't cause the problem to arise (as much).

Related

Is there any alternative for PeriodicWorkManager, if I would like to run a background work more frequently than 15 minutes?

The PeriodicTimeRequest has a minimum periodic time of 15 minutes. But I see, that for example Google Maps location sharing can update more frequently than that, and facebook messenger can also receive messages almost instantly.
I would like to send a notification to the user, when it got a new message. My application has to work on local network, so Firebase is not an option. I have to send a json request to the server, and if there is a new message, I show a notification to the user.
Regarding FCM:
FCM, which is available in all devices with Google Play takes the weight of subscribing to and receiving push events, under all the resource constraints Android has been ever introducing.
It's tightly coupled with the OS and is unified (one entity, one persistent connection for all apps in your device), which is why it works :)
Regarding Frequency of your Work:
Given your requirement of more frequent pings to the server, you'd need to have a service which runs all the time, i.e. A Foreground Service.
It is resource consuming though, so good luck convincing the user with a good reason why it should stay alive all the time.
I think you've managed to make the client-server interaction possible, since identifying a server in a local network is a huge task in itself.
use this in your service.
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
CountDownTimer timer = new CountDownTimer(15 * 60 * 1000, 1000) {
#Override
public void onTick(long millisUntilFinished) {
// execute your task here, every sec
//if you want increase the count down interval from 1000 to what you want
}
#Override
public void onFinish() {
this.start();
// it will start again.
}
};
timer.start();
return START_STICKY;
}
I am afraid it is not going to be possible without using a set of workarounds. Which means you might not get a consistent behavior.
#Arvind has done a very good job explaining the benefits of a Firebase Service and it is the recommended approach for achieving such task.
First I'd like to point out that such restrictions on the WorkManager exist because Android has been suffering (between other things) of developers trying to abuse some mechanisms to get their software working and at the end of the day, the battery of the users had been suffering from such abuses and since Android 6 Google has started trying to address these issues. There's a good read for you over here about Doze mode and how to work with it
I am pointing this stuff out because I've been trying to build a chat service that wouldn't rely on Firebase and I really don't want you to waste as much time as me banging your head against a wall. There are things that you simply can't fight. That means that if the device enters in a "deep-sleep" mode sometimes you can only accept it.
My approach
Please
keep in mind the user interests and the life of their batteries and try to be as smooth as you can with their devices and this is just a workaround over the restrictions that have been imposed upon us. And that I discourage this approach due to the amount of work that it takes to pull off and for how misused it can be.
My solution
Essentially, to get notified (ie getting your code running) in an Android App you're going to be wanting to receive system events or Broadcasts. This is where you set up a BroadcastReceiver and you get Intents delivered to it and you can act upon them accordingly. BUT YOU HAVE TO BE QUICK BECAUSE YOU HAVE ONLY 10 SECONDS OF RUNTIME BEFORE THE OS KILLS THE PROCESS AGAIN. Ideally you would have a really quick server so you can have very little IO times to ensure you can be within 10 second restriction as frequently as possible.
So essentially you would be using a combination various of services that you would like to be monitoring in order to get notifications (aka Broadcasts) whenever the state of those changes. Here are a few ideas:
WiFi state (which will also be useful to see if you can reach your local server)
Bluetooth Low Energy packets (or Nearby which may solve the entirety of your problem depending on Nearby's capabilities)
WorkManager as you already pointed out.
AlarmManager to schedule a broadcast of intents every so often.
Geofencing (although it involves reading the user's location; you can set really small geofences around the office building and get notified by a Broadacast when users go through that geofence)
So whenever you receive a Broadcast of these sources you would handle such notifications from within the same BroadcastReceiver
From the implementation body of this Broadcast receiver you would poll the local network's server to check whether if your user has new messages or not and lift up a notification. And it's important to keep the amount of work and IO times the app has to do at a minimum since those add up and you've got only 10 seconds.
You can get around the 10 second mark if you launch a ForegroundService. Then, that period of time is going to be extended until a 10 minute mark and you will need a visible notification for the user stating something that you're checking if it's got any new messages.
Keep in mind
Don't stress the user's battery too much. Or Android will penalise your app and you'll end up notified less often or even completely not notified.
Be gentle with the user. If the user has to force-kill your app at some point it will stop receiving any sort of Broadcasts or running any sort of WorkTasks.
This solution can behave differently accross devices. Since the decisions of notifying your app are made by the OS, different OS (redmi, samsung, meizu...) you are likely to not end up with a consistent behavior across all devices
You don't have control over things, the OS does
Within measure, try to time your Broadcasts to your BroadcastReceiver within spans of 3 minutes or so; so you are always receiving a Broadcast below the 15 minute mark.

onTaskRemoved not getting called if the activity started by the service is removed

I am using oreo 8.1.0.
I am facing a weird problem whose solution I can't find on stackoverflow, hence I am writing this question. I know one solution that through foreground service, I can implement it but I don't find notification user friendly in my application context.
I will describe my problem using two cases.
Case 1:
When user opens my app by clicking on the icon and removes it from recent apps, then service automatically restarts. This is fine.
Case 2:
Here my app is closed and is not in recent apps.
When user copies a text, then my service starts one of the activity of my app but when he removes it from the recent apps, then my service gets stopped permanently.
So my problem lies in second case,I don't want my service to get killed. Even if it gets killed I want it to restart.
I tried all the methods mentioned on the stackoverflow like using START_STICKY and onTaskRemoved but I am not able to make it work.
Even I tried killing my activity whenever user clicks on on recent app button and remove it from the recent apps programmatically but this also did not work.
Though this restarts the service even in second case when user kills my app using the back button.
This part of the code is from the activity which opens when user copies some text.
#Override
public void finish() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
super.finishAndRemoveTask();
} else {
super.finish();
}
}
This part of the code is from the service that starts the activity.
#Override
public void onTaskRemoved(Intent rootIntent){
Log.d("testing 12","onTaskRemoved");
Intent restartServiceTask = new Intent(getApplicationContext(),CBWatcherService.class);
restartServiceTask.setPackage(getPackageName());
PendingIntent restartPendingIntent =PendingIntent.getService(getApplicationContext(), 1,restartServiceTask, PendingIntent.FLAG_ONE_SHOT);
AlarmManager myAlarmService = (AlarmManager) getApplicationContext().getSystemService(this.ALARM_SERVICE);
myAlarmService.set(
AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + 1000,
restartPendingIntent);
super.onTaskRemoved(rootIntent);
}
#Override
public int onStartCommand(Intent intent,int flag,int startId){
super.onStartCommand(intent, flag, startId);
return START_STICKY;
}
EDIT 1
I just need the service to remain alive.Here are the things that will not hinder the user experience in my app case
1. Killing the activity by yourself programitically when onPause is called so that service does not get killed is acceptable if you make it happen
2. Showing the notification for few second is acceptable
3. Restarting the service is acceptable
From Android Oreo there is a paradigm shift to limit background service execution. It was a frequent criticism affecting battery life, performance and security. As posted in the comments above there are alternative approaches such as JobScheduler.
Refactoring services to the JobScheduler pattern can be quite involved. I advise you look into changing your IntentService to a JobintentService from the Android support v4 library. It uses JobScheduler on Oreo and later targets but reverts to the older IntentService design on older devices. You just need to replace your override of onHandleIntent with onHandleWork in your Service implementation.
Add
android:permission="android.permission.BIND_JOB_SERVICE"
to the service declaration in your AndroidManifest.xml. It can also be useful to add
<uses-permission android:name=”android.permission.WAKE_LOCK” />
But, saying all that, reading through your question, it seems to me your basic complaint is with the cosmetic issue of foreground services requiring a notification. In my view, your solution is either to simply use Context.startForegroundService, or make sure your service is bound. Either way there is visible content to the user that the service is running. You will find it an uphill struggle to try to subvert Android design decisions; better to embrace it - they are wanting you to show these notifications from now on.
Anyway updating your code for newer Android targets is still good practice even if you can't completely avoid those notification icons.
This was the problem
Whenever my service used to start the activity, my service used to get destroyed after the activity used to get started.
Why onTaskRemoved was not working?
The thing I was doing was not working because my service used to get destroyed and hence it was not active to listen to onTaskRemoved.
How I solved it?
The solution to this problem was putting a check in the activity(started by service) to check whether the service is alive or not. As in my case service was getting destroyed. Hence I need to start the service again if the service is not alive.
It even fits the OREO design pattern as we can start the background service when the app is in foreground and the service will stay alive even if the activity gets destroyed.
On Oreo though after sometime service gets destroyed but this is a different problem.
More Info

Sleep VS alarmManager.set VS alarmManager.setRepeat VS Handler

I'm looking for the most efficient way to perform a simple task. As a new android developer, I'm not too sure which of these strategies would be best for my app in terms of memory efficiency. I imagine some of these approaches might cause threading issues that I'm unaware of.
All three solutions are currently behaving as desired.
It's a really simple app. The idea is that my MainActivity starts an IntentService which will be running in the background after the app is opened. All the functionality I need right now is for a notification to be created at random intervals throughout the day(about an hour apart), indefinitely, until stopped by the user. The notification is made in a simple void method, displaying notification as text and vibrating the phone once.
My MainActivity starts the IntentService:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, NotificationService.class);
startService(intent);
}
}
My IntentService is very simple. It is called NotificationService, extends IntentService, and only overrides the onHandleIntent method. The constructor is empty except for super("Service"). The question comes in how to cause the notifications to pop up throughout the day in the background in the most efficient way. This in my implementation is done in the onHandleIntent method for all three methods.
Method One:
#Override
protected void onHandleIntent(Intent intent) {
makeNotification();
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent pintent = PendingIntent.getService(
getApplicationContext(), 0, intent, 0);
alarm.cancel(pintent);
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()
+ 60000 * 60, pintent);
}
Note that with this, the user would have to uninstall the app to get the notifications to stop, which is not desirable (although I think I could just add a button or something that would cancel the intent)
Method Two:
protected void onHandleIntent(Intent intent) {
makeNotification();
AlarmManager alarm = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
PendingIntent pintent = PendingIntent.getService(
getApplicationContext(), 0, intent, 0);
alarm.cancel(pintent);
alarm.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()
+ 60*1000, 60000*60 ,pintent);
}
Method three:
#Override
protected void onHandleIntent(Intent intent) {
makeNotification();
try {
sleep(60000 * 60);
startService(intent);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Can someone please help me with deciding the pros/cons of these three methods? Im not sure I understand which one would be ideal, although they all three give me proper functionality. As a side note, in my research I've noticed a "Handler" class which may also be useful here.
All the functionality I need right now is for a notification to be created at random intervals throughout the day(about an hour apart), indefinitely, until stopped by the user.
The AlarmManager, and potentially JobScheduler, are the only viable options.
The idea is that my MainActivity starts an IntentService which will be running in the background after the app is opened
Not really. An IntentService will only be running for as long as it takes to complete onHandleIntent() (including executing it N times if N commands are sent to it in rapid succession). An IntentService can run for a little while, but it is designed to handle some sort of business logic transaction. It is not designed to run indefinitely, and doing so would be bad for the user anyway.
Can someone please help me with deciding the pros/cons of these three methods?
Option three is unusable. First, it will not be reliable, as it stops working once your process is terminated. Second, it ties up a hunk of system RAM for no good reason, RAM that the user could put to more productive use. Only have a service running when it is actively delivering value to the user. Watching the clock tick is not actively delivering value to the user.
I've noticed a "Handler" class which may also be useful here
No, as it will suffer from the same problems as option three.
With regards to your two AlarmManager options, it boils down to whether you want regularly-occurring alarms (setRepeating()) or irregularly-occurring alarms (set()).
If you go the setRepeating() option, move the AlarmManager code out of the service and into the activity. There is no point — and definite costs — to calling setRepeating() on every alarm. After all, the point behind setRepeating() is that it knows to repeat, so you do not need to tell it on every occurrence "oh, hey, I know that I told you the last 1,337 times that you should repeat, but, um, don't forget to repeat, m'kay?".
With the set() option, since you are specifically not asking for the alarms to repeat, you would continue to schedule them in the service (or perhaps once from the activity, then the rest from the service), more or less as you have it.

Timing for Android GPS logging is inaccurate

In my app I am trying to get the users current location and log it. The user can select an interval. Right now the logging is not happening consistently. Sometimes the logs are just off by a couple seconds and sometimes they are off by a few hours. Also if it is not logging and you turn the GPS off/on then it will start working again. The entire app is based around the idea that you will only be able to get your location using GPS when you use it. I have a few theories as to why this may be.
I am getting the logs by using AlarmManager setExact inside a broadcast receiver. I know that this is not guaranteed to be perfectly accurate and could possible account for a few seconds here and there.
I also know that the GPS can take some time to acquire. Is there a normal range for this time. I could see this taking up to a few minutes possibly but several hours seems like a lot.
I don't know a lot about loopers and am having some difficulty understanding them. I was wondering if the looper in the requestSingleUpdate could have anything to do with it
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, Looper.myLooper());
I know loopers process tasks in the background using a queue and I was't sure if other tasks could get stuck on the queue in front of it causing delays.
My last theory is that it has something to do with not timing out while searching for the GPS signal. If I am searching for a signal every 10 seconds (the fastest allowed) but the phone cannot find a signal the AlarmManager will fire again and I will have two services trying to get a signal. I don't really understand how the services work under the hood so I don't know if this is a possibility or not.
If anyone has any ideas/resources and could point me in the right direction I would really appreciate it.
Here is the code. I can include more if that would be helpful.
#Override
public void onReceive(Context context, Intent intent) {
SharedPreferences pref = context.getSharedPreferences(SettingsActivity.PREFERENCES, Context.MODE_PRIVATE);
if(!pref.getBoolean(SettingsActivity.ARG_TRACK, true)){
return;
}
alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent alarmIntent = new Intent(context.getApplicationContext(), AlarmReceiver.class);
pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0);
long interval = pref.getLong(SettingsActivity.ARG_TRACKER_INTERVAL, 15000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmMgr.setExact(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()+interval, pendingIntent);
}else{
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), interval, pendingIntent);
}
context.startService(new Intent(context.getApplicationContext(), AlarmService.class));
}
Inside my service I am calling LocationManager requestSingleUpdate()
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, Looper.myLooper());
In my app I am trying to get the users current location and log it. The user can select an interval. Right now the logging is not happening consistently. Sometimes the logs are just off by a couple seconds and sometimes they are off by a few hours. Also if it is not logging and you turn the GPS off/on then it will start working again. The entire app is based around the idea that you will only be able to get your location using GPS when you use it. I have a few theories as to why this may be.
If i'm not wroing, in your scenario, the interval is not period of location provider. It's directly period of alarm. The story like that
User selects an interval(period), for example 10 minutes.
After 10 minutes(approximately), device wakes up and makes location request to receive single location.
In that case you are facing the gps provider's lag. So, each time when wake up, you are waiting the gps provider to be warm. This is why the logs aren't consistently.
I am getting the logs by using AlarmManager setExact inside a broadcast receiver. I know that this is not guaranteed to be perfectly accurate and could possible account for a few seconds here and there.
You are using exact settings for wake up. This is not actual reason of long lag.
I also know that the GPS can take some time to acquire. Is there a normal range for this time. I could see this taking up to a few minutes possibly but several hours seems like a lot.
You have to wait until GPS provider to be warm. This warming time could change by where you stay at this moment. If you are inside a building, takes long
I don't know a lot about loopers and am having some difficulty understanding them. I was wondering if the looper in the requestSingleUpdate could have anything to do with it
Simply, when you pass a thread's looper here, onLocationChanged() method will be used the looper. But you are already making single request. No more update will be fired. (In your scenario, each wake up is one single location request)
My last theory is that it has something to do with not timing out while searching for the GPS signal. If I am searching for a signal every 10 seconds (the fastest allowed) but the phone cannot find a signal the AlarmManager will fire again and I will have two services trying to get a signal. I don't really understand how the services work under the hood so I don't know if this is a possibility or not.
This is a problem of your scenario. If you set alarm with short period, it's so normal the next wake up could happen. You can follow below things
Due to wait until gps warm, make long alarm period at least 5 minutes
Make timeout scenario, for example wait 1 minute or more until receiving location.
If cant receive location in time, shutdown everything and wait the next wakeup.

Run service every 30 minutes without using wakelocks

tl;dr: my app doesn't update my SharedPreferences with the last value from the server because sometimes the phone is sleeping.
Explanation:
I'm new in this android world, and I've been working on my first "real" application but I'm having a few issues right know...
Here's how I need my application to work:
Check every 30 minutes for new data (data is pulled from a json file on my server)
If the data is different from the stored on the phone, then store the new data and send a notification to the user. (I'm using SharedPreferences for the "storage")
Here's what I used to try to achieve that:
MainActivity.java / Activity (my main activity, duh')
cargarJson.java / AsyncTask (a class that pulls json data and returns a jsonobject). Cargar = Load (in spanish)
DolarService.java / IntentService (The service creates a loadJson object and execute it, then sends the notification).
AlarmReceiver.java / BroadcastReceiver (It's a receiver that has an AlarmManager that executes my DolarService every 30 minutes and runs everytime the phone boot up and when the user runs the app for the first time).
I kind of managed to do it, but the problem is that I don't want to use wakelocks because it drains the battery really fast.
My AlarmManager doesn't use wakelocks so when it sends the notification every 30 minutes it contains the last value stored in the SharedPreferences when the phone was "awake" and not the new data from the server.
Here's my source:
https://gist.github.com/anonymous/d5cccf5ba331f041630e
My question: how do I keep my up-to-date data in my SharedPreferences (updating it every 30 minutes) without using wakelocks. Also, is something wrong with my approach to do this app ?
Thank you in advance and sorry for my poor english, I tried my best to explain my situation!
It doesn't look like you're using wake locks. You can make sure of this, however, by removing your <uses-permission android:name="android.permission.WAKE_LOCK" /> from your manifest, and then you'll know for certain that your application isn't acquiring a wakelock, because it doesn't have permission.
Edit:
You're using your IntentService incorrectly. See the documentation on how to implement an IntentService. An IntentService already creates it's own worker thread, similar to an AsyncTask, so executing an AsyncTask within an intent service is unnecessary in your case. Instead, you should pull out everything in your AsyncTask and implement it in your IntentService's onHandleIntent(), rather than it's onCreate() and onDestroy() methods.
The other option would be to use a normal Service, and stop it in your AsyncTask's onPostExecute() method.
Also, this is a little off topic, but when passing a context to another object, you should use a WeakReference<T>:
WeakReference<Context> context;
public cargarJson(String jsonUrl, Context context) {
this.jsonUrl = jsonUrl;
this.context = new WeakReference<Context>(context);
}
All things are fine. But its good if you use a backgorund service for your server operations and start this service through the AlarmManager followed by PendingIntent in every 30 minutes.

Categories

Resources