Android activity stack trace

Жизненный цикл Activity Stack (часть 2)

Как и договаривались в первой части статьи, в этой мы будем рассматривать инструменты для изменения стандартного поведения Activity Stack.

Вся теория по сегодняшей теме присутствует на developer.android.com/guide/topics/manifest/activity-element.html, я буду кое-где на неё ссылаться, а мы постараемся разобраться как оно работает на деле и выяснить, в каких ситуациях это можно использовать в реальной жизни.

Некоторые параметры могут быть добавлены как в AndroidManifest’е, так и Intent-флагом динамически в коде:

android:launchMode

Параметр определяет что должно происходить, когда мы активируем новый Intent с вызовом конкретной Activity.
В нашем примере применяется к ActivityA.

«standard» и «singleTop» (FLAG_ACTIVITY_SINGLE_TOP)

«standard» — это поведение по умолчанию. Система всегда создаёт новую Activity и добавляет её в верх стэка.
Изменим нашу ActivityA так, чтобы она вместо перехода на ActivityB стартовала себя же снова.

Видим, что в стэке было 2 одинаковых Activity и только после двух нажатий back процесс умер.

Модификатор «singleTop» защищает от дублирования Activity, которая находятся в вершине стэка, при её повторном вызове.

Новая Activity не была создана, вместо этого был вызов onNewIntent() . По первому back мы вышли из приложения.

«singleTask» и «singleInstance»

Модификаторы «singleTask» и «singleInstance» не разрешают иметь более одной сущности одной Activity. Отличаются они способностью иметь вместе с собой в task’е другие Activity.

При повторном переходе на ActivityA система уничтожила все Activity, которые были выше её в стэке. Нажатие back вывело нас на Home Screen.

Повторный переход на ActivityA не вызвал цепных реакций, но открыл отдельный task с одной единственной ActivityA. Он был завершён по первому нажатию back. Ещё двух нажатий хватило, чтобы выйти на Home Screen, т.к. единственная сущность ActivityA была уничтожена выше и возврата к ней не было. Внешне переход от ActivityA к ActivityB и от ActivityC к ActivityA (т.е. переход между разными task’ами внутри одного процесса) выглядил как смена приложения, т.е. сворачивание одной Activity и «выпрыгивание» нового вместо более плавного перехода.

android:noHistory (FLAG_ACTIVITY_NO_HISTORY)

Значение по умолчанию — false . Если true , то к остановленной Activity вернуться будет нельзя.
Параметр применён к ActivityA со значением true :

Судя по моменту запуска onDestroy() у ActivityA, она оставалась в памяти даже после того, как было вызвано ActivityA. onStop() , хотя возврат к ней уже не был возможен.

Параметр удобно использовать, если нужно показать лого при запуске приложения и больше к нему не возвращаться.

android:clearTaskOnLaunch и android:finishOnTaskLaunch

Параметр clearTaskOnLaunch при значении true будет обязывать систему уничтожать все не корневые Activity у стэка (а точнее у конкретного task’а) при повторном запуске приложения. Имеет смысл применять только для корневой Activity, поэтому в примере, с которого снимался лог, я добавил его к ActivityA:

App start->A->B->C->Home->App start:

Видим что при повторном запуске приложения, Android уничтожил из памяти дочерние ActivityB и ActivityC. Имеем также в виду, что возврат к приложению через меню Recents (долгий tap по кнопке Home) не инициирует Intent LAUNCHER, а потому случится возврат к ActivityC.

Точно такого же поведения можно добиться с помощью параметра finishTaskOnLaunch . Android уничтожит те Activity при повторном запуске приложения, у которых значение этого параметра будет true . Т.е. для нашего примера достаточно прописать его в ActivityB и ActivityC, чтобы увидеть тот же лог:

Оба параметра имеют значение false по умолчанию.

Один из возможных случаев применения — реализовать невозможность возврата в остановленную Activity в сочетании с параметром excludeFromRecents (невключение Activity в меню Recents). Хотя, полагаю, есть и более специфичные или, наоборот, простые случаи.

android:parentActivityName

Этим параметром можно сделать родителем конкретной Activity любую нужную нам. Но есть некоторая оговорка, что возврат к нему будет происходить не по кнопке back, а по Navigation Up (http://developer.android.com/training/implementing-navigation/ancestral.html), как, например в Action Bar’e. Но мы, чтобы не заморачиваться, переопределим onBackPressed() в ActivityC и сделаем ActivityA родителем ActivityC:
Например:

Видим, что после нажатия back произошло даже больше того, что ожидалось. Были уничтожены не только те Activity, которые стояли выше родительской, то так же пересоздалась и она сама. Но в целом поведение ожидаемо.

Применять разумно для того, чтобы дать пользователю выйти, к примеру, в главное меню после долгих странствий по дочерним Activity без многочисленных возвратов по кнопке back (в случае реализации, как положено, с Action Bar’ом).

android:allowTaskReparenting и android:taskAffinity

Параметр allowTaskReparenting разрешает привязать вызванную из task’а №1 Activity до этого созданную в task’e №2 (т.е. привязанную к нему) к task’у №1.
Подготовка:

На форму ActivityA добавим ещё одну кнопку, которая будет стартовать ActivityC.

В файле манифеста мы разрешили ActivityC менять родителя, если на это претендует ActivityA, которая здесь является отдельным task’ом по причине android:launchMode=»singleInstance» .

App start->A->B->C->Home->App start->A->C->back:

До нажатия Home у нас было два сущности: Task1[A], Task2[B,C]. После повторного запуска приложения мы из ActivityA обратились к ActivityC, т.е. к Task2, который далее, не будь прописаны allowTaskReparenting и taskAffinity , вёл бы себя как отдельное приложение и по нажатию back вернул бы нас к своему корневому ActivityB. Благодаря параметрам, кнопка back вывела нас обратно в Task1.

В реальной жизни редко бывает необходимость строить такие сложные схемы работы внутри одного приложения, поэтому логичнее представить на месте Task1 и Task2 отдельные приложения, одно из которых вызывает Activity другого для выполнения короткой задачи и после нажатия back получает обратно контроль над экраном устройства.

Читайте также:  Настройки сервера входящей почты gmail для андроид пошагово

Источник

Tasks и Back Stack в Android

Итак. Каждое Android приложение, как минимум, состоит из фундаментальных объектов системы — Activity. Activity — это отдельный экран который имеет свою отдельную логику и UI. Количество Activity в приложении бывает разное, от одного до много. При переходах между различными Activity пользователь всегда может вернуться на предыдущую, закрытую Activity при нажатии кнопки back на устройстве. Подобная логика реализована с помощью стека (Activity Stack). Его организация «last in, first out» — т.е. последний вошел, первый вышел. При открытии новой Activity она становится вершиной, а предыдущая уходит в режим stop. Стек не может перемешиваться, он имеет возможность добавления на вершину новой Activity и удаление верхней текущей. Одна и та же Activity может находиться в стеке, сколько угодно раз.
Task — это набор Activity. Каждый таск содержит свой стек. В стандартной ситуации, каждое приложение имеет свой таск и свой стек. При сворачивании приложения, таск уходит в background, но не умирает. Он хранит весь свой стек и при очередном открытии приложения через менеджер или через launcher, существующий таск восстановится и продолжит свою работу.

Ниже покажу картинку, как работает стек.

Если продолжать нажимать кнопку back, то стек будет удалять Activity до того, пока не останется главная корневая. Если же на ней пользователь нажмет back, приложение закроется и таск умрет. Кстати, я говорил о том, что когда мы сворачиваем наше приложение и запускам например новое, то наш таск просто уходит в background и будет ждать момента, пока мы его вызовем. На самом деле есть одно «но». Если мы будем иметь много задач в background или же просто сильно нагружать свое устройство, не мала вероятность того, что таск умрет из за нехватки системных ресурсов. Это не война конечно, но то что мы потеряем все наши текущие данные и наш стек очистится — это точно. Кстати для избежания потери данных в таком случаи, вам стоит почитать про SavingActivityState.

Маленький итог

Управление тасками

Существует два пути для изменения стандартной организации тасков. Мы можем устанавливать специальные атрибуты в манифесте для каждой Activity. Также мы можем устанавливать специальные флаги для Intent, который запускает новую Activity с помощью startActivity(). Заметьте, что иногда атрибуты в манифесте и флаги в Intent могут противоречить друг другу. В этом случаи флаги Intent будут более приоритетны.

Атрибут launchMode

Для каждой Activity в манифесте можно указать атрибут launchMode. Он имеет несколько значений:

  • standard — (по умолчанию) при запуске Activity создается новый экземпляр в стеке. Activity может размещаться в стеке несколько раз
  • singleTop — Activity может распологаться в стеке несколько раз. Новая запись в стеке создается только в том случаи, если данная Activity не расположена в вершине стека. Если она на данный момент является вершиной, то у нее сработает onNewIntent() метод, но она не будет пересоздана
  • singleTask — создает новый таск и устанавливает Activity корнeвой для него, но только в случаи, если экземпляра данной Activity нет ни в одном другом таске. Если Activity уже расположена в каком либо таске, то откроется именно тот экземпляр и вызовется метод onNewIntent(). Она в свое время становится главной, и все верхние экземпляры удаляются, если они есть. Только один экземпляр такой Activity может существовать
  • singleInstance — тоже что и singleTask, но для данной Activity всегда будет создаваться отдельный таск и она будет в ней корневой. Данный флаг указывает, что Activity будет одним и единственным членом своего таска

На самом деле не важно, в каком таске открыта новая Activity. При нажатии кнопки back мы все равно вернемся на предыдущий таск и предыдущие Activity. Единственный момент, который нужно учитывать — это параметр singleTask. Если при открытии такой Activity мы достанем ее из другого background таска, то мы полностью переключаемся на него и на его стек. на картинке ниже это продемонстрировано.

Флаги

Как и говорил, мы можем устанавливать специальный флаги для Intent, который запускает новую Activity. Флаги более приоритетны, чем launchMode. Существует несколько флагов:

  • FLAG_ACTIVITY_NEW_TASK — запускает Activity в новом таске. Если уже существует таск с экземпляром данной Activity, то этот таск становится активным, и срабатываем метод onNewIntent().
    Флаг аналогичен параметру singleTop описанному выше
  • FLAG_ACTIVITY_SINGLE_TOP — если Activity запускает сама себя, т.е. она находится в вершине стека, то вместо создания нового экземпляра в стеке вызывается метод onNewIntent().
    Флаг аналогичен параметру singleTop описанному выше
  • FLAG_ACTIVITY_CLEAR_TOP — если экземпляр данной Activity уже существует в стеке данного таска, то все Activity, находящиеся поверх нее разрушаются и этот экземпляр становится вершиной стека. Также вызовется onNewIntent()

Affinity

Стандартно все Activity нашего приложения работают в одном таске. По желанию мы можем изменять такое поведение и указывать, чтобы в одном приложении Activity работали в разных тасках, или Activity разных приложений работали в одном. Для этого мы можем в манифесте для каждой Activity указывать название таска параметром taskAffinity. Это строковое значение, которое не должно совпадать с названием package, т.к. стандартный таск приложения называется именно как наш пакет. В общем случаи данный параметр указывает, что Activity будет гарантированно открываться в своём отдельном таске. Данный параметр актуален, если мы указываем флаг FLAG_ACTIVITY_NEW_TASK или устанавливаем для Activity атрибут allowTaskReparenting=«true». Этот атрибут указывает, что Activity может перемещаться между тасками, который её запустил и таском, который указан в taskAffinity, если один из них становится активным.

Читайте также:  Как установить android через флешку

Чистка стека

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

  • alwaysRetainTaskState — если флаг установлен в true для корневой Activity, то стек не будет чиститься и полностью восстановится даже после длительного времени
  • clearTaskOnLaunch — если установить флаг в true для корневой Activity, то стек будет чиститься моментально, как только пользователь покинет таск. Полная противоположность alwaysRetainTaskState
  • finishOnTaskLaunch — работает аналогично clearTaskOnLaunch, но может устанавливаться на любую Activity и удалять из стека именно её

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

Источник

Android activity stack trace

Custom Activity On Crash library

This library allows launching a custom activity when the app crashes, instead of showing the hated «Unfortunately, X has stopped» dialog.

Add the following dependency to your build.gradle:

. and you are done!

You can combine this library with other crash handlers such as Crashlytics or ACRA.

If you are using Crashlytics, there’s nothing to do, just use it normally.

If you are using ACRA, you should initialize it inside Application.onCreate instead of Application.attachBaseContext , and enable alsoReportToAndroidFramework , otherwise, it will not work.

Force an app crash by throwing an uncaught exception, using something like this in your code:

You can customize the behavior of this library in several ways by setting its configuration at any moment. However, it’s recommended to do it on your Application class so it becomes available as soon as possible.

Add a snippet like this to your Application class:

Here is a more detailed explanation of each option that can be set using CaocConfig.Builder :

This method defines if the error activity should be launched when the app crashes while on background. There are three modes:

  • CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM : launch the error activity even if the app is in background.
  • CaocConfig.BACKGROUND_MODE_CRASH : launch the default system error when the app is in background.
  • CaocConfig.BACKGROUND_MODE_SILENT : crash silently when the app is in background.

The default is CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM .

Defines if CustomActivityOnCrash crash interception mechanism is enabled. Set it to true if you want CustomActivityOnCrash to intercept crashes, false if you want them to be treated as if the library was not installed. This can be used to enable or disable the library depending on flavors or buildTypes. The default is true .

This method defines if the error activity must show a button with error details. If you set it to false , the button on the default error activity will disappear, thus disabling the user from seeing the stack trace. The default is true .

This method defines if the library must track the activities the user visits and their lifecycle calls. This is displayed on the default error activity as part of the error details. The default is false .

This method defines if the error activity must show a «Restart app» button or a «Close app» button. If you set it to false , the button on the default error activity will close the app instead of restarting. If you set it to true and your app has no launch activity, it will still display a «Close app» button! The default is true .

This controls if the stack trace must be relogged when the custom error activity is launched. This functionality exists because the Android Studio default Logcat view only shows the output for the current process. This makes it easier to see the stack trace of the crash. You can disable it if you don’t want an extra log. The default is true .

Defines the time that must pass between app crashes to determine that we are not in a crash loop. If a crash has occurred less that this time ago, the error activity will not be launched and the system crash screen will be invoked. The default is 3000 .

This method allows changing the default upside-down bug image with an image of your choice. You can pass a resource id for a drawable or a mipmap. The default is null (the bug image is used).

This method sets the activity that must be launched by the error activity when the user presses the button to restart the app. If you don’t set it (or set it to null), the library will use the first activity on your manifest that has an intent-filter with action cat.ereza.customactivityoncrash.RESTART , and if there is none, the default launchable activity on your app. If no launchable activity can be found and you didn’t specify any, the «restart app» button will become a «close app» button, even if showRestartButton is set to true .

As noted, you can also use the following intent-filter to specify the restart activity:

This method allows you to set a custom error activity to be launched, instead of the default one. Use it if you need further customization that is not just strings, colors or themes (see below). If you don’t set it (or set it to null), the library will use the first activity on your manifest that has an intent-filter with action cat.ereza.customactivityoncrash.ERROR , and if there is none, a default error activity from the library. If you use this, the activity must be declared in your AndroidManifest.xml , with process set to :error_activity .

As noted, you can also use the following intent-filter to specify the error activity:

This method allows you to specify an event listener in order to get notified when the library shows the error activity, restarts or closes the app. The EventListener you provide can not be an anonymous or non-static inner class, because it needs to be serialized by the library. The library will throw an exception if you try to set an invalid class. If you set it to null , no event listener will be invoked. The default is null .

Customization of the default activity

Читайте также:  Lenovo yoga tablet 2 windows установка android

You can override several resources to customize the default activity:

Theme:

The activity uses your application theme by default. It must be a child of Theme.AppCompat or a default one will be used. If you want to specify a specific theme only for the error activity, you can do so by redeclaring it on your manifest like this:

Image:

By default, an image of a bug is displayed. You can change it to any image by using the provided errorDrawable(int) method. You can also do it the old way and create a customactivityoncrash_error_image drawable on all density buckets (mdpi, hdpi, xhdpi, xxhdpi and xxxhdpi).

Strings:

You can provide new strings and translations for the default error activity strings by overriding the following strings:

There is a sample project module with examples of these overrides. If in doubt, check the code in that module.

Completely custom error activity

If you choose to create your own completely custom error activity, you can use these methods:

Returns the stack trace that caused the error as a string.

Returns several error details including the stack trace that caused the error, as a string. This is used in the default error activity error details dialog.

Returns the config of the library when the crash happened. Used to call some methods.

Kills the current process and restarts the app again with a startActivity() to the passed intent. You MUST call this to restart the app, or you will end up having several Application class instances and experience multiprocess issues.

The same as CustomActivityOnCrash.restartApplication , but allows you to specify a custom intent.

Closes the app and kills the current process. You MUST call this to close the app, or you will end up having several Application class instances and experience multiprocess issues.

The sample project module includes an example of a custom error activity. If in doubt, check the code in that module.

No need to add special rules, the library should work even with obfuscation.

This library relies on the Thread.setDefaultUncaughtExceptionHandler method. When an exception is caught by the library’s UncaughtExceptionHandler it does the following:

  1. Captures the stack trace that caused the crash
  2. Launches a new intent to the error activity in a new process passing the crash info as an extra.
  3. Kills the current process.

The inner workings are based on ACRA’s dialog reporting mode with some minor tweaks. Look at the code if you need more detail about how it works.

  • CustomActivityOnCrash will not work with any custom UncaughtExceptionHandler set after initializing the library that does not call back to the original handler.
  • If your app initialization or error activity crash, there is a possibility of entering an infinite restart loop (this is checked by the library for the most common cases, but could happen in rarer cases).
  • The library has not been tested with multidex enabled. It uses Class.forName() to load classes, so maybe that could cause some problem in API
  • This will not avoid ANRs from happening.
  • This will not catch native errors.
  • There is no guarantee that this will work on every device.
  • This library will not make you toast for breakfast 🙂

Any contribution in order to make this library better will be welcome!

The library is licensed under the Apache License 2.0.

The bug image used in the default error activity is licensed under CC-BY by Riff: https://www.sketchport.com/drawing/6119265933459456/lady-bug If you use the image in your app, don’t forget to mention that!

About

Android library that allows launching a custom activity when your app crashes, instead of showing the hated «Unfortunately, X has stopped» dialog.

Источник

Оцените статью