Skip to content Skip to sidebar Skip to footer

Xamarin: Android Widget With Timer, Stops When App Killed

I have this code: public class MyWidgetProvider : AppWidgetProvider { public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)

Solution 1:

This is how I solved it:

publicstaticclassWidgetConsts
{
    publicconststring DebugTag = "com.myapp.WIDGET";
    publicconststring ActionWakeup = "com.myapp.WIDGET_WAKEUP";
    publicconststring ActionWidgetUpdate = "android.appwidget.action.APPWIDGET_UPDATE";
    publicconststring ActionWidgetDisabled = "android.appwidget.action.APPWIDGET_DISABLED";
}

[BroadcastReceiver]
[IntentFilter(new string[] { WidgetConsts.ActionWakeup })]
publicclassAlarmReceiver : BroadcastReceiver
{
    publicoverridevoidOnReceive(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")]
publicclassMyWidgetProvider : AppWidgetProvider
{
    publicstatic System.Timers.Timer widgetTimer = null;

    publicoverridevoidOnUpdate(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 secif (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);
    }

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

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

    staticpublicvoidUpdateAppWidget(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...

Solution 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

@OverridepublicintonStartCommand(Intent intent, int flags, int startId)
 {
 return START_STICKY_COMPATIBILITY;
 //return super.onStartCommand(intent, flags, startId);
 }

@OverridepublicintonStartCommand(Intent intent, int flags, int startId)
 {
 flags = START_STICKY;
 returnsuper.onStartCommand(intent, flags, startId);
 // return START_REDELIVER_INTENT;
 }
@OverridepublicvoidonStart(Intent intent, int startId)
{
// again regsiter broadcastIntentFilterlocalIntentFilter=newIntentFilter("android.intent.action.USER_PRESENT");
localIntentFilter.setPriority(Integer.MAX_VALUE);// max intmyReceiversearchReceiver=newmyReceiver();
registerReceiver(searchReceiver, localIntentFilter);
super.onStart(intent, startId);
}

2,Restart the Service in the Service's onDestroy().

publicvoidonDestroy()
{
Intent localIntent = new Intent();
localIntent.setClass(this, MyService.class); // restart Servicethis.startService(localIntent);
}

3,create a broadcast and regsiter in XML

publicclassmyReceiverextendsBroadcastReceiver
{
 @OverridepublicvoidonReceive(Context context, Intent intent)
 {
 context.startService(newIntent(context, Google.class));
 }
}

<receiver android:name=".myReceiver" >
      <intent-filterandroid:priority="2147483647" ><!--Priority plus highest--><!-- when applicayion lauch invoke --><actionandroid:name="android.intent.action.BOOT_COMPLETED" /><!-- unlock invole --><actionandroid:name="android.intent.action.USER_PRESENT" /><!--context switch --><actionandroid:name="android.media.RINGER_MODE_CHANGED" /></intent-filter>
</receiver>
<serviceandroid:name=".MyService" >

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

<uses-permissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED" /><uses-permissionandroid: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

privateclassScreenBroadcastReceiverextendsBroadcastReceiver {
    private String action = null;

    @Override
    publicvoidonReceive(Context context, Intent intent) {
        action = intent.getAction();
        if (Intent.ACTION_SCREEN_ON.equals(action)) { // screen on
            mScreenStateListener.onScreenOn();
        } elseif (Intent.ACTION_SCREEN_OFF.equals(action)) { // screen off
            mScreenStateListener.onScreenOff();
        } elseif (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.

Post a Comment for "Xamarin: Android Widget With Timer, Stops When App Killed"