Android AppWidget 有时不更新
Android AppWidget not updating sometimes
问题
我的 AppWidget
没有及时更新。我不明白为什么。
相关代码
这是我的 app_widget_info.xml
文件。 updatePeriodMillis
设置为零,因为我使用 AlarmManager
:
手动更新小部件
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="250dp" android:minHeight="110dp" android:updatePeriodMillis="0"
android:initialLayout="@layout/app_widget"
android:widgetCategory="home_screen|keyguard"
android:initialKeyguardLayout="@layout/app_widget"
android:previewImage="@drawable/preview_image" />
以下是我的 AppWidgetProvider
class:
的相关部分
public class MyAppWidget extends AppWidgetProvider {
private static PendingIntent service1 = null, service2 = null, update_service = null;
public static void nullifyService( String service_name ) {
if ( service_name.equals( "service1" ) ) {
service1 = null;
} else {
service2 = null;
}
}
public static void updateMyWidget(final Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
/* ... code to update my widget ... */
/* ... this code is tested and I know it works ... */
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// set alarms if necessary for the beginning and end of the next void
if ( null == update_service || ( null == service1 && null == service2 ) ) {
final AlarmManager m = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
// set alarms for the beginning and end of the upcoming/current event window
if ( null == service1 && null == service1 ) {
final Calendar s1_c = Calendar.getInstance(), // service1 alarm time
s2_c = Calendar.getInstance(); // service2 alarm time
final Intent s1_i = new Intent(context,MyService.class).putExtra( "serviceName", "service1" ),
s2_i = new Intent(context,MyService.class).putExtra( "serviceName", "service2" );
final long s1_millis, s2_millis;
// get the current VOC info
if ( db == null ) db = new MyDataBase(context);
alarm_info = db.getCurrentAlarms();
// alarm times for service1 and service2 are UNIX timestamps
// pulled from a sqlite database (tested and working fine)
s1_c.setTime(new Date(alarm_info.getLong(4) * 1000 ));
s2_c.setTime(new Date(alarm_info.getLong(1) * 1000 ));
// if it is still before the next service1 alarm time
// set an alarm for the exact time
if ( s1_c.getTimeInMillis() > Calendar.getInstance().getTimeInMillis() ) {
s1_c.set(Calendar.MILLISECOND, 0);
s1_c.set(Calendar.SECOND, 0);
service1 = PendingIntent.getService(context, 0, s1_i, PendingIntent.FLAG_ONE_SHOT );
s1_millis = SystemClock.elapsedRealtime() + s1_c.getTime().getTime() - System.currentTimeMillis();
m.setExact(AlarmManager.ELAPSED_REALTIME, s1_millis, service1);
}
// set the alarm for the service2 alarm time
s2_c.set(Calendar.MILLISECOND, 0);
s2_c.set(Calendar.SECOND, 0);
service2 = PendingIntent.getService(context, 1, s2_i, PendingIntent.FLAG_ONE_SHOT );
s2_millis = SystemClock.elapsedRealtime() + s2_c.getTime().getTime() - System.currentTimeMillis();
m.setExact(AlarmManager.ELAPSED_REALTIME, s2_millis, service2);
}
// set the widget to update every 15 minutes regardless
if ( null == update_service ) {
final Intent up_i = new Intent(context,MyService.class).putExtra( "serviceName", "update" );
update_service = PendingIntent.getService(context, 2, up_i, PendingIntent.FLAG_CANCEL_CURRENT);
m.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), 15 * 60 * 1000, update_service);
}
}
// There may be multiple widgets active, so update all of them
for( int appWidgetId : appWidgetIds) {
updateMyWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
final AlarmManager m = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
m.cancel(service1);
m.cancel(service2);
m.cancel(update_service);
}
}
这里是我的 Service
class:
的相关部分
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId){
// update all of the widgets
ComponentName cn = new ComponentName(this, MyAppWidget.class);
AppWidgetManager m = AppWidgetManager.getInstance(this);
for ( int awid : m.getAppWidgetIds(cn) ) {
MyAppWidget.updateMyWidget(this,m,awid);
}
String service_name = intent.getStringExtra( "serviceName" );
if ( "service1" == service_name || "service2" == service_name ) {
MyAppWidget.nullifyService(service_name);
}
return START_NOT_STICKY;
}
}
我所知道的和我尝试过的
update_service
工作正常,每 15 分钟可靠地更新一次小部件
- 正在设置
service1
和 service2
的闹钟。
这是来自 运行 adb shell dumpsys alarm
的片段,显示了一个重复警报(每 15 分钟或 900000 毫秒触发一次):
ELAPSED #0: Alarm{41a9e1b8 type 3 com.mycompany.myappwidget}
type=3 whenElapsed=231265839 when=+11m52s295ms window=-1 repeatInterval=900000 count=0
operation=PendingIntent{41dbf158: PendingIntentRecord{41cf7920 com.mycompany.myappwidget startService}}
这是另一个显示设置在准确时间触发的警报之一:
ELAPSED #4: Alarm{433d1560 type 3 com.mycompany.myappwidget}
type=3 whenElapsed=90066209 when=+4h34m36s121ms window=0 repeatInterval=0 count=0
operation=PendingIntent{41f0ec28: PendingIntentRecord{41f1e690 com.mycompany.myappwidget startService}}
这里是已经触发的警报的统计数据,其中 d
和 c
指的是启动的服务:
com.mycompany.myappwidget +1s857ms running, 0 wakeups:
+1s817ms 0 wakes 83 alarms: cmp={com.mycompany.myappwidget/com.mycompany.myappwidget.d}
+40ms 0 wakes 1 alarms: cmp={com.mycompany.myappwidget/com.mycompany.myappwidget.c}
如您所见,警报正在被触发,但界面没有更新。
- 由于重复警报每 15 分钟可靠地触发一次,我不认为 OS 基于每 30 分钟仅更新小部件的限制
杀死它们
- 我尝试为每个警报创建不同的
Service
class 以避免警报像 suggested by this post 那样相互抵消
- 我已经使用
Log
语句来确认 updateMyWidget()
函数在触发每个警报时实际上被调用了。似乎调用了该函数,但并不总是更新小部件。
- 我已经尝试为每个
PendingIntent
设置不同的唯一 requestCode
,这样当设置新的警报时,它不会无意中取消其他警报设置的 PendingIntent
问题和想法
我做错了吗?平均而言,更新似乎只需要大约 200 毫秒,所以也许我应该使用 PendingIntent.getBroadcast()
并调用 android.appwidget.action.APPWIDGET_UPDATE
而不是创建 Service
来处理更新。这让我抓狂,所以任何可能指向正确方向的提示或线索都将不胜感激。
更新
@Karakuri 的回答教会了我如何区分 PendingIntent
对象,以免它们在设置警报时相互覆盖。以下是我修复此代码的方法:
- 我将
putExtra()
调用更改为 setAction()
并将操作设置为预定义常量之一,例如Intent.ACTION_MAIN
,这在语义上似乎与服务正在做的事情相似
- 我从 class 级范围中删除了所有服务,因为事实证明没有必要在
onUpdate()
调用之间保留它们
- 我摆脱了
onUpdate()
中的所有条件逻辑
- 我回到使用内置 android 更新机制,即将
app_widget_info.xml
中的 android:updatePeriodMillis
更改为 1800000
(30 分钟)并删除我用 setRepeating()
设置的重复闹钟
- 我发现我的警报的边界条件存在问题,导致 UI 在我认为应该更新时无法更新——不过,如果我没有发现这个问题'开始使用
setAction()
非常感谢!!!
这是我的建议,希望这里的某处可以解决您的问题。
不要使用 ==
来比较您的服务(或任何地方)中的字符串。将 .equals()
与命名常量一起使用。
您的 PendingIntent
仅在其他方面有所不同。当你把相同的 Intent
给 PendingIntent
时,它只保留其中一个。为了比较 Intent
s,不考虑 extras(参见 PendingIntent
的文档)。我不会使用附加功能,而是给每个 Intent
一个不同的操作并在您的服务中使用 intent.getAction()
。
不要依赖静态变量来保持状态。您的进程可以随时关闭,您将丢失该数据。您的 AppWidget 运行 在不同的进程中,因此除非您有其他东西 运行,否则 Android 可能会决定清除您的进程。我会使用某种持久性存储,可能 SharedPreferences
如果它只是少数几个键。
问题
我的 AppWidget
没有及时更新。我不明白为什么。
相关代码
这是我的 app_widget_info.xml
文件。 updatePeriodMillis
设置为零,因为我使用 AlarmManager
:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="250dp" android:minHeight="110dp" android:updatePeriodMillis="0"
android:initialLayout="@layout/app_widget"
android:widgetCategory="home_screen|keyguard"
android:initialKeyguardLayout="@layout/app_widget"
android:previewImage="@drawable/preview_image" />
以下是我的 AppWidgetProvider
class:
public class MyAppWidget extends AppWidgetProvider {
private static PendingIntent service1 = null, service2 = null, update_service = null;
public static void nullifyService( String service_name ) {
if ( service_name.equals( "service1" ) ) {
service1 = null;
} else {
service2 = null;
}
}
public static void updateMyWidget(final Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
/* ... code to update my widget ... */
/* ... this code is tested and I know it works ... */
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// set alarms if necessary for the beginning and end of the next void
if ( null == update_service || ( null == service1 && null == service2 ) ) {
final AlarmManager m = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
// set alarms for the beginning and end of the upcoming/current event window
if ( null == service1 && null == service1 ) {
final Calendar s1_c = Calendar.getInstance(), // service1 alarm time
s2_c = Calendar.getInstance(); // service2 alarm time
final Intent s1_i = new Intent(context,MyService.class).putExtra( "serviceName", "service1" ),
s2_i = new Intent(context,MyService.class).putExtra( "serviceName", "service2" );
final long s1_millis, s2_millis;
// get the current VOC info
if ( db == null ) db = new MyDataBase(context);
alarm_info = db.getCurrentAlarms();
// alarm times for service1 and service2 are UNIX timestamps
// pulled from a sqlite database (tested and working fine)
s1_c.setTime(new Date(alarm_info.getLong(4) * 1000 ));
s2_c.setTime(new Date(alarm_info.getLong(1) * 1000 ));
// if it is still before the next service1 alarm time
// set an alarm for the exact time
if ( s1_c.getTimeInMillis() > Calendar.getInstance().getTimeInMillis() ) {
s1_c.set(Calendar.MILLISECOND, 0);
s1_c.set(Calendar.SECOND, 0);
service1 = PendingIntent.getService(context, 0, s1_i, PendingIntent.FLAG_ONE_SHOT );
s1_millis = SystemClock.elapsedRealtime() + s1_c.getTime().getTime() - System.currentTimeMillis();
m.setExact(AlarmManager.ELAPSED_REALTIME, s1_millis, service1);
}
// set the alarm for the service2 alarm time
s2_c.set(Calendar.MILLISECOND, 0);
s2_c.set(Calendar.SECOND, 0);
service2 = PendingIntent.getService(context, 1, s2_i, PendingIntent.FLAG_ONE_SHOT );
s2_millis = SystemClock.elapsedRealtime() + s2_c.getTime().getTime() - System.currentTimeMillis();
m.setExact(AlarmManager.ELAPSED_REALTIME, s2_millis, service2);
}
// set the widget to update every 15 minutes regardless
if ( null == update_service ) {
final Intent up_i = new Intent(context,MyService.class).putExtra( "serviceName", "update" );
update_service = PendingIntent.getService(context, 2, up_i, PendingIntent.FLAG_CANCEL_CURRENT);
m.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), 15 * 60 * 1000, update_service);
}
}
// There may be multiple widgets active, so update all of them
for( int appWidgetId : appWidgetIds) {
updateMyWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
final AlarmManager m = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
m.cancel(service1);
m.cancel(service2);
m.cancel(update_service);
}
}
这里是我的 Service
class:
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId){
// update all of the widgets
ComponentName cn = new ComponentName(this, MyAppWidget.class);
AppWidgetManager m = AppWidgetManager.getInstance(this);
for ( int awid : m.getAppWidgetIds(cn) ) {
MyAppWidget.updateMyWidget(this,m,awid);
}
String service_name = intent.getStringExtra( "serviceName" );
if ( "service1" == service_name || "service2" == service_name ) {
MyAppWidget.nullifyService(service_name);
}
return START_NOT_STICKY;
}
}
我所知道的和我尝试过的
update_service
工作正常,每 15 分钟可靠地更新一次小部件- 正在设置
service1
和service2
的闹钟。
这是来自 运行 adb shell dumpsys alarm
的片段,显示了一个重复警报(每 15 分钟或 900000 毫秒触发一次):
ELAPSED #0: Alarm{41a9e1b8 type 3 com.mycompany.myappwidget}
type=3 whenElapsed=231265839 when=+11m52s295ms window=-1 repeatInterval=900000 count=0
operation=PendingIntent{41dbf158: PendingIntentRecord{41cf7920 com.mycompany.myappwidget startService}}
这是另一个显示设置在准确时间触发的警报之一:
ELAPSED #4: Alarm{433d1560 type 3 com.mycompany.myappwidget}
type=3 whenElapsed=90066209 when=+4h34m36s121ms window=0 repeatInterval=0 count=0
operation=PendingIntent{41f0ec28: PendingIntentRecord{41f1e690 com.mycompany.myappwidget startService}}
这里是已经触发的警报的统计数据,其中 d
和 c
指的是启动的服务:
com.mycompany.myappwidget +1s857ms running, 0 wakeups:
+1s817ms 0 wakes 83 alarms: cmp={com.mycompany.myappwidget/com.mycompany.myappwidget.d}
+40ms 0 wakes 1 alarms: cmp={com.mycompany.myappwidget/com.mycompany.myappwidget.c}
如您所见,警报正在被触发,但界面没有更新。
- 由于重复警报每 15 分钟可靠地触发一次,我不认为 OS 基于每 30 分钟仅更新小部件的限制 杀死它们
- 我尝试为每个警报创建不同的
Service
class 以避免警报像 suggested by this post 那样相互抵消
- 我已经使用
Log
语句来确认updateMyWidget()
函数在触发每个警报时实际上被调用了。似乎调用了该函数,但并不总是更新小部件。 - 我已经尝试为每个
PendingIntent
设置不同的唯一requestCode
,这样当设置新的警报时,它不会无意中取消其他警报设置的PendingIntent
问题和想法
我做错了吗?平均而言,更新似乎只需要大约 200 毫秒,所以也许我应该使用 PendingIntent.getBroadcast()
并调用 android.appwidget.action.APPWIDGET_UPDATE
而不是创建 Service
来处理更新。这让我抓狂,所以任何可能指向正确方向的提示或线索都将不胜感激。
更新
@Karakuri 的回答教会了我如何区分 PendingIntent
对象,以免它们在设置警报时相互覆盖。以下是我修复此代码的方法:
- 我将
putExtra()
调用更改为setAction()
并将操作设置为预定义常量之一,例如Intent.ACTION_MAIN
,这在语义上似乎与服务正在做的事情相似 - 我从 class 级范围中删除了所有服务,因为事实证明没有必要在
onUpdate()
调用之间保留它们
- 我摆脱了
onUpdate()
中的所有条件逻辑
- 我回到使用内置 android 更新机制,即将
app_widget_info.xml
中的android:updatePeriodMillis
更改为1800000
(30 分钟)并删除我用setRepeating()
设置的重复闹钟
- 我发现我的警报的边界条件存在问题,导致 UI 在我认为应该更新时无法更新——不过,如果我没有发现这个问题'开始使用
setAction()
非常感谢!!!
这是我的建议,希望这里的某处可以解决您的问题。
不要使用
==
来比较您的服务(或任何地方)中的字符串。将.equals()
与命名常量一起使用。您的
PendingIntent
仅在其他方面有所不同。当你把相同的Intent
给PendingIntent
时,它只保留其中一个。为了比较Intent
s,不考虑 extras(参见PendingIntent
的文档)。我不会使用附加功能,而是给每个Intent
一个不同的操作并在您的服务中使用intent.getAction()
。不要依赖静态变量来保持状态。您的进程可以随时关闭,您将丢失该数据。您的 AppWidget 运行 在不同的进程中,因此除非您有其他东西 运行,否则 Android 可能会决定清除您的进程。我会使用某种持久性存储,可能
SharedPreferences
如果它只是少数几个键。