Производительность Xamarin.Android

Существует множество методов повышения производительности приложений, созданных с помощью Xamarin.Android. Вместе они могут значительно снизить загрузку ЦП и сократить объем памяти, используемой приложением. Все они описаны в этой статье.

Общие сведения о производительности

Низкая производительность приложения проявляется по-разному. Из-за нее приложение может переставать отвечать на запросы, могут возникать задержки при прокрутке и сокращаться время работы батареи. Однако оптимизация производительности предусматривает не только правильную реализацию кода. Необходимо также учитывать эффективность работы пользователей. Например, чтобы повысить удобство работы, необходимо сделать так, чтобы выполнение операций не мешало пользователю выполнять другие действия.

Существует ряд методов повышения производительности приложений, созданных на платформе Xamarin.Android, в том числе воспринимаемой пользователем. К ним относятся:

Примечание.

Прежде чем прочитать эту статью, ознакомьтесь со статьей Кроссплатформенная производительность, в которой описываются универсальные для всех платформ способы оптимизации использования памяти и повышения производительности приложений, построенных с помощью платформы Xamarin.

Оптимизация иерархий макетов

Для каждого добавляемого в приложение макета выполняются инициализация, разметка и отрисовка. При наличии вложенных экземпляров LinearLayout, использующих параметр weight, этап разметки может быть достаточно ресурсоемким, поскольку каждый дочерний объект измеряется два раза. Использование вложенных экземпляров LinearLayout может привести к появлению глубокой иерархии представлений, что повлечет снижение производительности для макетов, которые используются много раз, таких как ListView. Таким образом, оптимизация макетов позволяет многократно повысить производительность.

Например, рассмотрим макет LinearLayout для строки представления списка, который содержит значок, название и описание. LinearLayout будет содержать ImageView, а также вертикальный объект LinearLayout, содержащий два экземпляра TextView:

<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>

Иерархия этого макета имеет 3 уровня, поэтому при его использовании для каждой строки ListView производительность заметно снижается. Чтобы оптимизировать этот макет, можно преобразовать его в плоскую структуру, как показано в следующем примере кода:

<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>

Количество уровней в иерархии уменьшено с 3 до 2, а один объект RelativeLayout заменил собой два экземпляра LinearLayout. Таким образом, при использовании макета для каждой строки ListView удастся добиться значительного повышения производительности.

Оптимизация представлений списков

Пользователи рассчитывают на быструю прокрутку и загрузку экземпляров ListView. Если каждая строка в представлении списка будет содержать иерархии представлений с глубоким уровнем вложения или сложные макеты, производительность прокрутки может заметно снижаться. Тем не менее существует несколько способов повысить производительность ListView:

  • Повторное использование представлений строк. Дополнительные сведения см. в разделе Повторное использование представлений строк.
  • Преобразование макетов в плоскую структуру, если это возможно.
  • Кэширование содержимого строки, извлеченного из веб-службы.
  • Предотвращение масштабирования изображений.

Совместное применение этих способов позволяет обеспечить плавную прокрутку экземпляров ListView.

Повторное использование представлений строк

Если в ListView отображаются сотни строк, на создание сотен объектов View затрачивается большой объем памяти, хотя при этом на экране одновременно отображается лишь несколько из них. Вместо этого в память можно загружать только многократно используемые объекты View, которые отображаются в строках на экране, загружая в них необходимое содержимое. Это позволяет избежать создания сотен дополнительных объектов и значительно сэкономить время и ресурсы памяти.

Таким образом, если строка скрывается с экрана, ее представление может быть помещено в очередь для повторного использования, как показано в следующем примере кода:

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;
}

По мере прокрутки списка ListView вызывает переопределение метода GetView, запрашивая отображение новых представлений. Если это возможно, неиспользуемое представление передается в параметре convertView. Если это значение равно null, код создает новый экземпляр View, иначе свойства convertView можно сбросить и использовать повторно.

Дополнительные сведения см. в разделе Повторное использование представления строк в статье Заполнение объекта ListView данными.

Удаление обработчиков событий в действиях

Действие, которое уничтожается в среде выполнения Android, по-прежнему может существовать в среде выполнения Mono. Таким образом, можно удалять обработчики событий для внешних объектов в методе Activity.OnPause, благодаря чему в среде выполнения не будет храниться ссылка на действие, которое было уничтожено.

В действии объявите обработчик или обработчики событий на уровне класса:

EventHandler<UpdatingEventArgs> service1UpdateHandler;

Затем реализуйте обработчики в действии, например, как в OnResume:

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

При выходе из состояния выполнения действия вызывается метод OnPause. В реализации OnPause удалите обработчики событий следующим образом:

App.Current.Service1.Updated -= service1UpdateHandler;

Ограничение продолжительности существования служб

При запуске службы Android выполняет соответствующий ей процесс. При этом выделяемая для процесса память не может использоваться где-то еще. Таким образом, если служба выполняется, но не используется, повышается риск снижения производительности приложения из-за нехватки памяти. Кроме того, в этом случае снижается эффективность переключения между приложениями, поскольку уменьшается число доступных для кэширования процессов в Android.

Время существования службы можно ограничить с помощью класса IntentService, который завершает собственную работу после обработки запустившего его намерения.

Освобождение ресурсов по уведомлению

В рамках жизненного цикла приложения обратный вызов метода OnTrimMemory позволяет получить уведомление о нехватке памяти устройства. Реализация этого обратного вызова позволяет прослушивать следующие уведомления о состоянии памяти:

  • TrimMemoryRunningModerate — приложению может потребоваться высвобождение некоторых ненужных ресурсов.
  • TrimMemoryRunningLow — приложению следует освободить ненужные ресурсы.
  • TrimMemoryRunningCritical — приложению следует освободить как можно больше некритических процессов.

Кроме того, при кэшировании процесса приложения обратный вызов OnTrimMemory может получать следующие уведомления о состоянии памяти:

  • TrimMemoryBackground — следует освободить ресурсы, которые можно эффективно и быстро перестроить в случае возвращения пользователя в приложение.
  • TrimMemoryModerate — высвобождение ресурсов может обеспечить кэширование других процессов в системе для повышения общей производительности.
  • TrimMemoryComplete — если объем доступной памяти не увеличится, процесс приложения в скором времени будет завершен.

В ответ на полученное уведомление должны быть высвобождены ресурсы, соответствующие его уровню.

Освобождение ресурсов при скрытии пользовательского интерфейса

Когда пользователь переходит в другое приложение, следует высвободить любые ресурсы, используемые интерфейсом текущего приложения. Это позволяет значительно повысить производительность кэширования процессов в среде Android и общую эффективность.

Чтобы получать уведомление, когда пользователь выходит из пользовательского интерфейса, реализуйте обратный вызов OnTrimMemory в классах Activity и прослушивание на уровне TrimMemoryUiHidden, указывающем на скрытие пользовательского интерфейса в представлении. Это уведомление будет получено только в том случае, если все компоненты пользовательского интерфейса приложения скрываются. Освобождение ресурсов пользовательского интерфейса при получении уведомления позволяет гарантировать быстрое возобновление доступа к ним при возвращении пользователя в приложение после выполнения других действий.

Оптимизация графических ресурсов

Изображения — это один из самых ресурсоемких ресурсов в приложениях. Они часто хранятся в высоком разрешении. При выводе изображения на экран используется разрешение, соответствующее характеристикам экрана. Если разрешение изображения превышает возможности экрана, следует уменьшить его масштаб.

Дополнительные сведения см. в разделе Оптимизация графических ресурсов в руководстве Кроссплатформенная производительность.

Ликвидация неиспользуемых графических ресурсов

Чтобы оптимизировать использование памяти, рекомендуется ликвидировать неиспользуемые ресурсы больших изображений. Тем не менее важно обеспечить надлежащее удаление изображений. Вместо явного вызова .Dispose() вы можете использовать преимущества инструкций using для эффективной работы с объектами IDisposable.

Например, класс Bitmap реализует интерфейс IDisposable. Создавая оболочку при создании экземпляра объекта BitMap в блоке using, можно гарантировать, что при выходе из блока он будет ликвидирован надлежащим образом:

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

Дополнительные сведения об освобождении высвобождаемых ресурсов см. в разделе Освобождение ресурсов IDisposable.

Исключение арифметических операций с плавающей запятой

На устройствах Android арифметические операции с плавающей запятой выполняются примерно в два раза медленнее операций с целыми числами. Соответственно, следует по возможности использовать операции с целыми числами. Тем не менее на современном оборудовании разница между арифметическими операциями со значениями типов float и double незаметна.

Примечание.

Даже при выполнении целочисленных арифметических операций некоторым процессорам не хватает ресурсов для деления. В связи с этим целочисленные операции деления и получения модуля часто выполняются в программном обеспечении.

Закрытие диалоговых окон

При использовании класса ProgressDialog (а также любого диалогового окна или оповещения) вместо вызова метода Hide по завершении работы с диалоговым окном следует вызывать метод Dismiss. В противном случае диалоговое окно по-прежнему будет существовать и создавать утечку памяти за счет хранения ссылки на действие.

Итоги

В этой статье были рассмотрены методы повышения производительности приложений, созданных на платформе Xamarin.Android. Вместе они могут значительно снизить загрузку ЦП и сократить объем памяти, используемой приложением.