Xamarin:Android 带计时器的小部件,在应用程序终止时停止
Xamarin: Android Widget with timer, stops when app killed
我有这个代码:
public class MyWidgetProvider : AppWidgetProvider
{
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
Log.Debug("WIDGET", "Updating the widget");
// Open app on click
RemoteViews views = new RemoteViews(context.PackageName, Resource.Layout.MyWidget);
Intent launchAppIntent = new Intent(context, typeof(MainActivity));
PendingIntent launchAppPendingIntent = PendingIntent.GetActivity(context, 0, launchAppIntent, PendingIntentFlags.UpdateCurrent);
views.SetOnClickPendingIntent(Resource.Id.main, launchAppPendingIntent);
appWidgetManager.UpdateAppWidget(appWidgetIds[0], views);
// Start timer
System.Timers.Timer timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.Elapsed += OnTimedEvent;
timer.Enabled = true;
}
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
Log.Debug("WIDGET", "Updating status...");
new Handler(Looper.MainLooper).Post(() =>
{
//Run my code to periodically update the widget
});
}
}
我想知道为什么会出现以下情况:
- 当我将小部件放在 phone 屏幕上时,计时器开始 运行,没问题。
- 当我点击应用程序启动的小部件时,计时器继续 运行,没问题。
- 当我点击后退按钮时,应用程序进入后台,计时器继续 运行,没问题。
- 当我在任务管理器中终止应用程序时,计时器停止,这很糟糕。
- 当我再次点击小部件时,应用程序启动但计时器没有恢复运行,这很糟糕。
- 计时器仅在调用下一次 OnUpdate 时才恢复运行(我的间隔时间最短为 30 分钟),这很糟糕,因为我需要在屏幕打开时频繁更新(或者当小部件对用户可见时更好) ).
我想在这里了解一下基础知识,因为我找不到任何相关信息。为什么当我第一次将小部件放在屏幕上(没有 运行ning 应用程序)时计时器 运行s 并在应用程序被杀死时停止?
是的,我已经阅读了几乎所有关于小部件基础知识的内容,然后是关于使用 AlarmManager、Service、JobService、JobIntentService、JobScheduler 等的内容。但是我对这个带有计时器的解决方案很感兴趣,因为它非常简单并且适用于所有现有 Android 版本(甚至是最新的奥利奥)。需要解决的问题是在屏幕熄灭时停止计时器,并在屏幕亮起时重新启动。节省 phone 电池。
首先,你可以尝试让Widget应用不熟练
小部件本身不会被杀死。 widget本来就是一个broadcastreciver,是静态的。这意味着可以随时接收订阅的广播小部件,并调用 onReceive() 方法。 widgets不能运行的原因是要为相应的service杀掉它们如果想让widget一直运行,service应该什么时候被杀死并重新启动。
服务是Android系统的一个组成部分,和Activity的水平差不多,但是他自己不能运行,只能运行在后台,并且可以与其他组件交互。
Android开发过程中,每次调用startService(Intent)时,都会调用Service对象的OnStartCommand(Intent, int, int)方法,然后在onStartCommand方法中进行一些处理
1,创建不被kill的servide
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
return START_STICKY_COMPATIBILITY;
//return super.onStartCommand(intent, flags, startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
// return START_REDELIVER_INTENT;
}
@Override
public void onStart(Intent intent, int startId)
{
// again regsiter broadcast
IntentFilter localIntentFilter = new IntentFilter("android.intent.action.USER_PRESENT");
localIntentFilter.setPriority(Integer.MAX_VALUE);// max int
myReceiver searchReceiver = new myReceiver();
registerReceiver(searchReceiver, localIntentFilter);
super.onStart(intent, startId);
}
2、在Service的onDestroy()中重启Service。
public void onDestroy()
{
Intent localIntent = new Intent();
localIntent.setClass(this, MyService.class); // restart Service
this.startService(localIntent);
}
3、在XML
中创建广播并注册
public class myReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
context.startService(new Intent(context, Google.class));
}
}
<receiver android:name=".myReceiver" >
<intent-filter android:priority="2147483647" ><!--Priority plus highest-->
<!-- when applicayion lauch invoke -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
<!-- unlock invole -->
<action android:name="android.intent.action.USER_PRESENT" />
<!--context switch -->
<action android:name="android.media.RINGER_MODE_CHANGED" />
</intent-filter>
</receiver>
<service android:name=".MyService" >
注意:解锁、启动、切换场景激活广播需要添加权限,如启动完成、移动phone状态等。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
============================================= =====================
其次,如果Widget app不熟练,可以听屏幕锁定或解锁。
自定义一个ScreenListener并添加ScreenBroadcastReceiver
private class ScreenBroadcastReceiver extends BroadcastReceiver {
private String action = null;
@Override
public void onReceive(Context context, Intent intent) {
action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) { // screen on
mScreenStateListener.onScreenOn();
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // screen off
mScreenStateListener.onScreenOff();
} else if (Intent.ACTION_USER_PRESENT.equals(action)) { // screen unlock
mScreenStateListener.onUserPresent();
}
}
}
以便您可以使用计时器或其他方式向客户展示。
============================================= =================================
更多信息:
这个方法不是最好的,还有很多需要改进的地方,给个建议吧。
我是这样解决的:
public static class WidgetConsts
{
public const string DebugTag = "com.myapp.WIDGET";
public const string ActionWakeup = "com.myapp.WIDGET_WAKEUP";
public const string ActionWidgetUpdate = "android.appwidget.action.APPWIDGET_UPDATE";
public const string ActionWidgetDisabled = "android.appwidget.action.APPWIDGET_DISABLED";
}
[BroadcastReceiver]
[IntentFilter(new string[] { WidgetConsts.ActionWakeup })]
public class AlarmReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action.Equals(WidgetConsts.ActionWakeup))
{
Log.Debug(WidgetConsts.DebugTag, "Wakeup alarm called");
if (MyWidgetProvider.widgetTimer == null)
{
Log.Debug(WidgetConsts.DebugTag, "Widget updating does not run, enforcing update...");
MyWidgetProvider.UpdateAppWidget(context);
}
else
{
Log.Debug(WidgetConsts.DebugTag, "Widget updating runs, no action needed");
}
}
}
}
[BroadcastReceiver]
[IntentFilter(new string[] { WidgetConsts.ActionWidgetUpdate })]
[IntentFilter(new string[] { WidgetConsts.ActionWidgetDisabled })]
[MetaData("android.appwidget.provider", Resource = "@xml/widget_info")]
public class MyWidgetProvider : AppWidgetProvider
{
public static System.Timers.Timer widgetTimer = null;
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
Log.Debug(WidgetConsts.DebugTag, "Updating the widget");
// Open app on click
RemoteViews views = new RemoteViews(context.PackageName, Resource.Layout.MyWidget);
Intent launchAppIntent = new Intent(context, typeof(MainActivity));
PendingIntent launchAppPendingIntent = PendingIntent.GetActivity(context, 0, launchAppIntent, PendingIntentFlags.UpdateCurrent);
views.SetOnClickPendingIntent(Resource.Id.main, launchAppPendingIntent);
appWidgetManager.UpdateAppWidget(appWidgetIds[0], views);
// set timer for updating the widget views each 5 sec
if (widgetTimer == null)
{
widgetTimer = new System.Timers.Timer();
widgetTimer.Interval = 5000;
widgetTimer.Elapsed += OnTimedEvent;
}
widgetTimer.Enabled = true;
// set alarm to wake up the app when killed, each 60 sec
// needs a fresh BroadcastReceiver because AppWidgetProvider.OnReceive is
// not virtual and overriden method in this class would not be called
AlarmManager am = (AlarmManager)context.GetSystemService(Context.AlarmService);
Intent ai = new Intent(context, typeof(AlarmReceiver));
ai.SetAction(WidgetConsts.ActionWakeup);
PendingIntent pi = PendingIntent.GetBroadcast(context, 0, ai, PendingIntentFlags.CancelCurrent);
am.SetRepeating(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime(), 1000 * 60, pi);
}
public override void OnDisabled(Context context)
{
Log.Debug(WidgetConsts.DebugTag, "Disabling the widget");
if (widgetTimer != null)
{
Log.Debug(WidgetConsts.DebugTag, "Stopping timer");
widgetTimer.Enabled = false;
}
else
Log.Debug(WidgetConsts.DebugTag, "Timer is null");
base.OnDisabled(context);
}
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
Log.Debug(WidgetConsts.DebugTag, "Updating status...");
new Handler(Looper.MainLooper).Post(() =>
{
//Run my code to periodically update the widget
RemoteViews views = new RemoteViews(Application.Context.PackageName, Resource.Layout.MyWidget);
AppWidgetManager manager = AppWidgetManager.GetInstance(Application.Context);
ComponentName thisWidget = new ComponentName(Application.Context, Java.Lang.Class.FromType(typeof(MyWidgetProvider)));
int[] appWidgetIds = manager.GetAppWidgetIds(thisWidget);
views.SetTextViewText(Resource.Id.myText, "my text");
manager.UpdateAppWidget(appWidgetIds[0], views);
});
}
static public void UpdateAppWidget(Context context)
{
Intent intent = new Intent(context, typeof(MyWidgetProvider));
intent.SetAction(WidgetConsts.ActionWidgetUpdate);
int[] ids = AppWidgetManager.GetInstance(context).GetAppWidgetIds(new ComponentName(context, Java.Lang.Class.FromType(typeof(MyWidgetProvider))));
intent.PutExtra(AppWidgetManager.ExtraAppwidgetIds, ids);
context.SendBroadcast(intent);
}
}
优点:
简单的解决方案,适用于所有 Android 系统(在 3.2、4.3、8.1 上测试)。
在 Android 系统上电池友好 >= 6.0,带有打瞌睡模式(使用 GSam 电池监视器测量)。不受 >=8.0.
中新的后台执行限制的限制
缺点:
在没有打瞌睡模式的情况下,低于 6.0 的系统会耗尽电池电量,但今天没有人关心这些...
我有这个代码:
public class MyWidgetProvider : AppWidgetProvider
{
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
Log.Debug("WIDGET", "Updating the widget");
// Open app on click
RemoteViews views = new RemoteViews(context.PackageName, Resource.Layout.MyWidget);
Intent launchAppIntent = new Intent(context, typeof(MainActivity));
PendingIntent launchAppPendingIntent = PendingIntent.GetActivity(context, 0, launchAppIntent, PendingIntentFlags.UpdateCurrent);
views.SetOnClickPendingIntent(Resource.Id.main, launchAppPendingIntent);
appWidgetManager.UpdateAppWidget(appWidgetIds[0], views);
// Start timer
System.Timers.Timer timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.Elapsed += OnTimedEvent;
timer.Enabled = true;
}
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
Log.Debug("WIDGET", "Updating status...");
new Handler(Looper.MainLooper).Post(() =>
{
//Run my code to periodically update the widget
});
}
}
我想知道为什么会出现以下情况:
- 当我将小部件放在 phone 屏幕上时,计时器开始 运行,没问题。
- 当我点击应用程序启动的小部件时,计时器继续 运行,没问题。
- 当我点击后退按钮时,应用程序进入后台,计时器继续 运行,没问题。
- 当我在任务管理器中终止应用程序时,计时器停止,这很糟糕。
- 当我再次点击小部件时,应用程序启动但计时器没有恢复运行,这很糟糕。
- 计时器仅在调用下一次 OnUpdate 时才恢复运行(我的间隔时间最短为 30 分钟),这很糟糕,因为我需要在屏幕打开时频繁更新(或者当小部件对用户可见时更好) ).
我想在这里了解一下基础知识,因为我找不到任何相关信息。为什么当我第一次将小部件放在屏幕上(没有 运行ning 应用程序)时计时器 运行s 并在应用程序被杀死时停止?
是的,我已经阅读了几乎所有关于小部件基础知识的内容,然后是关于使用 AlarmManager、Service、JobService、JobIntentService、JobScheduler 等的内容。但是我对这个带有计时器的解决方案很感兴趣,因为它非常简单并且适用于所有现有 Android 版本(甚至是最新的奥利奥)。需要解决的问题是在屏幕熄灭时停止计时器,并在屏幕亮起时重新启动。节省 phone 电池。
首先,你可以尝试让Widget应用不熟练
小部件本身不会被杀死。 widget本来就是一个broadcastreciver,是静态的。这意味着可以随时接收订阅的广播小部件,并调用 onReceive() 方法。 widgets不能运行的原因是要为相应的service杀掉它们如果想让widget一直运行,service应该什么时候被杀死并重新启动。
服务是Android系统的一个组成部分,和Activity的水平差不多,但是他自己不能运行,只能运行在后台,并且可以与其他组件交互。 Android开发过程中,每次调用startService(Intent)时,都会调用Service对象的OnStartCommand(Intent, int, int)方法,然后在onStartCommand方法中进行一些处理
1,创建不被kill的servide
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
return START_STICKY_COMPATIBILITY;
//return super.onStartCommand(intent, flags, startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
// return START_REDELIVER_INTENT;
}
@Override
public void onStart(Intent intent, int startId)
{
// again regsiter broadcast
IntentFilter localIntentFilter = new IntentFilter("android.intent.action.USER_PRESENT");
localIntentFilter.setPriority(Integer.MAX_VALUE);// max int
myReceiver searchReceiver = new myReceiver();
registerReceiver(searchReceiver, localIntentFilter);
super.onStart(intent, startId);
}
2、在Service的onDestroy()中重启Service。
public void onDestroy()
{
Intent localIntent = new Intent();
localIntent.setClass(this, MyService.class); // restart Service
this.startService(localIntent);
}
3、在XML
中创建广播并注册public class myReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
context.startService(new Intent(context, Google.class));
}
}
<receiver android:name=".myReceiver" >
<intent-filter android:priority="2147483647" ><!--Priority plus highest-->
<!-- when applicayion lauch invoke -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
<!-- unlock invole -->
<action android:name="android.intent.action.USER_PRESENT" />
<!--context switch -->
<action android:name="android.media.RINGER_MODE_CHANGED" />
</intent-filter>
</receiver>
<service android:name=".MyService" >
注意:解锁、启动、切换场景激活广播需要添加权限,如启动完成、移动phone状态等。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
============================================= =====================
其次,如果Widget app不熟练,可以听屏幕锁定或解锁。
自定义一个ScreenListener并添加ScreenBroadcastReceiver
private class ScreenBroadcastReceiver extends BroadcastReceiver {
private String action = null;
@Override
public void onReceive(Context context, Intent intent) {
action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) { // screen on
mScreenStateListener.onScreenOn();
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // screen off
mScreenStateListener.onScreenOff();
} else if (Intent.ACTION_USER_PRESENT.equals(action)) { // screen unlock
mScreenStateListener.onUserPresent();
}
}
}
以便您可以使用计时器或其他方式向客户展示。
============================================= =================================
更多信息:
这个方法不是最好的,还有很多需要改进的地方,给个建议吧。
我是这样解决的:
public static class WidgetConsts
{
public const string DebugTag = "com.myapp.WIDGET";
public const string ActionWakeup = "com.myapp.WIDGET_WAKEUP";
public const string ActionWidgetUpdate = "android.appwidget.action.APPWIDGET_UPDATE";
public const string ActionWidgetDisabled = "android.appwidget.action.APPWIDGET_DISABLED";
}
[BroadcastReceiver]
[IntentFilter(new string[] { WidgetConsts.ActionWakeup })]
public class AlarmReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action.Equals(WidgetConsts.ActionWakeup))
{
Log.Debug(WidgetConsts.DebugTag, "Wakeup alarm called");
if (MyWidgetProvider.widgetTimer == null)
{
Log.Debug(WidgetConsts.DebugTag, "Widget updating does not run, enforcing update...");
MyWidgetProvider.UpdateAppWidget(context);
}
else
{
Log.Debug(WidgetConsts.DebugTag, "Widget updating runs, no action needed");
}
}
}
}
[BroadcastReceiver]
[IntentFilter(new string[] { WidgetConsts.ActionWidgetUpdate })]
[IntentFilter(new string[] { WidgetConsts.ActionWidgetDisabled })]
[MetaData("android.appwidget.provider", Resource = "@xml/widget_info")]
public class MyWidgetProvider : AppWidgetProvider
{
public static System.Timers.Timer widgetTimer = null;
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
Log.Debug(WidgetConsts.DebugTag, "Updating the widget");
// Open app on click
RemoteViews views = new RemoteViews(context.PackageName, Resource.Layout.MyWidget);
Intent launchAppIntent = new Intent(context, typeof(MainActivity));
PendingIntent launchAppPendingIntent = PendingIntent.GetActivity(context, 0, launchAppIntent, PendingIntentFlags.UpdateCurrent);
views.SetOnClickPendingIntent(Resource.Id.main, launchAppPendingIntent);
appWidgetManager.UpdateAppWidget(appWidgetIds[0], views);
// set timer for updating the widget views each 5 sec
if (widgetTimer == null)
{
widgetTimer = new System.Timers.Timer();
widgetTimer.Interval = 5000;
widgetTimer.Elapsed += OnTimedEvent;
}
widgetTimer.Enabled = true;
// set alarm to wake up the app when killed, each 60 sec
// needs a fresh BroadcastReceiver because AppWidgetProvider.OnReceive is
// not virtual and overriden method in this class would not be called
AlarmManager am = (AlarmManager)context.GetSystemService(Context.AlarmService);
Intent ai = new Intent(context, typeof(AlarmReceiver));
ai.SetAction(WidgetConsts.ActionWakeup);
PendingIntent pi = PendingIntent.GetBroadcast(context, 0, ai, PendingIntentFlags.CancelCurrent);
am.SetRepeating(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime(), 1000 * 60, pi);
}
public override void OnDisabled(Context context)
{
Log.Debug(WidgetConsts.DebugTag, "Disabling the widget");
if (widgetTimer != null)
{
Log.Debug(WidgetConsts.DebugTag, "Stopping timer");
widgetTimer.Enabled = false;
}
else
Log.Debug(WidgetConsts.DebugTag, "Timer is null");
base.OnDisabled(context);
}
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
Log.Debug(WidgetConsts.DebugTag, "Updating status...");
new Handler(Looper.MainLooper).Post(() =>
{
//Run my code to periodically update the widget
RemoteViews views = new RemoteViews(Application.Context.PackageName, Resource.Layout.MyWidget);
AppWidgetManager manager = AppWidgetManager.GetInstance(Application.Context);
ComponentName thisWidget = new ComponentName(Application.Context, Java.Lang.Class.FromType(typeof(MyWidgetProvider)));
int[] appWidgetIds = manager.GetAppWidgetIds(thisWidget);
views.SetTextViewText(Resource.Id.myText, "my text");
manager.UpdateAppWidget(appWidgetIds[0], views);
});
}
static public void UpdateAppWidget(Context context)
{
Intent intent = new Intent(context, typeof(MyWidgetProvider));
intent.SetAction(WidgetConsts.ActionWidgetUpdate);
int[] ids = AppWidgetManager.GetInstance(context).GetAppWidgetIds(new ComponentName(context, Java.Lang.Class.FromType(typeof(MyWidgetProvider))));
intent.PutExtra(AppWidgetManager.ExtraAppwidgetIds, ids);
context.SendBroadcast(intent);
}
}
优点: 简单的解决方案,适用于所有 Android 系统(在 3.2、4.3、8.1 上测试)。 在 Android 系统上电池友好 >= 6.0,带有打瞌睡模式(使用 GSam 电池监视器测量)。不受 >=8.0.
中新的后台执行限制的限制缺点: 在没有打瞌睡模式的情况下,低于 6.0 的系统会耗尽电池电量,但今天没有人关心这些...