Single activity application android

A Single-Activity Android Application. Why not?!

The reason behind writing this article was one of the I/O 2016 sessions where a speaker told about Google’s plans to neutralize the UX differences between Native Android App and Web. In its turn, availability of stable working libraries and my solid development experience finally served as a call to action. Though in all fairness, experience is not always the best motivator for making changes to the development process which has been ‘debugged to shine’.

Obvious Things about Activity

A basic Android application project can consist of one or more Activities. Activity may or may not contain Fragments. Fragment can include some UI that can be reused in an unspecified Activity or Fragment. It seems to be all logical and taken from the Bible. The question is: When should I create a new Activity? Let’s see what options we might have:

  1. An independent screen such as SettingsActivity which exists as one-off instance in the application.
  2. A screen that I can create as multiple instances, such as ProfileActivity.
  3. A screen that can be accessed using a Deep Link.
  4. A screen with unique settings of Status Bar, Navigation Bar and other window parameters.

Seems to be all, doesn’t it?

Based on our experience and the options above we can point out the following benefits (will reduce the development time and the number of bugs):

  1. Can set style for out-of-the-box screens using xml of Status Bar and Navigation Bar to input parameters.
  2. Built-in transition between screens.
  3. Can reuse or replace a previously launched Activity.

If there are advantages, then there should be some disadvantages as well. Let’s find out what they are.

  • One of the main drawbacks is asynchronous launch. No matter how ‘heavy’ your Activity is, Android will decide itself on what the delay should be before the launch.
  • The Shared Element Transition does not work between Fragment and Activity. That’s a bit of a headache when building complex UI/UX.
  • If screen layout is changed, the system re-creates screens for all previous Activities along with all the fragments relating to the backward navigation. By the way the same is true when restoring an app after it’s killed by the system.

Summarizing the above, the disadvantages can be brought down to a common denominator — the loss of control over the application by developer.

What you’re signing up for or taking control of it

Fragments do solve these problems, but also bring all the advantages that we’ve used to live with to nought! One has to pay in full for a complete control over the application whereas the chances of making a mistake are doubled. In addition to all the advantages of a Multiple-Activity which turn into disadvantages when you use a Single-Activity, you will have to solve the following tasks:

  1. Writing or selecting a library to navigate between screens.
  2. Breaking down the application into DI (Dependency Injection) modules and creating relations between them both for data transfer and semantically.
  3. Controlling UI of the main Activity depending on the state which the application is in.

If you have enough skills and proficiency, the estimate for developing an app will be one and half times higher in terms of the number of screens and their relationships between each other. That is, if you have 2 screens in the application and it takes 8 hours to implement them (layout, animations, saving state), then in a Single-Activity App the solution will require 12 hours as such.

Will I get those precious 60 fps?

Animation smoothness between the screens will depend only on a smart arrangement of the views, their number as well as the data binding (text, pictures, audio, video). Messing up is only possible in the background. For example, one of the apps or services would take hold of the CPU or an I/O channel.

Activity or Fragment launching is practically the same under ideal circumstances. But you may ask why? Activity is actually “heavy” and has many properties and components. That’s all fine, but let’s get down the facts.

Читайте также:  Android убрать строку с кнопками

There are 3 measurable values that are absolutely important for us as they reflect the resource ‘efficiency’ of using an Activity or a Fragment.

To get the above values, let’s use the Android Studio 3.2.1 Profiler and Battery Historian.

Analyze a screen with a typical layout that consists of: TextView, ImageView, Button, ProgressBar, and RecyclerView — and a default fadeIn/Out animation to render the transition between screens. It is important that it is drawn within ≤16 ms to maintain the experimental integrity.

Let’s analyze not only how an Activity or a Fragment becomes visible, but also their closing to simulate the user’s actions. Therefore, we will go through the following scenario.

Running the scenarios with a Single and Multiple Activity several times we’ve obtained the following results from Battery Historian.

Источник

The Android Lifecycle cheat sheet — part I: Single Activities

Android is designed to empower users and let them use apps in a intuitive way. For example, users of an app might rotate the screen, respond to a notification, or switch to another task, and they should be able to continue using the app seamlessly after such an event.

To provide this user experience, you should know how to manage component lifecycles. A component can be an Activity, a Fragment, a Service, the Application itself and even the underlying process. The component has a lifecycle, during which it transitions through states. Whenever a transition happens, the system notifies you via a lifecycle callback method.

To help us explain how lifecycles work, we’ve defined a series of scenarios which are grouped according to the components that are present:

Part I: Activities — single activity lifecycle (this post)

The diagrams are also available as a cheat sheet in PDF format for quick reference.

Note: these diagrams apply to Android P / Jetpack 1.0 behavior.

The following scenarios showcase the default behavior of the components, unless otherwise noted.

If you find errors or you think something important is missing, report it in the comments.

Part I: Activities

Single Activity — Scenario 1: App is finished and restarted

  • The user presses the Back button, or
  • The Activity.finish() method is called

The simplest scenario shows what happens when a single-activity application is started, finished and restarted by the user:

  • onSaveInstanceState is not called (since the activity is finished, you don’t need to save state)
  • onCreate doesn’t have a Bundle when the app is reopened, because the activity was finished and the state doesn’t need to be restored.

Single Activity — Scenario 2: User navigates away

  • The user presses the Home button
  • The user switches to another app (via Overview menu, from a notification, accepting a call, etc.)

In this scenario the system will stop the activity, but won’t immediately finish it.

When your activity enters the Stopped state, the system uses onSaveInstanceState to save the app state in case the system kills the app’s process later on (see below) .

Assuming the process isn’t killed, the activity instance is kept resident in memory, retaining all state. When the activity comes back to the foreground, the activity recalls this information. You don’t need to re-initialize components that were created earlier.

Single Activity — Scenario 3: Configuration changes

  • Configuration changes, like a rotation
  • User resizes the window in multi-window mode

Configuration changes like rotation or a window resize should let users continue exactly where they left off.

  • The activity is completely destroyed, but the state is saved and restored for the new instance.
  • The Bundle in onCreate and onRestoreInstanceState is the same.

Single Activity — Scenario 4: App is paused by the system

  • Enabling Multi-window mode (API 24+) and losing the focus
  • Another app partially covers the running app (a purchase dialog, a runtime permission dialog, a third-party login dialog…)
  • An intent chooser appears, such as a share dialog

This scenario doesn’t apply to:

  • Dialogs in the same app. Showing an AlertDialog or a DialogFragment won’t pause the underlying activity.
  • Notifications. User receiving a new notification or pulling down the notification bar won’t pause the underlying activity.

Источник

Разбираемся с launchMode Android Activity: standard, singleTop, singleTask и singleInstance

Activity — это одна из самых ярких концепций в Android (самой популярной мобильной операционной системе с хорошо продуманной архитектурой управления памятью, которая отлично реализует многозадачность).

Так или иначе, с запуском Activity на экран не все так однозначно. Способ, которым оно было запущено, также важен. Нюансов в этой теме очень много. Одним из действительно важных является launchMode, о котором мы и поговорим в этой статье.

Читайте также:  Opens street map android

Каждое Activity создается для работы с разными целями. Некоторые из них предназначены для работы отдельно с каждым Intent, например, отправленным Activity для составления электронной почты в почтовом клиенте. В то время как другие предназначены для работы в качестве синглтона, например, Activity почтового ящика.

Вот почему важно указывать, нужно ли создавать новое Activity или использовать существующее, иначе это может привести к плохому UX или сбоям. Благодаря разработчикам ядра Android, это легко сделать с помощью launchMode, который был специально для этого разработан.

Определение launchMode

По сути, мы можем определить launchMode напрямую в качестве атрибута тега activity> в AndroidManifest.xml :

Доступно 4 типа launchMode. Давайте рассмотрим их по очереди.

Это режим «по умолчанию».

Поведение Activity, установленного в этот режим, будет всегда создавать новую Activity, чтобы работать отдельно с каждым отправленным Intent. По сути, если для составления электронного письма отправлено 10 Intent-ов, должно быть запущено 10 Activity, чтобы обслуживать каждый Intent отдельно. В результате на устройстве может быть запущено неограниченное количество таких Activity.

Поведение на пре-Lollipop Android

Этот вид Activity будет создан и помещен в верх стека в той же задаче, которая и отправила Intent.

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

А это то, что вы увидите в диспетчере задач. (Может показаться немного странным)

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

Поведение на Lollipop Android

Если эти Activity относятся к одному и тому же приложению, поведение будет таким же, как и в пре-Lollipop реализации — размещение в стеке поверх задачи.

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

Это то, что вы увидите в диспетчере задач.

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

Примером такого вида Activity является Compose Email Activity (составление письма) или Social Network’s Status Posting Activity (обновление статуса в соцсети). Если у вас на уме Activity, которое отдельно обрабатывает каждый Intent, то вы думаете именно о standard Activity.

singleTop

Следующий режим — singleTop. Он ведет себя почти так же, как и standard, что означает, что экземпляров singleTop Activity можно создать столько, сколько мы захотим. Единственное отличие состоит в том, что если уже есть экземпляр Activity с таким же типом наверху стека в вызывающей задаче, не будет создано никакого нового Activity, вместо этого Intent будет отправлен существующему экземпляру Activity через метод onNewIntent() .

В режиме singleTop вы должны предусмотреть обработку входящего Intent в onCreate() и onNewIntent() , чтобы он работал во всех случаях.

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

А теперь представьте, что если мы всегда запускаем новое SearchActivity, чтобы обслуживать новый результат поиска, то мы получим 10 новых Activity для 10 итераций поиска. Было бы очень странно возвращаться назад, так как вам нужно было бы нажимать назад 10 раз, чтобы пройти через все результаты поиска, чтобы вернуться к корневой Activity.

Вместо этого, если SearchActivity уже находится наверху стека, лучше отправить Intent в существующий экземпляр Activity и позволить ему обновить результат поиска. Теперь будет только одно SearchActivity, размещенное наверху стека, и вы можете просто нажать кнопку «Назад» один раз, чтобы вернуться к предыдущему Activity. В этом больше смысла.

В любом случае singleTop работает в той же задаче, что и вызывающая сторона. Если вы ожидаете, что Intent будет отправлен в существующее Activity, помещенное поверх любой другой задачи, я должен вас разочаровать, сказав, что там это так уже не работает. В случае, если Intent отправлен из другого приложения в singleTop Activity, новое Activity будет запущено в том же аспекте, что и для standart launchMode (пре-Lollipop: помещено поверх вызывающей задачи, Lollipop: будет создана новая задача).

Читайте также:  Моды для stardew valley android

singleTask

Этот режим сильно отличается от standart и singleTop. Activity с singleTask launchMode разрешено иметь только один экземпляр в системе (аля синглтон). Если в системе уже существует экземпляр Activity, вся задача, удерживающая экземпляр, будет перемещен наверх, а Intent будет предоставлен через метод onNewIntent() . В противном случае будет создано новое Activity и помещено в соответствующую задачу.

Работая в одном приложении

Если в системе еще не было экземпляра singleTask Activity, будет создан новый, и он будет просто помещен вверх стека в той же задаче.

Но если он существует, все Activity, расположенные над этим singleTask Activity, автоматически будут жестоко уничтожены надлежащим образом (жизненный цикл закончен), чтобы отобразить на вершине стека нужное нам Activity. В то же время Intent будет отправлен в singleTask Activity через прекрасный метод onNewIntent() .

Это не имеет смысла с точки зрения пользовательского опыта, но оно он разработано именно таким образом…

Вы можете заметить один нюанс, который упоминается в документации:

Система создает новую задачу и инстанцирует экземпляр activity в корне новой задачи.

Но на практике похоже, что это работает не так, как описано. SingleTask Activity по-прежнему помещается наверх стека Activity задачи, как видно из результата команды dumpsys activity .

Если вы хотите, чтобы singleTask Activity вело себя так, как описано в документе: создайте новую задачу и поместите Activity в качестве корневого Activity. Вам нужно определить атрибут taskAffinity для singleTask Activity следующим образом.

Таким будет результат, когда мы попытаемся запустить SingleTaskActivity .

Ваша задача решить, использовать taskAffinity или нет в зависимости от желаемого поведения Activity.

Взаимодействуя с другим приложением

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

Если не существует задачи, которая бы являлась владельцем вызывающей singleTask Activity, вместо нее будет выведено наверх новое Activity.

В случае, если в какой-либо задаче существует экземпляр Activity, вся задача будет перемещена вверх, и для каждого отдельного Activity, расположенного над singleTask Activity, будет завершен жизненный цикл. Если нажата кнопка «Назад», пользователь должен пройти через Activity в стеке, прежде чем вернуться к вызывающей задаче.

Примером использования этого режима является любое Entry Point Activity, например, страница «Входящие» почтового клиента или таймлайн соцсети. Эти Activity не предполагают более чем одного экземпляра, поэтому singleTask отлично справится со своей задачей. В любом случае вы должны использовать этот режим с умом, так как в этом режиме Activity могут быть уничтожены без подтверждения пользователя, как описано выше.

singleInstance

Этот режим очень похож на singleTask, где в системе мог существовать только один экземпляр Activity. Разница в том, что задача, которая располагает этим Activity, может иметь только одно Activity — то, у которого атрибут singleInstance. Если из этого вида Activity вызывается другое Activity, автоматически создается новое задание для размещения этого нового Activity. Аналогичным образом, если вызывается singleInstance Activity, будет создана новая задача для размещения этого Activity.

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

Вот что происходит, когда вызывается singleInstance Activity, в то время как в стеке уже существует какое-либо Activity.

А вот что мы видим в диспетчере задач.

Поскольку эта задача может иметь только одно Activity, мы больше не можем переключаться обратно на задачу № 1. Единственный способ сделать это — перезапустить приложение из лаунчера, но, как в итоге получится, singleInstance задача будет скрыта в фоновом режиме.

Во всяком случае, есть некоторые обходные пути для этой проблемы. Как и в случае с singleTask Activity, просто назначьте атрибут taskAffinity для singleInstance Activity, разрешающим существование нескольких задач в диспетчере задач.

Теперь картина имеет больше смысла.

Этот режим используется редко. Некоторые из вариантов использования на практике — это лаунчер-Activity или приложение, для которого вы на 100% уверены, что там должно быть только одно Activity. В любом случае, я предлагаю вам не использовать этот режим, если на то нет крайней необходимости.

Intent-флаги

Помимо назначения режима запуска непосредственно в AndroidManifest.xml , мы также можем регулировать поведение с помощью инструмента, называемого Intent-флагами, например:

запустит StandardActivity с условием singleTop launchMode.

Есть довольно много флагов, с которыми вы можете работать. Вы можете найти больше информации об этом здесь.

Источник

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