Fast App Resume
В продолжение последней статьи про интеграцию в хаб «Фотографии» сегодня раскроем тему Fast Application Resume, которая появилась в WP8, и рассмотрим, для чего он нужен и какие преимущества дает использование этой функции, а также различные сценарии использования App Resume.
Забегая вперед, хотелось бы отметить упомянутую проблему использования Fast Resume вместе с Photo Sharing, Push Notification и т.д. Как оказалось, проблемы нет. Почти нет. Если пользователь обновил свой телефон (WP8 Update 3), то с новым обновлением проблем с переходом по Url уже нет. Однако коллега texnedo показал один интересный сценарий, при котором наблюдается баг с AppResume. Если в приложении включен App Resume и залочить экран не сворачивая приложения, то приложение просто восстанавливается без перехода по toast url.
Для чего нужен App Resume?
При классической модели выполнения приложения, клик по тайлу (на рабочем столе или при клике в списке приложений) не только запускает, но и перезапускает приложение, если оно было запущено и свернуто. В некоторых случаях проще не перезапускать приложение, а возвращаться в уже запущенное приложение так, словно пользователь вернулся по кнопке «Назад». Для «тяжелых» приложений значительно быстрее вернуться в запущенное приложение, нежели перезапускать его и заново инициализировать данные, что субъективно сокращает время «запуска».
Как и в случае с Fast Application Switch (в отличие от App Resume, эта возможность быстрого возврата в запущенное приложение после сворачивания по кнопке «Назад»), App Resume не является заменой Tombstone и нужно учитывать, что если приложение «уснуло», то необходимо полностью сохранять данные и самостоятельно их восстанавливать.
Теперь рассмотрим, как интегрировать эту фичу в приложение, рассмотрим различные сценарии возврата.
Как включить App Resume
Включить эту возможность достаточно просто: все, что надо сделать, это открыть файл WMAppManifest.xml в режиме редактирования XML (в контекстном меню – Open With – Xml editor) и дописать атрибут:
ActivationPolicy="Resume"
для тега <DefaultTask />
.
В конечном итоге строка по умолчанию в манифесте вида:
<DefaultTask Name ="_default" NavigationPage="MainPage.xaml" />
будет выглядеть следующим образом:
<DefaultTask Name ="_default" NavigationPage="MainPage.xaml" ActivationPolicy="Resume"/>
Как работает App Resume
Для демонстрации деталей работы App Resume сделаем простое приложение с возможностью навигации между двумя страницами. Также на обеих страницах будем выводить значение какой-нибудь переменной, которое можно будет менять по кнопке, и будем показывать историю навигации, в том числе, было ли приложение запущено или активировано.
Для простоты, значение переменной сохраним в памяти в статичном классе в статичном поле и будем выводить на обеих страницах в OnNavigateTo
static class FakeCacheStorage
{
public static int Counter { get; set; }
}
Для того, чтобы разобраться, как именно работает App Resume, мы должны временно закомментировать строчку:
RootFrame.Navigated += CheckForResetNavigation;
в App.xaml.cs, где в обработчике хранится код.
В первую очередь проверим Fast Application Switching. Запустим приложение и увеличим счетчик до какого-нибудь числа (пускай будет 4) и посмотрим, что у нас будет до сворачивания приложения и возврата по кнопке «Назад»
Как мы и ожидали, вся разница в способе запуска приложения. Изначально приложение было запущено, а потом активировано по кнопке «Назад».
А теперь посмотрим, что было бы, если бы мы свернули приложение и вместо кнопки «Назад» запустили приложение через основной тайл приложения.
Как мы видим, приложение было запущено, а не активировано, и мы оказались на главной странице. Кроме того, значение переменной было сброшено. Явно необходимо восстанавливать все пользовательские данные из хранилища.
А теперь, если включить в манифесте App Resume, то, в конечном итоге, мы увидим другую картину:
А вот это уже интереснее — приложение было активировано, а не запущено, при этом мы оказались на главной странице и значение счетчика сохранилось! А нашу вторую страницу мы увидим, только если нажмем кнопку «Назад». Если бы мы не выводили историю навигации, то можно было бы подумать, что приложение было просто перезапущено. Самое интересное началось бы, когда пользователь попытался выйти из приложения по кнопке «Назад» — для этого ему придется три раза нажимать эту кнопку. Далее мы рассмотрим, для чего было так сделан App Resume и какие сценарии использования такого поведения мы можем использовать.
App Resume как способ «быстро запуска» приложения
То, что мы видели в предыдущем разделе, дает интересную пищу для размышлений. Мы можем использовать App Resume для быстрого запуска приложений с сохранением всех данных, загруженных в память, но нам надо избавиться от старой истории навигации. То есть по сути пользователь должен по кнопке «Назад» выти из приложения. Если вы создаете свое WP8-приложение в студии начиная с версии VS2012, то необходимые методы уже добавлены в сгенерированный проект. Для рассмотрения работы App Resume мы временно закомментировали строку с обработчиком App Resume:
RootFrame.Navigated += CheckForResetNavigation;
Если мы раскомментируем эту строку, то уже не увидим историю навигации.
По сути, выглядит так, словно приложение было запущено (пользователь попадает на главную страницу и по кнопке «Назад» выйдет из приложения), но при этом данные в памяти остались.
Рассмотрим подробнее, что выполняется в этом методе.
В методе InitializePhoneApplication (файл app.xaml.cs) есть отдельная подписка на событие Navigated:
RootFrame.Navigated += CheckForResetNavigation;
История навигации удаляется по аргументу Reset:
private void CheckForResetNavigation(object sender, NavigationEventArgs e)
{
// If the app has received a 'reset' navigation, then we need to check
// on the next navigation to see if the page stack should be reset
if (e.NavigationMode == NavigationMode.Reset)
RootFrame.Navigated += ClearBackStackAfterReset;
}
И, соответственно, далее в методе ClearBackStackAfterReset чистится история навигации:
private void ClearBackStackAfterReset(object sender, NavigationEventArgs e)
{
// Unregister the event so it doesn't get called again
RootFrame.Navigated -= ClearBackStackAfterReset;
// Only clear the stack for 'new' (forward) and 'refresh' navigations
if (e.NavigationMode != NavigationMode.New && e.NavigationMode != NavigationMode.Refresh)
return;
// For UI consistency, clear the entire page stack
while (RootFrame.RemoveBackEntry() != null)
{
; // do nothing
}
}
Почему здесь есть подписка на New и Refresh? Рассмотрим на нашем примере.
Если мы используем App Resume, находясь на второй странице, то мы можем сделать точку остановки в методе OnNavigateTo нашей странице и увидеть, что страница запускается с параметром New. Однако, если мы уже находились на главной странице в момент сворачивания приложения, то мы вернемся на главную сначала с параметром навигации Reset, а уже потом с параметром навигации Refresh, что и обрабатывается в этом методе.
Далее рассмотрим еще один интересный сценарий использования App Resume — когда мы не запускаем приложение, а возвращаемся в него.
App Resume для возврата в запущенное приложение. «Как в iOS или WP8.1»
Прошел слух, что теперь приложения под WP8.1 по основному тайлу будут не перезапускаться по умолчанию, а восстанавливаться. Так это или не так, мы можем узнать только после Build. А пока мы можем сделать поведение «как в iOS», то есть если пользователь свернул приложение и нажал на основной тайл, то попадает на ту же страницу, где он был, а приложение не перезапускается. Важно понимать, что надо использовать эту возможность не для того, чтобы сделать «как в iOS», а для того, чтобы сделать действительно удобное и качественное приложение, которым будет удобно пользоваться. Да и в целом в WP-пользователи привыкли, что приложения при нажатии на основной тайл, скорее всего, перезапустятся.
По сути, пользователь должен попадать в свое приложение так же, как если бы он просто переключился на приложение по кнопке «Назад», с сохранением истории навигации и пользовательских данных на экране.
Нам необходимо обработать два основных сценария поведения:
1. Пользователь нажимает на тайл приложения.
2. Пользователь активирует приложение по ссылке (Push Notification, Secondary Tile, Photo Share и т.д.)
В первом случае, для реализации этого сценария в первую очередь отключим сброс истории при App Resume:
в App.xaml.cs удаляем подписку на Navigated и метод CheckForResetNavigation, подписываемся на событие Navigating (оно наступает ДО того, как мы перейдем на страницу и можем отменить переход):
RootFrame.Navigating+=ContinueExecution;
При запуске приложения мы можем увидеть в методе, что сначала приходит событие с аргументом Reset, а затем событие с аргументом New при клике на тайл приложения.
С учетом этого поведения подробнее рассмотрим реализацию ContinueExecution:
private bool isRelaunch = false;
private void ContinueExecution(object sender, NavigatingCancelEventArgs e)
{
if (e.NavigationMode == NavigationMode.Reset || (isRelaunch && e.NavigationMode==NavigationMode.New))
{
if (isRelaunch && e.Uri.OriginalString != "/MainPage.xaml")
{
RootFrame.Navigated+=ClearBackStackAfterReset;
return;
}
e.Cancel = true;
isRelaunch = !isRelaunch;
}
}
Метод ClearBackStackAfterReset генерируется студией для новых WP8-проектов и просто очищает историю навигации:
private void ClearBackStackAfterReset(object sender, NavigationEventArgs e)
{
RootFrame.Navigated -= ClearBackStackAfterReset;
if (e.NavigationMode != NavigationMode.New && e.NavigationMode != NavigationMode.Refresh)
return;
while (RootFrame.RemoveBackEntry() != null)
{
}
}
Как мы можем здесь видеть, было добавлено условие проверки на стартовую страницу:
if (isRelaunch && e.Uri.OriginalString != "/MainPage.xaml")
Проверка необходима для второго сценария, когда свернутое приложение активируется по вторичным тайлам, Push Notification и т.д.
В этом случае будет очищена история, и после активации приложения и обработки пользовательской истории по кнопке «Назад» мы выйдем из приложения.
Если же вы хотите, к примеру, по Push Notification не удалять историю, а продолжать пользоваться приложением, то достаточно удалить строку
RootFrame.Navigated+=ClearBackStackAfterReset;
или же добавить еще одно условие для ссылок с Push Notification.
Для демонстрации работы второго сценария добавим на следующую страницу кнопку для генерации второй ссылки:
<Button Content="Secondary Tile" Click="ButtonTile_OnClick"></Button>
Добавим довольно простой обработчик для этой кнопки:
private void ButtonTile_OnClick(object sender, RoutedEventArgs e)
{
var secondTile = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains("issecondtile"));
if (secondTile == null)
{
var newTileData = new StandardTileData
{
Title = "Second Page",
};
ShellTile.Create(new Uri("/SecondPage.xaml?id=someid&issecondtile=true", UriKind.Relative), newTileData);
}
}
Теперь, если мы запустим приложение, то перейдем на вторую страницу. Если после этого нажмем на тайл, то увидим, что приложение запустилось без истории навигации и при этом по основному тайлу запускается с историей навигации.
Итоги
Несмотря на простоту App Resume, объяснение принципов работы получилось довольно объемным. Как мы видим, с помощью App Resume можно не только ускорить запуск приложения, но и сделать поведение приложения «как в iOS». Особенно важно также не забывать про Tombstone в сценарии восстановления приложения по тайлу. В этом случае у нас сохранится вся история навигации, но при этом все пользовательские данные в памяти будут уничтожены и нам необходимо восстановить их на экране из хранилища.
Автор: Atreides07