Wydajność platformy Xamarin.Android

Istnieje wiele technik zwiększania wydajności aplikacji utworzonych za pomocą platformy Xamarin.Android. Łącznie te techniki mogą znacznie zmniejszyć ilość pracy wykonywanej przez procesor CPU i ilość pamięci zużywanej przez aplikację. W tym artykule opisano i omówiono te techniki.

Przegląd wydajności

Niska wydajność aplikacji przedstawia się na wiele sposobów. Może to sprawić, że aplikacja wydaje się nie odpowiadać, może powodować powolne przewijanie i może zmniejszyć żywotność baterii. Jednak optymalizacja wydajności wymaga więcej niż tylko zaimplementowania wydajnego kodu. Należy również rozważyć środowisko użytkownika dotyczące wydajności aplikacji. Na przykład zapewnienie, że operacje są wykonywane bez blokowania użytkownikowi wykonywania innych działań, mogą pomóc w ulepszaniu środowiska użytkownika.

Istnieje wiele technik zwiększania wydajności i postrzeganej wydajności aplikacji utworzonych za pomocą platformy Xamarin.Android. To na przykład:

Uwaga

Przed przeczytaniem tego artykułu należy najpierw przeczytać artykuł Wydajność międzyplatformowa, w którym omówiono techniki specyficzne dla platformy, aby poprawić użycie pamięci i wydajność aplikacji utworzonych przy użyciu platformy Xamarin.

Optymalizowanie hierarchii układu

Każdy układ dodany do aplikacji wymaga inicjowania, układu i rysunku. Przekazywanie układu może być kosztowne podczas zagnieżdżania LinearLayout wystąpień używających parametru weight , ponieważ każde dziecko będzie mierzone dwa razy. Użycie zagnieżdżonych wystąpień LinearLayout programu może prowadzić do głębokiej hierarchii widoku, co może spowodować niską wydajność układów, które są zawyżone wielokrotnie, na przykład w obiekcie ListView. Dlatego ważne jest, aby takie układy zostały zoptymalizowane, ponieważ korzyści z wydajności zostaną następnie pomnożone.

Rozważmy LinearLayout na przykład wiersz widoku listy z ikoną, tytułem i opisem. Element LinearLayout będzie zawierać element ImageView i pionowy LinearLayout , który zawiera dwa TextView wystąpienia:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="5dip">
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="5dip"
        android:src="@drawable/icon" />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:text="Mei tempor iuvaret ad." />
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:singleLine="true"
            android:ellipsize="marquee"
            android:text="Lorem ipsum dolor sit amet." />
    </LinearLayout>
</LinearLayout>

Ten układ jest głęboki na 3 poziomach i jest marnotrawny, gdy zawyżony dla każdego ListView wiersza. Można go jednak ulepszyć, spłaszczając układ, jak pokazano w poniższym przykładzie kodu:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="5dip">
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="5dip"
        android:src="@drawable/icon" />
    <TextView
        android:id="@+id/secondLine"
        android:layout_width="fill_parent"
        android:layout_height="25dip"
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:text="Lorem ipsum dolor sit amet." />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_above="@id/secondLine"
        android:layout_alignWithParentIfMissing="true"
        android:gravity="center_vertical"
        android:text="Mei tempor iuvaret ad." />
</RelativeLayout>

Poprzednia hierarchia 3-poziomowa została zredukowana do hierarchii 2-poziomowej, a jeden RelativeLayout zastąpił dwa LinearLayout wystąpienia. Znaczący wzrost wydajności zostanie uzyskany podczas rozszerzania układu dla każdego ListView wiersza.

Optymalizowanie widoków listy

Użytkownicy oczekują bezproblemowego przewijania i szybkiego ładowania dla ListView wystąpień. Jednak wydajność przewijania może mieć miejsce, gdy każdy wiersz widoku listy zawiera głęboko zagnieżdżone hierarchie widoków lub gdy wiersze widoku listy zawierają złożone układy. Istnieją jednak techniki, których można użyć, aby uniknąć niskiej ListView wydajności:

  • Ponowne używanie widoków wierszy Aby uzyskać więcej informacji, zobacz Ponowne używanie widoków wierszy.
  • Spłaszczać układy, jeśli to możliwe.
  • Buforuj zawartość wiersza pobraną z usługi internetowej.
  • Unikaj skalowania obrazów.

Zbiorczo te techniki mogą pomóc w ListView bezproblemowym przewijaniu wystąpień.

Ponowne używanie widoków wierszy

Podczas wyświetlania setek wierszy w obiekcie ListViewbyłoby to strata pamięci do utworzenia setek View obiektów, gdy tylko niewielka liczba z nich jest wyświetlana na ekranie jednocześnie. Zamiast tego tylko View obiekty widoczne w wierszach na ekranie można załadować do pamięci, a zawartość jest ładowana do tych ponownie użytych obiektów. Zapobiega to utworzeniu wystąpienia setek dodatkowych obiektów, oszczędzaniu czasu i pamięci.

W związku z tym, gdy wiersz zniknie z ekranu, jego widok można umieścić w kolejce do ponownego użycia, jak pokazano w poniższym przykładzie kodu:

public override View GetView(int position, View convertView, ViewGroup parent)
{
   View view = convertView; // re-use an existing view, if one is supplied
   if (view == null) // otherwise create a new one
       view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
   // set view properties to reflect data for the given row
   view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
   // return the view, populated with data, for display
   return view;
}

Gdy użytkownik przewija, ListView wywołuje GetView przesłonięcie, aby zażądać wyświetlenia nowych widoków — jeśli jest dostępny, przekazuje nieużywany widok w parametrze convertView . Jeśli ta wartość to null kod tworzy nowe View wystąpienie, w przeciwnym razie convertView właściwości można zresetować i ponownie użyć.

Aby uzyskać więcej informacji, zobacz Temat Ponowne używanie widoku wiersza w wypełnianiu elementu ListView przy użyciu danych.

Usuwanie programów obsługi zdarzeń w działaniach

Gdy działanie zostanie zniszczone w środowisku uruchomieniowym systemu Android, nadal może być aktywne w środowisku uruchomieniowym Mono. W związku z tym usuń programy obsługi zdarzeń do obiektów zewnętrznych, Activity.OnPause aby zapobiec przechowywaniu odwołania do działania, które zostało zniszczone.

W działaniu zadeklaruj programy obsługi zdarzeń na poziomie klasy:

EventHandler<UpdatingEventArgs> service1UpdateHandler;

Następnie zaimplementuj programy obsługi w działaniu, na przykład w pliku OnResume:

service1UpdateHandler = (object s, UpdatingEventArgs args) => {
    this.RunOnUiThread (() => {
        this.updateStatusText1.Text = args.Message;
    });
};
App.Current.Service1.Updated += service1UpdateHandler;

Po zakończeniu działania jest wywoływany stan OnPause uruchomienia. W implementacji OnPause usuń programy obsługi w następujący sposób:

App.Current.Service1.Updated -= service1UpdateHandler;

Ograniczanie cyklu życia usług

Po uruchomieniu usługi system Android zachowuje uruchomiony proces usługi. Sprawia to, że proces jest kosztowny, ponieważ jego pamięć nie może być stronicowana ani używana w innym miejscu. Pozostawienie usługi uruchomionej, gdy nie jest wymagane, zwiększa zatem ryzyko, że aplikacja wykazuje niską wydajność z powodu ograniczeń pamięci. Może również sprawić, że przełączanie aplikacji będzie mniej wydajne, ponieważ zmniejsza liczbę procesów, które system Android może buforować.

Żywotność usługi może być ograniczona przy użyciu IntentServiceelementu , który kończy się po obsłużeniu intencji, która ją uruchomiła.

Zwalnianie zasobów po powiadomieniu

Podczas cyklu życia aplikacji wywołanie zwrotne udostępnia powiadomienie, OnTrimMemory gdy pamięć urządzenia jest niska. To wywołanie zwrotne należy zaimplementować w celu nasłuchiwania następujących powiadomień na poziomie pamięci:

Ponadto, gdy proces aplikacji jest buforowany, następujące powiadomienia na poziomie pamięci mogą być odbierane przez OnTrimMemory wywołanie zwrotne:

  • TrimMemoryBackground — zwalnianie zasobów, które mogą być szybko i wydajnie odbudowywane, jeśli użytkownik wróci do aplikacji.
  • TrimMemoryModerate — zwalnianie zasobów może pomóc systemowi zachować inne procesy buforowane w celu uzyskania lepszej ogólnej wydajności.
  • TrimMemoryComplete — proces aplikacji zostanie wkrótce zakończony, jeśli nie zostanie wkrótce odzyskana większa ilość pamięci.

Powiadomienia powinny odpowiadać, zwalniając zasoby na podstawie odebranego poziomu.

Zwalnianie zasobów, gdy interfejs użytkownika jest ukryty

Zwolnij wszystkie zasoby używane przez interfejs użytkownika aplikacji, gdy użytkownik przejdzie do innej aplikacji, ponieważ może znacznie zwiększyć pojemność systemu Android dla buforowanych procesów, co z kolei może mieć wpływ na jakość środowiska użytkownika.

Aby otrzymać powiadomienie, gdy użytkownik zakończy działanie interfejsu użytkownika, zaimplementuj OnTrimMemory wywołanie zwrotne w Activity klasach i nasłuchuje TrimMemoryUiHidden poziomu, co oznacza, że interfejs użytkownika jest ukryty przed widokiem. To powiadomienie zostanie odebrane tylko wtedy, gdy wszystkie składniki interfejsu użytkownika aplikacji staną się ukryte przed użytkownikiem. Zwalnianie zasobów interfejsu użytkownika po odebraniu tego powiadomienia gwarantuje, że jeśli użytkownik wróci z innego działania w aplikacji, zasoby interfejsu użytkownika są nadal dostępne, aby szybko wznowić działanie.

Optymalizowanie zasobów obrazu

Obrazy to niektóre z najdroższych zasobów używanych przez aplikacje i są często przechwytywane w wysokiej rozdzielczości. W związku z tym podczas wyświetlania obrazu wyświetl go w rozdzielczości wymaganej dla ekranu urządzenia. Jeśli obraz ma wyższą rozdzielczość niż ekran, powinien zostać przeskalowany w dół.

Aby uzyskać więcej informacji, zobacz Optymalizowanie zasobów obrazów w przewodniku dotyczącym wydajności międzyplatformowych.

Usuwanie nieużywanych zasobów obrazu

Aby zaoszczędzić użycie pamięci, dobrym pomysłem jest usuwanie dużych zasobów obrazów, które nie są już potrzebne. Należy jednak upewnić się, że obrazy są prawidłowo usuwane. Zamiast używać jawnego .Dispose() wywołania, możesz skorzystać z instrukcji , aby zapewnić poprawne użycie IDisposable obiektów.

Na przykład klasa Bitmap implementuje IDisposableelement . Zawijanie BitMap wystąpienia obiektu w using bloku gwarantuje, że będzie on prawidłowo usuwany po wyjściu z bloku:

using (Bitmap smallPic = BitmapFactory.DecodeByteArray(smallImageByte, 0, smallImageByte.Length))
{
    // Use the smallPic bit map here
}

Aby uzyskać więcej informacji na temat wydawania jednorazowych zasobów, zobacz Release IDisposable Resources (Zwalnianie zasobów IDisposable).

Unikaj arytmetyki zmiennoprzecinkowych

Na urządzeniach z systemem Android arytmetyka zmiennoprzecinkowa jest o około 2 razy wolniejsza niż arytmetyka całkowita. W związku z tym zastąp arytmetyczną zmiennoprzecinkową arytmetyczną całkowitą, jeśli jest to możliwe. Nie ma jednak różnicy czasu wykonywania między floatdouble arytmetyczną arytmetyczną na ostatnim sprzęcie.

Uwaga

Nawet w przypadku arytmetyki całkowitej niektóre procesory CPU nie mają możliwości dzielenia sprzętu. W związku z tym operacje dzielenia liczb całkowitych i modulu są często wykonywane w oprogramowaniu.

Okna dialogowe odrzucania

Jeśli używasz ProgressDialog klasy (lub dowolnego okna dialogowego lub alertu), zamiast wywoływać Hide metodę po zakończeniu celu okna dialogowego, wywołaj metodę Dismiss . W przeciwnym razie okno dialogowe będzie nadal aktywne i będzie wyciekać działanie, przechowując do niego odwołanie.

Podsumowanie

W tym artykule opisano i omówiono techniki zwiększania wydajności aplikacji utworzonych za pomocą platformy Xamarin.Android. Łącznie te techniki mogą znacznie zmniejszyć ilość pracy wykonywanej przez procesor CPU i ilość pamięci zużywanej przez aplikację.