Цель лабораторной работы - знакомство с основными методами и способами управления памятью при создании мобильного приложения.
Для выполнения данной лабораторной работы необходимо наличие ранее созданного проекта. Изучив теоретические основы управления памятью при создании приложений для мобильных устройств, необходимо применить описанные приемы и методы в созданном приложении (разумеется, при наличии таковой возможности). Результатом лабораторной работы должна являться таблица сравнения результатов выполнения алгоритмов до оптимизации и после, при аналогичной программно - аппаратной базе.
Dispose() в целесообразных ситуациях, если в использовании данного метода нет необходимости - обосновать причины такого решения.ОЗУ мобильных устройств имеют гораздо меньший объем и, как правило, не позволяют надлежащим образом реализовать механизмы, использующие дополнительные внешние накопители для организации быстрого обмена страницами с памятью. Кроме того, в условиях ограниченных ресурсных возможностей на мобильных устройствах могут быть реализованы лишь сравнительно простые механизмы очистки памяти от неиспользуемых объектов. Это означает, что небрежно организованное управление памятью в приложениях для мобильных устройств будет иметь весьма заметные отрицательные последствия как на макроскопическом, так и на микроскопическом уровнях. По сравнению с настольными компьютерами мобильные устройства гораздо менее терпимы к любым просчетам в управлении памятью.
Важно также понимать, что, несмотря на то, что каждое следующее поколение устройств по аппаратным характеристикам превосходит предшествующее, нельзя просто "отложить до лучших времен" проблемы управления памятью. Требования к устройствам также возрастают от поколения к поколению, следовательно, актуальность существующих в данный момент проблем в значительной степени сохранится.
Полезно рассортировать данные и ресурсы приложения, с которыми вы работаете, на две разновидности:
Эти данные представляют собой ресурсы, необходимые для эффективного функционирования приложения, а также отображения данных и манипулирования ими.
Рассмотрим в качестве примера простое приложение, ориентированное на работу с базами данных. Данное приложение обеспечивает хранение и обработку медицинских данных пациентов. Данные хранятся в базе данных и загружаются порциями, которые соответствуют отдельным пациентам, причем для загрузки или сохранения данных о пациенте требуется ввести защитный пароль. Для приложений такого рода можно выделить пять различных дискретных состояний, в которых пользователю для выполнения соответствующих задач предоставляются различные пользовательские интерфейсы. Такими состояниями являются следующими:
Некоторые из этих состояний разделяют общие ресурсы. Например, графическое перо черного цвета, или шрифт определенного размера, или растровое изображение могут использоваться в нескольких из перечисленных выше состояний. В соединении с базой данных нуждаются два состояния. Кроме того, некоторые объекты могут не требоваться для всех без исключения состояний, но их создание занимает длительное время; в этом случае целесообразно прибегнуть к кэшированию объектов, которое предварительно следует протестировать с точки зрения влияния на производительность.
Dispose, то вы всегда можете вызвать этот метод, если необходимость в использовании данного объекта в приложении отпала. Метод Dispose () должен вызываться тогда, когда вы собираетесь удалить любые переменные ссылки на него, чтобы предоставить сборщику мусора возможность удалить этот объект из памяти. Тем самым гарантируется, что дорогостоящий объект, представляемый ресурсом, будет немедленно освобожден. Как и в других ситуациях, имеющих отношение к управлению памятью, если в случае приложений для настольных компьютеров такой подход является просто плодотворным, то в случае мобильных приложений, испытывающих дефицит системных ресурсов (таких, например, как дескрипторы операционной системы), его применение жизненно необходимо.Пользовательские данные представляют собой фактические данные, которые может просматривать или которыми может манипулировать пользователь приложения. Управление объемом и временем жизни пользовательских данных, хранящихся в па мяти, может оказаться более сложным по сравнению с управлением служебными данными приложения, поскольку в зависимости от структуры и назначения приложения природа данных, сохраняемых в памяти, может быть самой различной. Пользовательские данные шахматной игры отличаются своей структурой от пользовательских данных истории болезни пациента. Состояние шахматной доски может храниться в целочисленном массиве фиксированных размеров. Объем данных истории болезни не может быть заранее определен; эти данные могут включать в себя результаты измерений, текстовые записи, изображения, ссылки на дополнительные данные и почти неограниченный объем любой другой подходящей информации.
Для управления потенциально сложными пользовательскими данными целесообразно использовать подход, предполагающий создание класса с хорошо продуманной инкапсуляцией, который и реализует управление состоянием, хранящимся в памяти в каждый момент времени. Этот класс отвечает за загрузку новых данных и освобождение памяти от старых данных, когда необходимость в них отпадает. Он управляет доступом к пользовательским данным извне и создает иллюзию неограниченных запасов памяти. Любой другой код приложения, осуществляющий доступ к данным извне инкапсулирующего класса, ничего не должен знать о внутреннем состоянии этого класса, реализующего фактическое управление пользовательскими данными. Возложение ответственности за управление всеми пользовательскими данными на определенный класс обеспечивает значительную гибкость в процессе проектирования. Некоторые из преимуществ такого подхода перечисляются ниже:
Для размещения объектов в памяти существуют две стратегии:
Приведенный ниже пример кода иллюстрирует два способа отложенного создания и кэширования глобально используемых графических ресурсов. Существует два способа создания объектов:
GraphicsGlobals. PlayerBitmapsCollection()/. Если массив изображений уже загружен в память, функция незамедлительно возвращает кэшированный объект. В противном случае отдельные ресурсы изображений сначала загружаются в массив и лишь затем возвращаются. Если приложение переходит в состояние, в котором пребывание изображений в памяти не требуются, код приложения может выполнить вызов GraphicsGlobals.g_PlayerBitmapsCollection_CleanUp();, в результате чего произойдет освобождение растровых ресурсов и массива.
Системные ресурсы, задействованные для обслуживания растровых изображений, будут немедленно освобождены, а управляемая память, которую занимали эти объекты, будет соответствующим образом восстановлена в процессе сборки мусора.GraphicsGlobals .g_GetBlackPen ();), она создает его экземпляр. В случае часто используемых ресурсов такой подход оказывается намного более эффективным, чем постоянное создание и уничтожение экземпляров ресурса всякий раз, когда он требуется для выполнения того или иного фрагмента кода. Создавая приведенный ниже код, я допустил, что все ресурсы должны освобождаться одновременно, и написал функцию ( GraphicsGlobals.g_CleanUpDrawingResources (); ), которая освобождает все каптированные ресурсы, которые были созданы.
Эта функция должна вызываться тогда, когда приложение переходит в состояние, в котором эти ресурсы не требуются.public static void g_PlayerBitmapsCollection_CleanUp(){
//Если не загружено ни одно изображение, то и память освобождать не от чего if(s_colPlayerBitmaps == null)
{ return; }
//Дать указание каждому из этих объектов освободить
//любые удерживаемые ими неуправляемые ресурсы s_Player_Bitmapl.Dispose();
s_Player_Bitmap2.Dispose();
s_Player_Bitmap3.Dispose();
s_Player_Bitmap4.Dispose();
//Обнулить каждую из этих переменных, чтобы им не соответствовали
//никакие объекты в памяти
s_Player_Bitmapl = null;
s_Player_Bitmap2 = null;
s_Player_Bitmap3 = null;
s_Player_Bitmap4 = null;
//Избавиться от массива s_colPlayerBitmaps = null;
//Функция: возвращает коллекцию изображений
public static System.Collections.ArrayList g_PlayerBitmapsCollection()
{
//
//Если изображения уже загружены, их достаточно только возвратить
//
if(s_colPlayerBitmaps != null) {return s_colPlayerBitmaps;)
//Загрузить изображения как ресурсы из исполняемого двоичного файла
System.Reflection.Assembly thisAssembly =System.Reflection.Assembly.GetExecutingAssembly();
System.Reflection.AssemblyName thisAssemblyName =thisAssembly.GetName();
string assemblyName = thisAssemblyName.Name; /
/Загрузить изображения
s_Player_Bitmapl = new System.Drawing.Bitmap(thisAssembly.GetManifestResourceStream(assemblyName +
".HankJRightRunl.bmp"));
s_Player_Bitmap2 = new System.Drawing.Bitmap(thisAssembly.GetManifestResourceStream(assemblyName +
".Hank_RightRun2.bmp"));
s_Player_Bitmap3 = new System.Drawing.Bitmap(thisAssembly.GetManifestResourceStream(assemblyName +
".Hank_LeftRunl.bmp"));
s_Player_Bitmap4 = new System.Drawing.Bitmap(thisAssembly.GetManifestResourceStream(assemblyName +
".Hank_LeftRun2.bmp"));
//Добавить изображения в коллекцию
s_colPlayerBitmaps = new System.Collections.ArrayList();
s_colPlayerBitmaps.Add(s_Player_Bitmapl);
s_colPlayerBitmaps.Add(s_Player_Bitmap2);
s_colPlayerBitmaps.Add(s_Player_Bitmap3);
s_colPlayerBitmaps.Add(s_PlayerJBitmap4);
//Возвратить коллекцию return s_colPlayerBitmaps;
}
private static System.Drawing.Pen s_blackPen;
private static System.Drawing.Pen s_whitePen;
private static System.Drawing.Imaging.ImageAttributes s_ImageAttribute;
private static System.Drawing.Font s_boldFont;
//
//Вызывается для освобождения от любых графических
//ресурсов, которые могли быть кэшированы
//
private static void g_CleanUpDrawingResources() {
//Освободить память от черного пера, если таковое имеется if(s_blackPen != null)
{s_blackPen.Dispose(); s_blackPen = null;}
// Освободить память от белого пера, если таковое имеется if(s_whitePen != null)
{ s_whitePen.Dispose(); s_whitePen = null;}
//Освободить память от атрибута ImageAttribute, если таковой имеется.
//Примечание. Метод Dispose() для этого типа не предусмотрен,
//поскольку все его данные являются управляемыми
if(s_ImageAttribute != null) {s_ImageAttribute = null;}
//Освободить память от полужирного шрифта, если таковой имеется
if(s_boldFont != null)
{s_boldFont.Dispose(); s_boldFont = null;}
//
//Эта функция позволяет получить доступ
//к черному перу, находящемуся в кэш-памяти
//
private static System.Drawing.Pen g_GetBlackPen() {
//Если перо еще не существует, создать его
if(s_blackPen == null)
{
s_blackPen = new System.Drawing.Pen( System.Drawing.Color.Black) ;
}
//Возвратить черное перо return s blackPen;
//
//Эта функция позволяет получить доступ
//к белому перу, находящемуся в кэш-памяти
//
private static System.Drawing.Pen g_GetWhitePen() {
//Если перо еще не существует, создать его
if(s_whitePen == null)
{s_whitePen = new System.Drawing.Pen(
System.Drawing.Color.White);} //Возвратить белое перо return s_whitePen;
}
//
//Эта функция позволяет получить доступ
//к полужирному шрифту, находящемуся в кэш-памяти
//
private static System.Drawing.Font g_GetBoldFont() {
//Если перо еще не существует, создать его
if(s_boldFont == null)
{
s_boldFont = new System.Drawing.Font( System.Drawing.FontFamily.GenericSerif, 10, System.Drawing.FontStyle.Bold) ;
}
//Возвратить полужирный шрифт return s_boldFont;
}
//
//Эта функция позволяет осуществлять доступ
//к находящемуся в кэш-памяти объекту imageAttributes,
// который мы используем для изображений с прозрачностью
// private static System.Drawing.Imaging.ImageAttributes g_GetTransparencyImageAttribute()
{
//Если объект не существует, создать его
if(s_ImageAttribute == null)
{
//Создать атрибут изображения s_ImageAttribute =new System.Drawing.Imaging.ImageAttributes();
s_ImageAttribute.SetColorKey(System.Drawing.Color.White,
System.Drawing.Color.White);
}
//Возвратить его return s_ImageAttribute;
}
} //Конец класса
Современные языки программирования, библиотеки классов и управляемые среды времени выполнения позволили значительно повысить продуктивность написания программ. В то же время, избавляя программиста от необходимости задумываться о низкоуровневом распределении памяти, в котором нуждаются алгоритмы, они невольно создают предпосылки для написания неэффективного кода. Неэффективность кода может быть обусловлена причинами двоякого рода:
Вашей целью должно быть распределение "нулевых объемов памяти" внутри циклов в написанном вами коде. Существуют случаи, когда это является неизбежным, как, например, при построении дерева объектов, которое требует размещения в памяти новых узлов для помещения их в иерархическую структуру. Во многих других случаях эффективность приложения можно существенно повысить, тщательно анализируя распределение памяти для каждого объекта и рассматривая альтернативные решения. Чего, как правило, следует избегать - так это выполнения операций размещения объектов в памяти и удаления их из памяти внутри алгоритмических циклов.
Во многих случаях, если вы хотите инкапсулировать некоторые простые данные, то для локальных переменных внутри функций гораздо эффективнее использовать не объекты, а структуры. Структура - это просто удобный способ сгруппировать в одном пакете взаимосвязанные данные, а не передавать их в виде отдельных переменных.
Структуры обладают более простыми свойствами по сравнению с объектами, но могут "упаковываться" в объекты и передаваться внутри программы так же, как они, если в этом возникает необходимость. Использование структур предоставляет определенные удобства и может привести к некоторому увеличению производительности (по сравнению с вариантом, когда используются объекты), но поскольку они выглядят, а во многих случаях и действуют подобно объектам и могут заключаться в объекты-оболочки, необходимо тщательно взвешивать, когда их следует использовать, чтобы избежать дополнительных накладных расходов и не создать лишнего мусора. В сомнительных случаях тестируйте алгоритмы, используя как отдельные переменные (например, базовые типы, подобные int, string, double ), так и структуры, чтобы сравнить производительность приложения в обоих случаях и убедиться в том, что она остается примерно одинаковой.
Современные языки программирования позволяют очень легко работать со строками, создавать их, разбивать, копировать и объединять. Рассмотрим, например, следующие простые операторы:
string strl = "internet"; string str2 = "explorer"; string str3 = strl + str2; string str3 = str3 + str3;
Простота операций со строками ведет к их нерациональному использованию. Не оптимизированная обработка строк является одной из наиболее вероятных причин плохой производительности. Ниже представлены некоторые рекомендации и правила, которыми следует руководствоваться при работе со строками.
string strl = "internet"; //"internet" - статические данные, скомпилированные //в двоичные данные вашего приложения string str2 = strl + strl; //только что была создана новая строка, являющаяся результатом конкатенации двух строк str2 = "explorer"; //Поскольку отсутствуют другие переменные, указывающие на те данные, на которые указывала переменная str2, //эти данные становятся мусором, и память должна быть //очищена от них.
StringBuilder. Все виды строк создаются на основе других переменных, обрабатываемых в циклах. Типичным примером динамического создания строк может служить цикл, генерирующий текстовый отчет, каждая строка которого содержит следующие данные://Неэффективный код, выполняющийся внутри цикла
{
myString = myString +"CustomerID: "
+ System.Convert.ToString(customer[idx].id) + ", Name: " + System.Convert.ToString(customer[idx].name);
}
Вместо того чтобы конкатенировать строки и создавать новую строку, для создания отчета можно было бы использовать класс StringBuilder. Класс StringBuilder очень удобно использовать для работы с массивами переменной размерности с целью создания строк. Он позволяет эффективно изменять длину или содержимое массива и, что самое важное, создавать новые строки на ос нове символьных массивов. Обязательно изучите класс StringBuilder, поскольку умение использовать его имеет решающее значение для написания эффективных алгоритмов, генерирующих строковые данные.В листинге представлены два аналогичных алгоритма, которые приводят к одному и тому же результату. В обоих алгоритмах осуществляется инкрементирование счетчика, и каждый раз, когда значение счетчика увеличивается, его строковое представление добавляется в расширяемый фрагмент текста. Оба алгоритма выполняют одинаковое количество итераций и характеризуются одинаковой степенью сложности написания, тем не менее, один из них работает гораздо быстрее другого.
//Для имитации создания типичного набора строк используются
//обычные строки
private void buttonl_Click(object sender, System.EventArgs e)
{
//Вызвать сборщик мусора, чтобы тест
//начинался с чистого состояния.
System.GC.Collect ();
int numberToStore = 0;
PerformanceSampling.StartSample (0, "StringAllocaitons");
string total_result = "";
for (int outer_loop = 0; outer_loop < LOOP_ITERATIONS; outer_loop++)
{
//Сбросить старый результат
total_result = "";
//Выполнять цикл до максимального значения x_counter, каждый
//раз присоединяя очередную тестовую строку к рабочей строке
for(int x_counter = 0; x_counter < COUNT_UNTIL; x_counter++)
{
total_result = total_result + numberToStore.ToString();
//Увеличить значение счетчика
numberToStore ++;
}
}
PerformanceSampling.StopSample(0);
//Отобразить длину строки
System.Windows.Forms.MessageBox.Show("Длина строки: " +total_result.Length.ToString());
//Отобразить строку
System.Windows.Forms.MessageBox.Show("Строка : " +total_result);
//Отобразить длительность интервала времени, ушедшего на вычисления
System.Windows.Forms.MessageBox.Show(PerformanceSampling.GetSampleDurationText(0));
}
//Для имитации создания типичного набора строк используется
//объект StringBuilder
private void button2_Click(object sender, System.EventArgs e) {
//Вызвать сборщик мусора, чтобы тест
//начинался с чистого состояния.
System.GC.Collect();
System.Text.StringBuilder sb = new System.Text.StringBuilder(); string total_result = ""; int numberToStore = 0;
PerformanceSampling.StartSample(1, "StringBuilder");
for (int outerJLoop = 0; outer_loop < LOOP_ITERATIONS; outer_loop++)
{
//Очистить объект StringBuilder (не создавая нового объекта)
sb.Length = 0;
//Очистить строку со старым результатом
total_result = "";
//Выполнять цикл до максимального значения x_counter, каждый раз
//присоединяя очередную тестовую
//строку к рабочей строке
for(int x_counter = 0; x_counter < COUNTJJNTIL; x_counter++)
{
sb.Append(numberToStore );
sb.Append(", ");
//Увеличить значение счетчика numberToStore ++;
}
//Имитируем выполнение некоторых операций над строкой...
total_result = sb.ToStringO ;
}
PerformanceSampling.StopSample(1);
//Отобразить длину строки
System.Windows.Forms.MessageBox.Show("Длина строки: "+ total_result.Length.ToString()) ;
//Отобразить строку
System.Windows.Forms.MessageBox.Show("String : " + total_result);
//Отобразить длительность интервала времени, ушедшего на вычисления System.Windows.Forms.MessageBox.Show(
PerformanceSampling.GetSampleDurationText(1));
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.