Windows Phone Mango - agenci  

Udostępnij na: Facebook

Autor: Marcin Kruszyński

Opublikowano: 2011-07-14

Pobierz i uruchom

Windows Phone 7.1 (nazwa kodowa „Mango”) przynosi wiele możliwości w zakresie multitaskingu. Zmianie uległ sam cykl życia aplikacji, w którym aplikacje, przysłonięte przez aplikację znajdującą się na pierwszym planie, domyślnie przechodzą w stan uśpienia i pozostają w pamięci (pozwala to na szybkie przełączanie między aplikacjami, tzw. fast switching). Mamy możliwość korzystania z gotowych serwisów systemowych do: ustawiania notyfikacji (przypomnień i alarmów), trasferu plików w tle, odtwarzania i strumieniowania audio w tle. Możemy także pisać agentów z określonymi przez nas funkcjonalnościami i zlecać ich wykonywanie w tle według wybranego planu. To ostatnie zagadnienie zostanie tutaj dokładnie omówione.   

Po przeczytaniu tego artykułu będziesz:

  • wiedział, co to jest agent i w jaki sposób działa,
  • znał rodzaje agentów, ich możliwości, ograniczenia i zastosowania,
  • potrafił zaimplementować prostego agenta.

Wprowadzenie

Na rysunku 1 przedstawiono aplikację z agentem i jej interakcje z elementami udostępnianymi przez system operacyjny.

Rys.1. Architektura aplikacji z agentem.

Agent wykonuje się w procesie innym, niż główna aplikacja, dzięki temu może działać także wtedy, gdy nie jest ona aktywna. Z punktu widzenia systemu operacyjnego aplikacja z interfejsem użytkownika i jej agent są traktowane jako jedna aplikacja. Agent współdzieli z główną aplikacją dostęp do Isolated Storage, co pozwala także współdzielić dostęp do lokalnej bazy danych SQL Server CE (opisanej w artykule Mango: Lokalna baza danych). Każda aplikacja może mieć tylko jednego agenta, w postaci oddzielnego projektu Visual Studio.

Każdy agent dziedziczy po klasie BackgroundAgent. W praktyce korzystamy z kilku jej klas pochodnych, by zaimplementować agentów:

  • zaplanowanych – z określoną przez nas funkcjonalnością, wykonywanych według określonego planu (dziedziczymy po klasie ScheduledTaskAgent),
  • audio – wyspecjalizowanych w zakresie odtwarzania lub strumieniowania audio (dziedziczymy odpowiednio po klasie AudioPlayerAgent lub AudioStreamingAgent). 

W tym artykule zostanie omówiony pierwszy, najczęściej spotykany rodzaj agentów.

Możliwości i ograniczenia

Główna aplikacja rejestruje zaplanowanego agenta w postaci zadania. Determinuje ono plan wykonywania agenta. Wyróżniamy dwa rodzaje tasków:

  • periodyczny (PeriodicTask) – okresowo uruchamia się na niewielką ilość czasu, ma duże ograniczenia na zasoby systemowe (np. cykle procesora, pamięć), odpowiedni dla szybkich zadań tj. wysyłanie informacji o lokalizacji urządzenia, cachowanie niewielkiej ilości danych czy ich przyrostowa synchronizacja.
  • zasobochłonny (ResourceIntensiveTask) – uruchamia się rzadko, gdy zasoby telefonu są mniej ograniczone (np. przy podłączeniu do zewnętrznego źródła zasilania i do sieci Wi-Fi), może wykonywać się przez dłuższy czas i ma dostęp do większej ilości zasobów systemowych, typowe zastosowanie – synchronizacja dużej ilości danych, kiedy użytkownik nie korzysta aktywnie z telefonu.

Agent może zostać zarejestrowany jako PeriodicTask lub ResourceIntensiveTask, lub jako oba. W danym czasie może wykonywać się tylko jedna instancja danego agenta.

Z zaplanowanymi agentami wiąże się kilka ograniczeń:

  • Niewspierane API – nie mogą m.in wyświetlać UI, korzystać z XNA, sensorów, kamery, mikrofonu, odtwarzać multimediów, używać systemowych tasków (launcherów i chooserów), notifikacji push, dodawać zaplanowanych akcji (przypomnień, alarmów, agentów), zlecać przesyłania plików w tle. Pozwalają m.in aktualizować i usuwać kafelki, wyświetlać popup notyfikacji toast, korzystać z Isolated Storage, lokalnej bazy danych, komunikacji sieciowej, lokalizacji geograficznej (wartości aktualizowane co 10 min). Szczegółowe informacje na ten temat znajdziesz w dokumentacji na stronie Unsupported APIs for Scheduled Tasks for Windows Phone.
  • Limit na zajmowaną pamięć – 5 MB (agenci audio 15 MB), przekroczenie tego limitu powoduje bezzwłoczne zakończenie procesu agenta.
  • Konieczność odnawiania rejestracji – rejestracja wygasa po 14 dniach (możemy ustawić termin wygaśnięcia mieszczący się w tym okresie). Kiedy aplikacja się wykonuje, może przedłużyć termin wygaśnięcia taska (na dwa tygodnie od bieżącego czasu). 

Agenci periodyczni i zasobochłonni (taski periodyczne i zasobochłonne) mają także charakterystyczne dla siebie ograniczenia. W przypadku agentów periodycznych są to:

  • okres 30 min – typowo wykonują się co 30 min, ze względu na optymalizację zużycia baterii system może wykonać je razem z innymi procesami w tle, co może powodować przesunięcia w czasie do 10 min,
  • czas wykonania 15 s – typowo wykonują się przez 15 s, z powodu różnych czynników agent może zostać wcześniej zakończony,
  • tryb oszczędzania baterii – po ustawieniu przez użytkownika takiego trybu pracy dla telefonu agenci mogą się nie uruchamiać,
  • limity urządzenia – limit na liczbę zaplanowanych agentów (różny dla różnych telefonów, np. 6), mniejszy limit, po którym użytkownik dostanie ostrzeżenie o dużej liczbie uruchomionych agentów i szybszym zużywaniu baterii. 

Natomiast dla agentów zasobochłonnych:

  • czas wykonania 10 min – typowo, różne czynniki mogą spowodować wcześniejsze zakończenie pracy agenta,
  • warunki do uruchomienia – zewnętrzne zasilanie, połączenie sieciowe przez Wi-Fi lub komputer, stopień naładowania baterii większy niż 90%, zablokowany ekran, nieprowadzenie rozmowy telefonicznej. Jeśli po uruchomieniu agenta stan urządzenia zmieni się na taki, w którym warunki przestają być spełniane, wykonywanie agenta zostaje bezzwłocznie zakończone. W danym czasie wykonuje się jeden agent tego typu. Może się zdarzyć, że taki agent nigdy się nie wykona na danym urządzeniu, jeśli np. użytkownik nie ma dostępu do sieci Wi-Fi czy komputera.

Implementacja

Zaprezentujemy teraz praktyczny przykład. Załóżmy, że mamy aplikację zarządzającą zadaniami z określonymi terminami realizacji. Na ekranie startowym użytkownik może przypinać kafelki dla poszczególnych zadań, które nawigują bezpośrednio do odpowiednich stron aplikacji (nowa funkcjonalność w Windows Phone 7.1). W zależności od czasu pozostającego do terminu realizacji, kafelki mają następujące kolory: zielony (nie mniej niż jeden dzień), żółty (mniej niż jeden dzień) lub czerwony (przekroczony termin). Chcemy co jakiś czas aktualizować ich kolory, także wtedy, gdy aplikacja się nie wykonuje. Cel ten zrealizujemy za pomoca periodycznego agenta. Jego działanie przedstawia rysunek 2 (dodatkowo obserwujemy tutaj kafelki dwustronne, pokazujące co jakiś czas swój rewers – nowa funkcjonalność w Windows Phone 7.1).

Rys.2. Okresowa zmiana koloru kafelków przez agenta.

Budowanie agenta zaczynamy od stworzenia nowego oddzielnego projektu Visual Studio typu Windows Phone Task Scheduler Agent. Następnie do projektu z główną aplikacją dodajemy referencję do tego projektu. Powoduje to modyfikację manifestu aplikacji, zostaje dodany do niego wpis o tasku wykonywanym w tle. Przykładowa konfiguracja przedstawia się następująco:

<Tasks>
      <DefaultTask Name="_default" NavigationPage="/Views/MainView.xaml" />
      <ExtendedTask Name="BackgroundTask">
        <BackgroundServiceAgent Specifier="ScheduledTaskAgent" Name="PhoneAppAgent"
         Source="PhoneAppAgent" Type="PhoneAppAgent.TaskScheduler" />
      </ExtendedTask>
    </Tasks>

Kod agenta aktualizującego kafelki może wyglądać następująco:

public class TaskScheduler : ScheduledTaskAgent
    {        
        protected override void OnInvoke(ScheduledTask task)
        {            
            UpdateTiles();         
            NotifyComplete();           
        }

        private void UpdateTiles()
        {           
            var repository = TaskRepository.Load();

            if (repository == null)
                return;
            
            foreach (var tile in ShellTile.ActiveTiles)
            {
                var taskid = GetTaskId(tile);                

                if (taskid != Guid.Empty)
                {                    
                    var task = repository.Find(taskid);

                    if (task != null)
                        UpdateTile(tile, task);                    
                }
            }          
        }

        private Guid GetTaskId(ShellTile tile)
        {
            Guid taskid = Guid.Empty;

            if (tile.NavigationUri.IsAbsoluteUri)
            {
                var parameters = tile.NavigationUri.GetParams();

                if (parameters.ContainsKey("taskid"))               
                    taskid = new Guid(parameters["taskid"]);                
            }

            return taskid;
        }              

        private void UpdateTile(ShellTile tile, Task task)
        {
            var tileData = new StandardTileData
            { 
                BackgroundImage = task.GetTileBackgroundUri(),
                BackBackgroundImage = task.GetTileBackBackgroundUri()
            };
            
            tile.Update(tileData);        
        }      
    }

W przypadku zaplanowanych agentów metoda OnInvoke(ScheduledTask) jest kluczowa – system operacyjny wywołuje ją przy uruchamianiu agenta. Na podstawie przekazanego do niej parametru możemy sprawdzić, czy agent uruchamiany jest jako PeriodicTask, czy jako ResourceIntensiveTask i wywołać odpowiedni kod. W prezentowanym przykładzie zamierzamy uruchamiać naszego agenta zawsze w postaci taska periodycznego, dlatego nie sprawdzamy tego parametru. Po zakończeniu przewidzianych przez nas operacji, w zależności od tego, czy zakończyły się sukcesem, czy niepowodzeniem, powinniśmy wywołać metodę NotifyCompleted() lub Abort(). Ma to za zadanie poinformować system operacyjny o tym, że dany agent nie potrzebuje już dłużej się wykonywać. Umożliwia to systemowi zaplanowanie wykonania innych agentów. Po wywołaniu metody Abort() agent nie zostanie już więcej uruchomiony, dopóki aplikacja nie dokona ponownie jego rejestracji (po ustaleniu przyczyny błędu).

W prezentowanym kodzie oprócz gotowego API do aktualizowania kafelków zastosowano kilka własnych klas i metod, które teraz postaramy się wyjaśnić. Lista z zadaniami użytkownika (obiekty klasy Task) trzymana jest w postaci pliku XML w Isolated Storage. Dostęp do tych danych zapewnia klasa TaskRepository. Korzysta z niej zarówno aplikacja, jak i jej agent. W metodzie GetTaskId wykorzystujemy extension metodę GetParams, która dla podanego Uri zwraca słownik z parametrami. Metoda UpdateTile korzysta z extension metod GetTileBackgroundUri oraz GetTileBackBackgroundUri. Implementacja pierwszej z nich została zrealizowana w następujący sposób:

public static class TaskExtensions
    {
        public static Uri GetTileBackgroundUri(this Task task)
        {
            var remaining = task.DateTime - DateTime.Now;

            if (remaining.TotalSeconds < 0)
            {
                return new Uri("Images/RedAgentTile.png", UriKind.Relative);
            }

            if (remaining.Days < 1)
            {
                return new Uri("Images/YellowAgentTile.png", UriKind.Relative);
            }

            return new Uri("Images/GreenAgentTile.png", UriKind.Relative);
        }

     …        
    }

Implementacja drugiej metody jest analogiczna, operuje jedynie na innym zestawie obrazków.

Zbudujmy teraz w naszej aplikacji prosty interfejs użytkownika z kontrolką CheckBox, pozwalający rejestrować agenta w systemie i usuwać go. Strona o takiej funkcjonalności może być zrealizowana w następujący sposób:

bool ignoreCheckBoxEvents = false;

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);

        ignoreCheckBoxEvents = true;

        var periodicAgent = ScheduledActionService.Find("MyTasksPeriodicAgent");
        if (periodicAgent == null || periodicAgent.IsScheduled == false)
        {
            PeriodicCheckBox.IsChecked = false;
        }
        else
        {
            PeriodicCheckBox.IsChecked = true;
        }           

        ignoreCheckBoxEvents = false;
    }

    private void PeriodicCheckBox_Checked(object sender, RoutedEventArgs e)
    {
        if (ignoreCheckBoxEvents)
            return;
        StartPeriodicAgent();
    }

    private void PeriodicCheckBox_Unchecked(object sender, RoutedEventArgs e)
    {
        if (ignoreCheckBoxEvents)
            return;
        StopPeriodicAgent();
    }

    private void StartPeriodicAgent()
    {
         var periodicTask = new PeriodicTask("MyTasksPeriodicAgent");

         periodicTask.Description = "Updates the My Tasks application tiles.";

         if (ScheduledActionService.Find(periodicTask.Name) != null)
         {
             StopPeriodicAgent();
         }

         ScheduledActionService.Add(periodicTask);
    }

    private void StopPeriodicAgent()
    {
         ScheduledActionService.Remove("MyTasksPeriodicAgent");
    }

Przy tworzeniu instancji PeriodicTask podajemy w konstruktorze nazwę dla taska. Należy ustawić także jego właściwość Description, której wartość będzie widoczna w systemowym panelu do zarządzania usługami w tle (rys. 3). Po zdeaktywowaniu taska z poziomu panelu, użytkownik może ponownie go aktywować po uruchomieniu związanej z nim aplikacji. Przed zarejestrowaniem taska za pomocą metody Add(ScheduledAction) powinniśmy sprawdzić, czy task o takiej nazwie nie został już zarejestrowany. Wtedy najpierw powinniśmy go usunąć. 

Rys.3. Systemowy panel do zarządzania usługami w tle.

Budowanie agenta zasobochłonnego przebiegałoby w analogiczny sposób, z tą jednak różnicą, że zamiast PeriodicTask używalibyśmy ResourceIntensiveTask.

Podsumowanie

W tym artykule zapoznaliśmy się z jednym z aspektów multitaskingu w Windows Phone 7.1 – agentami. Rozumiemy, w jaki sposób działają, dlaczego mogą wykonywać się w tle. Znamy rodzaje agentów, ich ograniczenia i zastosowania. Potrafimy zaimplementować prostego agenta.