I'm developing a test app to learn Android in which I need to fire up a service periodically to update some data. I'm using the AlarmManager with a BroadcastReceiver to set the alarm and it successfully updates the data with the default interval but I'd like to have this interval as a user defined value.
I currently have the following code to register the alarm:
In the manifest:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<receiver android:name=".BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
The receiver:
public class BootReceiver extends BroadcastReceiver {
private static final String TAG = BootReceiver.class.getSimpleName();
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG,"onReceive method");
AlarmCreator.setAlarm(context, intent);
}
}
The actual alarm:
public static void setAlarm(Context context, Intent intent){
Log.d(TAG, "Setting alarm");
// I need the context here
context.startService(new Intent(context, RefreshService.class));
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
long interval = Long.parseLong(prefs.getString("interval",
Long.toString(DEFAULT_INTERVAL)));
// Here
PendingIntent operation = PendingIntent.getService(context, -1,
new Intent(context, RefreshService.class),
PendingIntent.FLAG_UPDATE_CURRENT);
// And here
AlarmManager alarmManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
if (interval == 0) {
alarmManager.cancel(operation);
Log.d(TAG, "Cancelling alarm");
} else {
alarmManager.setInexactRepeating(AlarmManager.RTC,
System.currentTimeMillis(), interval, operation);
Log.d(TAG, "Setting alarm with interval: " + interval);
}
}
What I've tried
I know that using the onSharedPreferenceChanged I can execute code when a preference is updated, the problem is that in order to set the alarm I need to have a Context which the Preference class doesn't provide. I am aware of getActivity().getApplicationContext() the problem is that they can return null depending on the life cycle of the Preference activity.
public class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener{
private SharedPreferences prefs;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
}
#Override
public void onStart() {
super.onStart();
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
prefs.registerOnSharedPreferenceChangeListener(this);
}
#Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals("interval")){
// Since I'm not using the intent I can just simply pass null.
AlarmCreator.setAlarm(??,null);
}
}
}
The question
What should I pass to AlarmCreator.setAlarm(Content,intent) so that the alarm gets correctly canceled or updated? Is it even possible? Maybe there's a better approach that I'm missing.
Additional info
I just started learning Android development but I've spent a good amount of time reading the documentation and I just couldn't figure out how to do it.
There was a similar question which helped to get me on track but I found the answer too vague for my understanding at the moment.
Any Ideas would be appreciated.
Cheers.
It's been a while since I posted this question and now I feel stupid because the answer was obvious.
Call getActivity() and pass that for the context.
You can call getActivity() in any fragment or this if you need the context inside an Activity.
Keep in mind that the kind of context you pass matters, you can get different results if you use the activity's context instead of the application context (getActivity().getApplicationContext()) in certains situations. Check out this link to learn more.
Related
I'm developing an app that need to do some check in the server every certain amount of time. The check consist in verify if there are some notification to display. To reach that goal I implemented Service, Alarm Manager and Broadcast Reciever. This is the code that I'm using so far:
public class MainActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
setRecurringAlarm(this);
}
/**
*
* #param context
*/
private void setRecurringAlarm(Context context) {
Calendar updateTime = Calendar.getInstance();
Intent downloader = new Intent(context, MyStartServiceReceiver.class);
downloader.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, downloader, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, updateTime.getTimeInMillis(), 60000, pendingIntent);
}
...
}
Receiver class
public class MyStartServiceReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent dailyUpdater = new Intent(context, MyService.class);
context.startService(dailyUpdater);
Log.e("AlarmReceiver", "Called context.startService from AlarmReceiver.onReceive");
}
}
Service class
public class MyService extends IntentService {
public MyService() {
super("MyServiceName");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.e("MyService", "Service running!");
// TODO Do the hard work here
this.sendNotification(this);
}
private void sendNotification(Context context) {
// TODO Manage notifications here
}
}
Manifest.xml
<!--SERVICE AND BROADCAST RECEIVER-->
<service
android:name=".MyService"
android:exported="false"/>
<receiver
android:name=".MyStartServiceReceiver"
android:process=":remote"/>
The code works fine, the task in the service will be excecuted periodically. The problem is that the service is destroyed when the app is forced to close. I need to keep alive the service, capable to execute the task, even if the user has closed the app, so the user can be updated via notifications. Thank you for your time!
You can't. If the app is forced closed, that means either its crashed (in which case the service has to be stopped as it may no longer work correctly) or the user force closed it in which case the user wants the app to stop- which means the user doesn't want the service to run. Allowing a service to be automatically restarted even if the user stops it would be basically writing malware into the OS.
In fact, Android went the exact opposite (and correct) way- if the user force stops an app, NOTHING of the app can run until the user runs it again by hand.
You may go through this. I hope this will solve your problem. If you want to keep awake your service it is practically not possible to restart the app which is forced close. So if you disable force stop your problem may be solved.
According to these examples: here and here, I was trying to create Service which starts periodically.
First I created Service:
public class MonitorService extends IntentService {
private static final String TAG = "MonitorService";
public MonitorService() {
super(TAG);
}
#Override
protected void onHandleIntent(Intent intent) {
Log.d("TAG", "Service method was fired.");
}
}
Next I created Receiver:
public class MyReceiver extends BroadcastReceiver {
private static final String TAG = "MyReceiver";
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "MyReceiver on receive");
Intent i = new Intent(context, MonitorService.class);
context.startService(i);
}
}
I added starting method for this in MainActivity:
public void scheduleAlarm() {
Intent intent = new Intent(getApplicationContext(), MyReceiver.class);
final PendingIntent pIntent = PendingIntent.getBroadcast(this, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
long firstMillis = System.currentTimeMillis();
AlarmManager alarm = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
// 1s is only for testing
alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis, 1000, pIntent);
}
which is calling of course in onCreate method.
And I didn't forget to change AndroidManifest:
<receiver
android:name=".MyReceiver"
android:process=":remote" >
</receiver>
<service
android:name=".MonitorService"
android:exported="false" />
And unfortunately the result is that nothing happens in my logs.
So I have two questions.
QUESTION
How to solve issue with not starting service?
If I add scheduleAlarm method to onCreate it will be calling every time I start my application, what is the best way to start this method only for the first time application is started?
EDIT
According to #Lasse hints, I started debugging, and realized that Log.d is not working, when I changed it to Log.i, information from MonitorService was logged.
But... debugging is not stoping on breaking point in MyReceiver, and changing Log.d to Log.i there didn't help. Of course MonitorService is firing, weird thing.
Also time with 1000 ms results in firing service every minute, maybe it's minimum time, and changing to AlarmManager.INTERVAL now doesn't matter.
EDIT 2
Finally I'm getting logs from both service and receiver. I had tried many times and after that it is working, but I don't know why.
But with that another problem has appeared - I'm getting warning when my Service is running
W/art: Suspending all threads took: 21.787ms
I thought that Service is running background so it doesn't matter how long it is, should I concern about this warning?
Edited
Regarding the first question :
See this from the developer website
setInexactRepeating(), you have to use one of the AlarmManager interval constants--in this case, AlarmManager.INTERVAL_DAY.
So change your 1000 to use of of the constans
Regarding your other question you could override the application object and start it there. This way it is only called when launching the app.
At point A in my application I start my service and expect the service get closed from point B. However, there might be few scenarios that point B doesn't ask service to get closed. In this case I want the service close itself after fixed amount of time.
I have written following code into my Service class and expect the service gets closed after 10 seconds from launch time (It will be 45min in the future but I don't want to stay that long for test).
public class ChatService extends Service implements ITCPConnection
{
private static final int SERVICE_LIFE_TIME = 10 * 1000; // In millis
private AlarmReceiver mAlarmReceiver;
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
#Override
public void onCreate()
{
super.onCreate();
//
mAlarmReceiver = new AlarmReceiver();
registerReceiver(mAlarmReceiver, new IntentFilter());
//
Intent intent = new Intent(this, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarmMgr.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + SERVICE_LIFE_TIME, alarmIntent);
}
#Override
public void onDestroy()
{
super.onDestroy();
Log.e(TAG, "onDestroy()");
// Unregister receiver
if (mAlarmReceiver != null)
{
unregisterReceiver(mAlarmReceiver);
}
disconnect();
}
public void disconnect()
{
// If the alarm has been set, cancel it.
if (alarmMgr!= null)
{
alarmMgr.cancel(alarmIntent);
}
...
Log.e(TAG, "disconnect()");
}
/*****************
* Alarm Receiver
*****************/
private static class AlarmReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Log.e(TAG, "Stop service from AlarmReceiver");
context.stopService(intent);
}
}
}
My problem is AlarmReceiver.onReceive() never gets called and therefore my service will be alive indefinitely.
What you are trying to do is to targeting a broadcast receiver explicitly.
According to this, it cannot be done over a dinamically created (i.e. not declared into the manifest) broadcast receiver, because the os would not know how to resolve it.
To check if this is the root of the problem, you can go with the implicit way and set an action inside the intent and by filtering it in the IntentFilter.
Anyway, using the post delayed can be seen as a valid alternative, since you expect the service to be shut down naturally or still be around to intercept the delayed event.
Another (unrelated) thing is that you are calling
context.stopService(intent);
by using the broadcast intent and not the intent that started the service. You could simply call stopSelf().
I am trying to execute an action once at a later time using AlarmManager. I followed the code and the question here and came up with this.
public class EmailAccountUpdater extends BroadcastReceiver
{
// Constructors
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION))
{
Log.v("Test", " Step 1 - Creating the alarm " );
// Place holder
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent newIntent = new Intent("com.test.EMAIL_ACCOUNTS_CHANGED");
PendingIntent pendingIntent = PendingIntent.getBroadcast( context, 0, newIntent, PendingIntent.FLAG_CANCEL_CURRENT);
alarmManager.set( AlarmManager.RTC_WAKEUP, 35000, pendingIntent);
}
}
}
AlarmReceiver.java
public class AlarmReceiver extends BroadcastReceiver
{
// constructors
#Override
public void onReceive(Context context, Intent intent)
{
Log.v("Test","Step 2 - Alarm received");
if (intent.getAction().equals("com.test.EMAIL_ACCOUNTS_CHANGED"))
{
onAccountsUpdated();
}
}
public void onAccountsUpdated()
{
// do something
}
}
In the manifestManifest.xml
<receiver android:name="full.path.AlarmReceiver">
<intent-filter>
<action android:name="com.test.EMAIL_ACCOUNTS_CHANGED"/>
</intent-filter>
</receiver>
Basically what I wanted to do was to put the following in Placeholder (just below the first log statement).
Thread.sleep(35000);
onAccountsUpdated();
But according to this, it is not suggestible to use postDelayed and Thread.sleep in BroadcastReceiver. So I came up with this. What happens is I always get the Step 1 but never reach the step 2. What I am I doing wrong? Any help would be welcome.
The solution is (as per the thread you linked):
you want something to happen some time after the broadcast you can start a service, and that service wait the amount of time, or if the amount of time you want to wait is longer than a few seconds, you should just put the launch of this service in the AlarmManager and let the AlarmManager launch the service for you.
Your plan doesn't work because the context is destroyed after EmailAccountUpdater.onReceive returns.
I've found a lot of solutions here about the Alarm Manager but none of them seemed to work.
I create the background service with:
public void scheduleSync() {
Intent intent = new Intent(this, SyncReceiver.class);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), SYNC_INTERVAL, pendingIntent);
Log.d(TAG, "Sync scheduled.");
}
The SyncReceiver class is:
public class SyncReceiver extends BroadcastReceiver {
private static final String TAG = "SyncReceiver";
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, WebBackendSyncService_.class);
context.startService(i);
Log.d(TAG, "WebBackendSyncService started.");
}
}
And that is the WebBackendSyncService defined with Android Annotations:
#EIntentService
public class WebBackendSyncService extends IntentService {
public static final String ACTION = "com.invoicing.networking.WebBackendSyncService";
private static final String TAG = "WebBackendSyncService";
#RestService
APIService restClient;
public WebBackendSyncService() { super(ACTION);}
#Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "Handling sync intent.");
sendInvoices();
}
#Background
void sendInvoices() {
SyncData.sendInvoices(restClient);
}
}
Service and Broadcast receiver in the manifest:
<service
android:name=".networking.WebBackendSyncService_"
android:exported="false" />
<receiver
android:name=".networking.SyncReceiver"
android:process=":remote" />
Looking at those line for the past couple of hours pushed me to ask for help here. I hope you'll see something that I'm missing.
Looking at the console output it gets to "Sync scheduled."
First, your <service> element has a rogue underscore in the android:name attribute that you need to remove.
Second, get rid of android:process=":remote". Forking a whole process for a two-line BroadcastReceiver is not very efficient, and it will interfere with the next fix.
Third, switch from BroadcastReceiver to WakefulBroadcastReceiver and follow the instructions for using that class, as right now the device will readily fall asleep either before or during the service's work.
Resolved after removing unused dependencies and multi-dex config. Zero changes to the code itself, however, it works for some unknown reason.