Okay, so I'm working on an AppWidget that checks the battery level and displays it on a TextView. My code looks like this:
public class BattWidget extends AppWidgetProvider {
private RemoteViews views = new RemoteViews("com.nickavv.cleanwidgets", R.layout.battlayout);
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetIds[]) {
final int N = appWidgetIds.length;
context.getApplicationContext().registerReceiver(this,new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
for (int i = 0; i < N; i++) {
int appWidgetId = appWidgetIds[i];
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
Log.d("onReceive", "Received intent " + intent);
if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
Integer level = intent.getIntExtra("level", -1);
views.setTextViewText(R.id.batteryText, level+"%");
AppWidgetManager myAWM = AppWidgetManager.getInstance(context);
ComponentName cn = new ComponentName(context, AirWidget.class);
onUpdate(context, myAWM, myAWM.getAppWidgetIds(cn));
}
}
}
And I'm getting concerned because as soon as I drop the widget onto my homescreen it begins firing off about 100 of those Log calls a second, saying it's receiving ACTION_BATTERY_CHANGED. Isn't this only supposed to be broadcast for each percent decrease? It actually caused my entire launcher to lag, I had to uninstall it. That can't be right.
My code looks like this:
You cannot register a BroadcastReceiver from another BroadcastReceiver and get reliable results. Android will terminate your process, because it doesn't think anything is running. The only way to listen for ACTION_BATTERY_CHANGED will be to register that receiver from an activity or a service.
Isn't this only supposed to be broadcast for each percent decrease?
Where do you see that documented? AFAIK, ACTION_BATTERY_CHANGED will be broadcast whenever the hardware feels like it. Also, bear in mind that other data changes within that Intent, such as temperature.
If you want to implement this app widget, do not register for ACTION_BATTERY_CHANGED the way you are. Instead:
Allow the user to choose a polling period via a SharedPreference (e.g., once a minute, once every 15 mintues)
Use AlarmManager to give you control on that polling period via a getBroadcast() PendingIntent
In that BroadcastReceiver, call registerReceiver() for ACTION_BATTERY_CHANGED but with a null BroadcastReceiver, as this will return to you the last Intent that was broadcast for that action (note: you will still need to use getApplicationContext() for this)
Use AppWidgetManager to update your app widget instances with the battery level pulled out of the Intent you retrieved in the preceding step (note: if you are setting them all to be the same, you do not need to iterate over the IDs -- use the updateAppWidget() that takes a ComponentName as a parameter)
This has several advantages:
You do not care how often ACTION_BATTERY_CHANGED is broadcast
The user gets to control how much battery you consume by doing these checks (should be negligible if you keep the polling period to a minute or more)
Your process can be safely terminated in between polls, thereby making it less likely that users will attack you with task killers and semi-permanently mess up your app
Well, your onUpdate is registering its own class as receiver for the batteryinfo intent. This intent is then immediately triggered for the first info.
Your onReceive is calling your onUpdate again. We call this a loop. Hence the 100 logs a second ...
Related
Update
I gave up using ACTION_TIME_TICK because when click the physical button of a locked phone, it will also sent ACTION_TIME_TICK even the phone is still locked. That may make trouble if I keep pressing the key to wake up the phone.
Edit
The original purpose of this question:
Update widget text every 60s(not TextClock)?
Can I use ACTION_TIME_TICK to update android time widget?
I find an app called KWGT(Kustom Widget) and seems like using background?foreground service to update every minute. Its service(shows notification, so I' m sure it is service) lives longer than mine(alarm + foreground service).
How do KWGT do this? Its help site says: Kustom was using close to 0 resources when it was allowed to ignore battery optimization (because targeting old releases) and new version acts like the same.Does it mean alarm using AlarmManager.RTC mode?
I have an Android AppWidget that shows HH:mm, update every 60 seconds, it works but still has some problem. Here is what I’ ve got:
I’ ve tried TextClock, it seems good, but I still need to update some other text when time ticks, like “count down: 3 min”、“count down: 2 min”.
I use the AlarmManager, send broadcast every minute to update my widget. I ‘m a little worry about my device’ s battery. Another problem is, when I change my device’ s time, TextClock get the new time immediately, but my widget text not.
I know how TextClock do it: intent ACTION_TIME_TICK, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED sent by the system. The annotation says: “You cannot receive this through components declared in manifests, only by explicitly registering for it with Context.registerReceiver()”.
So, should I register that receiver in a service? I think it will not work very long because my process can not run forever. How those system time widgets do this? What' s the best way in 2021? My minSdk is API 26, if there is some better way after it, I can change to higher version.
My Alarm:
fun startAlarm() {
val calendar: Calendar = Calendar.getInstance()
calendar.set(Calendar.MILLISECOND, 0)
calendar.set(Calendar.SECOND, 0)
calendar.add(Calendar.MINUTE, 1)
val alarmIntent = Intent(context, MyWidget::class.java)
alarmIntent.action = MyWidget.ACTION_AUTO_UPDATE
val pendingIntent = PendingIntent.getBroadcast(
context,
ALARM_ID,
alarmIntent,
PendingIntent.FLAG_CANCEL_CURRENT
)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// RTC does not wake the device up
alarmManager.setRepeating(
AlarmManager.RTC,
calendar.timeInMillis,
INTERVAL_MILLIS.toLong(), // Value will be forced up to 60000 as of Android 5.1
pendingIntent
)
}
fun registerReceiver says:
Note: this method cannot be called from a BroadcastReceiver component; that is, from a BroadcastReceiver that is declared in an application's manifest. It is okay, however, to call this method from another BroadcastReceiver that has itself been registered at run time with registerReceiver, since the lifetime of such a registered BroadcastReceiver is tied to the object that registered it
What does this mean? Can I regeister it in class MyWidget : AppWidgetProvider()? What is the correct context?
Here is the receiver register in TextClock:
// OK, this is gross but needed. This class is supported by the
// remote views mechanism and as a part of that the remote views
// can be inflated by a context for another user without the app
// having interact users permission - just for loading resources.
// For example, when adding widgets from a managed profile to the
// home screen. Therefore, we register the receiver as the user
// the app is running as not the one the context is for.
getContext().registerReceiverAsUser(
mIntentReceiver,
android.os.Process.myUserHandle(),
filter,
null,
getHandler()
);
And registerReceiverAsUser is a function with #UnsupportedAppUsage so I think it can not help me.
Thanks a lot.
The best approach is to use TextClock or AnalogClock if you can. These update each minute (or second for AnalogClock on Android 12) without your application's process needing to run.
ACTION_TIME_TICK is only sent to registered receivers, which means you won't receive it when your application isn't running. The only plausible way to keep your app running would be to use a foreground service, requiring showing a notification to the user.
You can override listed method in the class which extends from AppWidgetProvider like this:
#Override
public void onReceive(Context context, Intent intent)
{
super.onReceive(context, intent);
Log.d("TAG", "mythou--------->onReceive Reason: " + intent.getAction());
}
#Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED);
context.getApplicationContext().registerReceiver(this, filter);
}
I have a question regarding Android widgets and especially Android services. Here is what I want to do: On the phone the user should be able to add my widget to his homescreen. The widget gets its data from network.
After reading some tutrials about this, I found out that the best approach would be to create a service that gets the data from network (TCP sockets) and then updates the widget constantly. That is done with ScheduledExecutorService which executes a Runnable in a certain interval.
The Runnable will connect to a server, get the data and should update the widget).
My problem is now, that the widget don't need to be updated when the screen is off and so the service should not run because of battery drain. My question is: How to do it the best way?
I found 2 approaches which would more or less do what I want:
When the first instance of the widget is added to homescreen, it will register a Broadcast Receiver that receives the ACTION_SCREEN_ON and ACTION_SCREEN_OFF intent action from Android OS.
If ACTION_SCREEN_ON is fired, it will start the updater service, otherwise it will stop it. But I'm really unsure if that's a good approach because of the broadcast receiver lifetime.
In the updater service's Runnable, which is executed periodically by the ScheduledExecutorService and actually does the network stuff, I check via PowerManager.isScreenOn(), if the screen is on. If yes, I execute the network code, otherwise not. But what is when the device is in standby? Is this code executed then? What about battery drain here?
Is there maybe a "best practice" approach for what I want to do? I've read alot about the AlarmManager and that it is a very powerful tool. Can you schedule tasks with this in the way, that they are only executed when the display is on?
Thanks for your help.
Best Regards
NiThDi
Your first solution (widget handling screen ON and OFF broadcasts) to the problem is the way to go. Start a new IntentService for a background communication to your application server. Once it finished, send your custom "Completed" broadcast with results and then handle it in your widget.
As an additional benefit it would allow multiple instances of your widget to work from one "source" and would not consume any resources in case user did not add any widgets.
UPDATE As it is impossible to use screen ON/OFF broadcasts with a widget, I would probably use this (AlarmManager with an ELAPSED_REALTIME (not ELAPSED_REALTIME_WAKEUP) alarm) to schedule an (intent) service run. This way your service will not be scheduled when screen is off, but will run periodically when it is on. And if the delay between screen ON and OFF is more than the period it will run immediately on screen coming back ON.
As for your question about broadcast receivers lifetime, as said before, use IntentService for a long running task and then broadcast your results back from it. When you starting a service that call does not block so broadcast receiver will finish in a timely matter.
UPDATE 2 According to this screen OFF does not mean device is sleeping. You already have a solution to this issue by using PowerManager.isScreenOn().
The ELAPSED_REALTIME approach could work I think, but strangly it is not for a small test app I created.
Basically the test app is a widget and a IntentService. The widget only shows the current time, while the Intent Service gets the current time and sends a broadcast which the widget consumes in the onReceive() method and updates itself. The widget is of course a registered receiver for the custom broadcast action.
In the onEnabled() method in the widget, I initialize the alarm for AlarmManager. Now some code:
Widget onEnabled():
#Override
public void onEnabled(Context c) {
super.onEnabled(c);
Intent intent = new Intent(c.getApplicationContext(), SimpleIntentService.class);
PendingIntent intentExecuted = PendingIntent.getService(c.getApplicationContext(), 45451894, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) c.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, 0, 3000, intentExecuted);
}
Widget onReceive():
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (intent.getAction().equals(ACTION_RESP)) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName thisWidget = new ComponentName(context.getApplicationContext(), Widget.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
for (int appWidgetId : allWidgetIds) {
RemoteViews remoteViews = new RemoteViews(context.getApplicationContext().getPackageName(), R.layout.widget);
String s = "";
if (intent.hasExtra("msg")) s = intent.getStringExtra("msg");
// Set the text
remoteViews.setTextViewText(R.id.textView1, s);
appWidgetManager.updateAppWidget(allWidgetIds, remoteViews);
}
}
}
SimpleIntentService onHandleIntent():
#Override
protected void onHandleIntent(Intent intent) {
Log.w("TestService", "SimpleIntentService onHandleIntent called.");
String msg = new Date().toGMTString();
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(Widget.ACTION_RESP);
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
broadcastIntent.putExtra("msg", msg);
sendBroadcast(broadcastIntent);
}
So, I have tested this on my Nexus 4 running Android 4.2.1 and on an emulator with Android 4.0.4. In both cases the SimpleIntentService gets created and onHandleIntent() gets called every 3 seconds, even when I manually turn the display off.
I have absolutely no idea why the AlarmManager still schedules the alarm, do you have?!
Thank you!
I have a Service that uses a custom Connection class (extends thread) to a hardware controller. When the User prefers, I wish to maintain this connection on a permanent basis. I already have the code to handle when the Android device loses its internet connection, switches between wi-fi, etc.
In order to stay connected, the controller requires that you speak to it within every 5 minutes. I currently, within the Connection class start a thread that runs in a while(), and checks the system time and the last time it communicated, and when > 4 minutes it requests a status. For some reason, at different times the communication doesn't occur in time. i.e., occurs after 5 minutes. The Service doesn't die, as far as I can tell but the "Ping" to the controller is late. This doesn't happen when I have the phone plugged into the charger (or debugger). Additionally, the behavior is the same when I move the Service to the foreground.
Does the phone slow down it's processor when it goes to sleep?
Is there a better way?
I'm thinking it's the AlarmManger, but I'm having trouble getting it to work with an inner-class, within the Service. I tried using the API demos as a starting point, but I can't seem to figure out how to get the Broadcast receiver registered. I am trying to register the receiver programmatically, with no changes to the manifest.
public class DeviceConnectionService extends Service {
#Override
public void onCreate() {
Intent intent = new Intent(this, PingConnection.class);
intent.setAction("KEEP_CONNECTION_ALIVE");
PendingIntent sender = PendingIntent.getBroadcast(this,
0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// We want the alarm to go off 30 seconds from now.
long firstTime = SystemClock.elapsedRealtime();
firstTime += 15*1000;
// Schedule the alarm!
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
firstTime, 15*1000, sender);
// register to listen to the Alarm Manager
if (mPingConnectionReceiver == null) {
mPingConnectionReceiver = new PingConnection();
getApplicationContext().registerReceiver(mPingConnectionReceiver,
new IntentFilter("KEEP_CONNECTION_ALIVE"));
}
}
// ...
public class PingConnection extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (dBug) Log.i("PingConnection", "Pinging Controller");
// do real work here
}
}
}
Does the phone slow down it's processor when it goes to sleep?
The phone shuts down its processor when it goes to sleep. That is the definition of "sleep".
I'm thinking it's the AlarmManger, but I'm having trouble getting it to work with an inner-class, within the Service. I tried using the API demos as a starting point, but I can't seem to figure out how to get the Broadcast receiver registered. I am trying to register the receiver programatically, with no changes to the manifest.
That is an unusual approach for AlarmManager. That being said, since you declined to describe "having trouble" in any detail, it is difficult to help you.
Get rid of getApplicationContext() (you don't need it and really don't want it in this case). I would register the receiver before touching AlarmManager. Before you go to production, please choose an action name that has your package name in it (e.g., com.something.myapp.KEEP_CONNECTION_ALIVE).
Beyond that, check LogCat for warnings.
UPDATE
In your LogCat, you should have a warning from AlarmManager complaining about not being able to talk to your BroadcastReceiver.
Replace:
Intent intent = new Intent(this, PingConnection.class);
intent.setAction("KEEP_CONNECTION_ALIVE");
with:
Intent intent = new Intent("KEEP_CONNECTION_ALIVE");
and you may have better luck.
you can't register AlarmManager in a Service.
All you can do is declare it as global in the Manifest.xml.
You can start the alarm from service in this way, by declaring it in Manifest.xml
If you have a remote service and you close the launcher activity, the AlarmManager will still run, but don't forget to stop it on onDestroy() method of the service.
I've tried to register only in the Service the AlarmManager as I didn't used it for the main activity, but no success!
It didn't work as registering as a normal BroadCastReceiver.
that's how the things are, you have to declare it in Manifest.xml as global
I know it's late, but maybe it's useful for someone else.
You can register it, the problem is when the Intent tries to call it.
Instead of calling it like this:
Intent intent = new Intent(this, PingConnection.class);
Create an empty intent and add an action you are going to listen to:
Intent intent = new Intent();
intent.setAction("value you want to register");
Then create the pending intent and send the broadcast like you have it.
Create an attribute for the receiver so you can access it in the whole class and unregister if necessary (if the pendingIntent is also an attribute you can unregister any time):
private PingConnection pingConnection = new PingConnection();
Register it like this:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("the value you used before");
getApplicationContext().registerReceiver(pingConnection, filter);
Now you won't get any errors, and the class is not static, and it's an inner class.
i am making an appwidget and i have problems with click event, which is lost when system kills the widget's process and later restarts it. this also happens after screen rotate.
building against SDK version 7 and running on emulator (2.1) and a real device with 2.3.3.
my onUpdate method:
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int wid : appWidgetIds) {
Log.i(TAG, "onUpdate widget #" + wid);
Intent intent = new Intent(context, MyClass.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, wid);
PendingIntent clickIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
RemoteViews widget = new RemoteViews(context.getPackageName(), R.layout.widget);
widget.setOnClickPendingIntent(R.id.widget_layout, clickIntent);
appWidgetManager.updateAppWidget(wid, widget);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
where R.id.widget_layout is id of linear layout of the appwidget. i tried to add this click event also to a textview, but with same result.
i am fighting this problem for several days and i found some people with this same problem, but no solution works for me :( i also tried different pending intent flags without any success.
second problem is, when i add another appwidget on home screen, it does not react to click events. in logcat i see the message from onUpdate method "onUpdate widget #xy", but the appwidget does not react to clicks. only the first appwidget placed on home screen reacts to clicks, but only for some time. any ideas?
When you say the first widget stops responding to clicks, do you mean that the onUpdate method is not being called? Perhaps put some code in onEnabled(Context context) to see if that's being called instead, and if so, put whatever logic is necessary in that function. Also, you can catch intents via the onReceive method (found at the same link) to see which ones your widget is actually receiving.
Also, ensure that the Context that you have the receiver running in (the one that gets passed to this function) is an Application or Service, and not an Activity, or the reference to it may not persist.
You must also make sure that every time you update the widget with a RemoteViews object, you send all of the data needed to fully reconstruct the widget. This is because on, e.g., screen rotation, the system doesn't have anything to reconstruct the widget with beside the very latest RemoteViews you passed it.
i have one widget in which there are two buttons and one service class in which i have two methods on clikcing of button i want to call the methods in service class.
any reference or code example?
how can i do it?
thankx
I'm guessing that your question pertains to appWidgets. If not, the answer below will probably be less useful, although you can still adapt part of it to your own use, I think.
First declare some action(s), like this:
public static final String ACTION_HIDE_GUIDE_OVERLAY = "dk.something.appwidget.calendar.guide.hide";
Use the action(s) to declare an Intent and a PendingIntent:
Intent intent = new Intent(context, CalendarService.class);
intent.setAction(CalendarService.ACTION_HIDE_GUIDE_OVERLAY);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent buttonPendingIntent1 = PendingIntent.getService(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Then register the pendingIntent with the RemoteView for your widget:
views.setOnClickPendingIntent(R.id.calendar_button_1, buttonPendingIntent1);
Finally, in the onStartCommand or onHandleIntent of your service, examine the action of the intent to determine which of your methods to call:
if (intent.getAction().equals(CalendarService.ACTION_INITIALIZE_CALENDAR)) {
int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
onInitializeCalendar(appWidgetIds);
} else if (intent.getAction().equals(CalendarService.ACTION_HIDE_GUIDE_OVERLAY)) {
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
onHideGuideOverlay(appWidgetId);
} else ...
Note: The code snippets are taken almost directly from my Glass Widgets, so to adapt it to your own use, you would have to replace the action constant with your own two actions (one action for each button), replace R.id.calendar_button_1 with the actual id of one of your buttons, replace the CalendarService with the name of your own service class and the methods onInitializeCalendar and onHideGuideOverlay with the name of the two methods in your service class that you want to call.
If you only ever have one instance of your widget visible at one time, then you don't need to fiddle around with the appWidgetId, but you probably can't guarantee that. I have some settings that can vary from widget instance to widget instance, so I put the id of the appwidget that was clicked into the intent, where the service can get at it when servicing my request.