Xamarin: Android Widget с таймером, останавливается, когда приложение убито - PullRequest
0 голосов
/ 07 декабря 2018

У меня есть этот код:

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 голосов
/ 20 декабря 2018

Вот как я это решил:

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 без режима ожидания, но сегодня никому нет до этого дела ...

0 голосов
/ 07 декабря 2018

Во-первых, вы можете попытаться сделать приложение Widget неопытным.

Сам виджет не будет убит.Изначально виджет был трансляционным, и он статический.Это означает, что подписанный широковещательный виджет может быть получен в любое время, и будет вызван метод onReceive (). Причина, по которой виджеты не могут быть запущены, заключается в том, что они должны быть уничтожены для соответствующей службы. Если вы хотите, чтобы виджет работал постоянно, служба должна быть убита и перезапущена.

Сервис является компонентом системы Android, он аналогичен уровню Активности, но он не может работать самостоятельно, может работать только в фоновом режиме и может взаимодействовать с другими компонентами.В процессе разработки Android каждый раз, когда вызывается startService (Intent), вызывается метод OnStartCommand (Intent, int, int) объекта Service, а затем некоторая обработка выполняется в методе 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 не обладает навыками, вы можете прослушивать блокировку или разблокировку экрана.

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

, чтобы вы могли работать с таймерами или другими показами с клиентом.

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

Дополнительная информация:

Этот метод не самый лучший, есть много мест, где можно улучшить, просто дать предложение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...