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
        });
    }
}

我想知道为什么会出现以下情况:

  1. 当我将小部件放在 phone 屏幕上时,计时器开始 运行,没问题。
  2. 当我点击应用程序启动的小部件时,计时器继续 运行,没问题。
  3. 当我点击后退按钮时,应用程序进入后台,计时器继续 运行,没问题。
  4. 当我在任务管理器中终止应用程序时,计时器停止,这很糟糕。
  5. 当我再次点击小部件时,应用程序启动但计时器没有恢复运行,这很糟糕。
  6. 计时器仅在调用下一次 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 的系统会耗尽电池电量,但今天没有人关心这些...