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:
- Optymalizowanie hierarchii układu
- Optymalizowanie widoków listy
- Usuwanie programów obsługi zdarzeń w działaniach
- Ograniczanie cyklu życia usług
- Zwalnianie zasobów po powiadomieniu
- Zwalnianie zasobów, gdy interfejs użytkownika jest ukryty
- Optymalizowanie zasobów obrazu
- Usuwanie nieużywanych zasobów obrazu
- Unikaj arytmetyki zmiennoprzecinkowych
- Okna dialogowe odrzucania
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 ListView
był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 IntentService
elementu , 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:
TrimMemoryRunningModerate
— aplikacja może chcieć zwolnić niektóre niepotrzebne zasoby.TrimMemoryRunningLow
— aplikacja powinna zwolnić niepotrzebne zasoby.TrimMemoryRunningCritical
— aplikacja powinna zwolnić dowolną liczbę niekrytycznych procesów, jak to możliwe.
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 IDisposable
element . 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 float
double
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ę.