Xamarin: Android Widget with timer, stops when app killed

感情迁移 提交于 2021-02-11 14:21:49

问题


I have this code:

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

And I would like to know why following occurs:

  1. When I drop the widget on phone screen, the timer starts to run, this is ok.
  2. When I click on the widget the app starts, timer continues to run, this is ok.
  3. When I click on back button the app goes to background, timer continues to run, this is ok.
  4. When I terminate the app in task manager the timer stops, this is bad.
  5. When I click on the widget again the app starts but the timer does not resume operation, this is bad.
  6. The timer resumes operation only when next OnUpdate is called (I have the lowest possible interval 30 minutes), this is bad because I need frequent updating when the screen is on (or better when the widget is visible to the user).

I would like know the basics here as I could not find any relevant information. Why the timer runs when I first drop the widget on screen (without running app) and stops when the app gets killed?

Yes I have read almost everything about widget basics, then about using AlarmManager, Service, JobService, JobIntentService, JobScheduler etc. But I am interested in this solution with timer as it is very simple and works across all present Android versions (even newest Oreo). Things to solve yet are to stop the timer when the screen goes off and start it again when it goes on. To save the phone battery.


回答1:


This is how I solved it:

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

Pros: Simple solution, works on all Android systems (tested on 3.2, 4.3, 8.1). Battery friendly on Android systems >= 6.0 with doze mode (measured with GSam Battery monitor). Not restricted by the new background execution limits in >=8.0.

Cons: Drains battery on systems below 6.0 without doze mode, but no one cares about these today...




回答2:


First,You can try to make the Widget app not be skilled.

The widget itself will not be killed. The widget is originally a broadcastreciver, and it is static. This means that a subscribed broadcast widget can be received at any time, and the onReceive() method will be called. The reason why widgets can't be run is that they should be killed for the corresponding service. If want the widget to run all the time, the service should when be killed and be restarted.

Service is a component of the Android system, it is similar to the level of Activity, but he can not run by himself, can only run in the background, and can interact with other components. In the Android development process, each time the startService (Intent) is called, the OnStartCommand(Intent, int, int) method of the Service object is called, and then some processing is done in the onStartCommand method.

1,Create the servide not be killed

@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,Restart the Service in the Service's onDestroy().

public void onDestroy()
{
Intent localIntent = new Intent();
localIntent.setClass(this, MyService.class); // restart Service
this.startService(localIntent);
}

3,create a broadcast and regsiter in 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" >

Note: Unlock, start, switch scene activation broadcast needs to add permissions, such as startup completion, and mobile phone status.

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

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

Second, If Widget app not be skilled, you can listen to screen is lock or unlock.

Custom a ScreenListener and add 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();
        }
    }
}

so that you can do with Timers or other showing with customer.

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

More info:

This method not the best, there are more places to improve,just give a suggestion.



来源:https://stackoverflow.com/questions/53659897/xamarin-android-widget-with-timer-stops-when-app-killed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!