Xamarin:带有计时器的Android窗口小部件,在应用被杀死时停止

时间:2018-12-06 21:26:00

标签: android xamarin android-widget

我有此代码:

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. 当我将小部件放在电话屏幕上时,计时器开始运行,没关系。
  2. 当我单击窗口小部件时,应用程序启动,计时器继续运行,没关系。
  3. 当我单击“后退”按钮时,应用进入后台,计时器继续运行,没关系。
  4. 当我在任务管理器中终止应用程序时,计时器停止运行,这很糟糕。
  5. 当我再次单击窗口小部件时,应用程序启动但计时器无法恢复运行,这很糟糕。
  6. 仅当调用下一个OnUpdate时,计时器才恢复操作(我的最小间隔为30分钟),这很糟糕,因为在屏幕打开时(或在用户可见小部件时,我需要频繁更新),我需要经常进行更新。

我想了解这里的基本知识,因为我找不到任何相关信息。为什么计时器在我第一次将小部件放到屏幕上(不运行应用程序)时运行,而在应用程序被终止时停止运行?

是的,我已经阅读了有关小部件基础知识的几乎所有内容,然后阅读了有关使用AlarmManager,Service,JobService,JobIntentService,JobScheduler等的所有内容。但是我对带有计时器的此解决方案感兴趣,因为它非常简单,并且可以在所有当前的Android版本中使用(甚至最新的奥利奥(Oreo)。解决的办法是在屏幕关闭时停止计时器,在屏幕打开时再次启动计时器。为了节省手机电池电量。

2 个答案:

答案 0 :(得分:0)

首先,您可以尝试使Widget应用不熟练。

小部件本身不会被杀死。该小部件最初是一个广播接收器,并且是静态的。这意味着可以随时接收已订阅的广播窗口小部件,并且将调用onReceive()方法。 无法运行窗口小部件的原因是应该为相应的服务将其杀死。如果希望窗口小部件一直运行,则应在终止服务并重新启动该服务之后。

Service是Android系统的一个组件,它类似于Activity的级别,但是他不能自己运行,只能在后台运行,并且可以与其他组件进行交互。 在Android开发过程中,每次调用startService(Intent)时,都会调用Service对象的OnStartCommand(Intent,int,int)方法,然后在onStartCommand方法中进行一些处理。

1,创建不被杀死的仆人

@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,在服务的onDestroy()中重新启动服务。

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" >

注意:解锁,启动,切换场景激活广播需要添加权限,例如启动完成和手机状态。

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

================================================ ==================

第二,如果Widget应用程序不熟练,则可以收听屏幕锁定或解锁。

自定义 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();
        }
    }
}

以便您可以使用计时器或与客户进行其他展示。

================================================ ==============================

更多信息:

这种方法不是最好的,还有很多地方可以改进,只是提出建议。

答案 1 :(得分:0)

这是我解决的方法:

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上测试)。 在打do模式下大于等于6.0的Android系统上,电池友好(通过GSam电池监视器测量)。不受> = 8.0中新的后台执行限制的限制。

缺点: 在不使用休眠模式的情况下,消耗低于6.0的系统上的电池,但是今天没人在乎这些。