10

The question, How can I get the current Activity? has been asked dozens of times on Stackoverflow and other sites and there are many proposed approaches. However, all of them have drawbacks in one form or another.

In this posting, I am assuming that there is no solution provided for this in Android's APIs, e.g., something like: Application.getTask().getRootActivity().

Wouldn't it be nice if there was :-)?

So, to be clear, I'm not asking for an answer to How can I get the current Activity?

Instead, I am asking for the reason that such a capability has not been provided. Given that each running app has a task (assuming that the task hasn't been emptied) and each such task has a root Activity, it would seem to be easy to provide access to that root Activity.

The fact that that such access is not provided, when it is so clearly desired, implies to me that there is something fundamental about the Android architecture that I don't understand.

What is it that I'm missing? Why is this information not provided by the Android APIs?

For background, here is a section summarizing some of the approaches that have been proposed. I found the following two links particularly informative (each of the approaches below is presented at one or both of the links).

Links

Approaches

  • Static Hook
  • Reflection
  • ActivityManager
  • Other (Instrumentation, AccessibilityService, UsageStatsManager)`

ActivityManager

The ActivityManager approach only provides the name of the Activity class, not the current Activity instance. E.g., for a Context instance c:

c.getSystemService().getActivityManager() .getAppTasks().get(0).getTaskInfo() .topActivity().getClassName()

Reflection

My favorite is reflection, as proposed by _AZ, but that approach is fragile, given that it relies on internals. What I would like to see from Android is this approach provided via a standard API that developers could then safely rely on.

Static Hook

The most common approach is using a static hook to save a reference to the currently running Activity. The hook can be either per-Activity or per-Application. Memory leaks can be avoided by saving/destroying the hook's value (e.g., in onCreate()/onDestroy(), onStart()/onStop(), onPause()/onResume()). However, issues can arise when multiple Activities are involved (e.g., due to overlapping lifecycles -- see below).

I implemented a static hook approach which does the following (to be perfectly transparent, I haven't implemented #1 yet -- I am currently using a per-Activity static hook, which is a bug).

  1. Provides a class that extends Application to provide the hook. The hook contains a Stack; each node in the stack is a simple ActivityInfo class which contains a reference to an Activity instance as well as the state of that instance (CREATED, STARTED, RESUMED).
  2. Provides a class called ActivityTracker that extends Activity. I then extend each of my Activities with ActivityTracker. ActivityTracker uses its lifecycle callbacks to push/pop itself to/from the stack and to update its state -- my other Activities don't have to do anything.

In theory, this would allow me to always know the full state of the task's back stack -- the full set of Activities, including the root Activity, as well as their current state. In practice, however, there is a twist -- when one Activity starts another Activity, their lifecycles overlap. During that period, peeking at the stop of the stack can yield an unexpected Activity instance.

From: https://developer.android.com/guide/components/activities/activity-lifecycle.html#soafa, "Coordinating activities":

Here's the order of operations that occur when Activity A starts Acivity B:

  1. Activity A's onPause() method executes.
  2. Activity B's onCreate(), onStart(), and onResume() methods execute in sequence. (Activity B now has user focus.)
  3. Then, if Activity A is no longer visible on screen, its onStop() method executes

Of course, this could be managed also. The bottom line is that we do have a global context available for storing information (the Application) and we do have full information about Activity lifecycle transitions, so with enough effort I believe that this static stack-based approach could probably be made pretty bullet-proof.

But in the End

But in the end it feels like I am simply rewriting code which probably already exists internally for managing an Activity back stack, which is why I ask (in case you've forgotten):

Why is there no Android API for getting the current Activity?


UPDATE


In this update, I'll summarize what I've learned from this thread and my own experiments and research. Hopefully, this summary will be useful to others.

Definitions

I'm going to use the following definitions for "Activity Visibility States", based on the Activity State definitions at https://developer.android.com/guide/components/activities/activity-lifecycle.html.

-----------------------------------
Visibility State   Definition
-----------------------------------
Not Visible        Created+Stopped
Partially Visible  Started+Paused 
Fully Visible      Resumed
-----------------------------------

Issues

The very definition of "Current Activity" is murky. When I use it, I mean the single Activity in the Fully Visible state. At any given instant, there may or may not be such an Activity. In particular, when Activity A starts Activity B, A's onPause() gets called and then B's onCreate(), onStart() and onResume(), followed by A's onStop(). There is a stretch between A's onPause() and B's onResume() where neither is in the Fully Visible state, so there is no Current Activity (as I define it). Of course, there are also situations where a background thread may want to access a Current Activity and there may or may not be an Activity at all, much less a Current Activity.

I've also realized that I may not always need a Current ("Fully Visible") Activity. In many cases, I may simply need a reference to an existing Activity, whether or not it is currently visible. In addition, that reference might be to just any Activity (for situations where I need to pass a generic Activity reference to some API method) or it might be to a specific Activity subclass instance (so that I can trigger some code specific to that Activity subclass).

Finally, there is the need to understand when Activity lifecycle callbacks are called by the main UI looper and how events like configuration changes are handled. For example, if I create a DialogFragment using an Activity intance which is currently in the "Not Visible" state, will it ever get displayed and, if so, when? Along similar lines, it turns out that the onDestroy() and onCreate() methods caused by a configuration change are contained in the same message in the UI's message queue (see Android UI Thread Message Queue dispatch order), so no other messages will be processed between those two callbacks (during a configuration change). Understanding this level of processing seems to be critical, but documentation on it is sorely lacking, if not missing completely.

Approaches

Here is a collection of approaches that can be used to address most of the above situations.

Background

  • For discussion, assume Activity A and Activity B, where A creates B.
  • Generally speaking, a "global" variable can be created by making it "public static" on pretty much any class. Conceptually, extending the Application class and adding it to the extended class would be good, but if that's too much work it could be included (for instance) in one of the Activity classes.

Generic Activity Reference

  • Useful whenever a generic Activity is needed.
  • Create a global variable. In both A and B, have onCreate() set it to "this" and onDestroy() set it to null.

Topmost Activity Reference

  • Useful whenever you want to access the currently visible Activity.
  • Create a global variable. In both A and B, have onResume() set it to "this". This approach works fine unless all Activities exit, in which case you may need to create a separate flag to indicate that situation. (That flag could be the Generic Activity Reference implementation mentioned above.)

Specific Activity Reference

  • Useful whenever a handle to a specific Activity subclass instance is needed.
  • In both A and B: create a global variable in the Activity subclass itself. Have onCreate() set it to "this and onDestroy() set it to null.

Application Context

  • Useful whenever a Context spanning the lifecycle of the entire app is needed or when you don't care about using a specific Activity Context (e.g., to create a Toast from a background thread).
  • You can get this from Activity's getApplication() and store it on a static hook.

Handling Configuration Changes

There may be times when you want to stop/start a background thread only across an Activity "session", where I define "session" to include the series of Activity instances which may be created and destroyed due to configuration changes. In my particular case, I have a Bluetooth Chat Activity and an associated background thread to handle the network connection. I don't want to have the connection destroyed and created each time the user rotates the device, so I need to create it only when one doesn't exist and destroy it only if a configuration change isn't underway. The key here is understand when onDestroy() is called due to a configuration change. This can be done with or without fragments. As is often the case, I prefer the non-fragment approach since the fragment approach doesn't seem worth the extra complexity to me.

Approach 1: Without Fragments

In onCreate(), create the background thread if it doesn't exist yet. In onDestroy(), destroy the background thread only if isFinally() returns false.

Approach 2: With Fragments

This works well because the FragmentManager will store fragment instances across configuration changes if setRetainInstance(true) is used. For an excellent example of this, see http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html. The example is for AsyncTasks, but can also be applied to managing a background thread (just create the thread instead of an AsyncTask in the fragment's onCreate() and then destroy the thread in the fragment's onDestroy()).

Closing

Fully understanding these issues requires a deep understanding of how the UI looper processes its message queue -- when Activity callbacks are called, how other messages are interleaved with them, when display updates occur, etc. For instance, if a DialogFragment is created using an instance of a non-visible Activity, will it get displayed at all and, if so, when? Perhaps some day Android will provide a deeper API to Tasks and their associated backstacks, along with documentation describing the UI's message processing and associated mechanisms in more detail. Until then, more "source code and/or ... empirical analysis" :-).

Thanks,

Barry

Community
  • 1
  • 1
Barry Holroyd
  • 1,005
  • 1
  • 11
  • 20
  • 3
    Asking "why did Team X decide to do Thing Y" is not especially useful here on Stack Overflow. The only people who can answer definitively is Team X, and it is unlikely that any of that team's members will see this question, let alone answer it. Anyone else is just offering opinions. Please note that there is no single "current activity", particularly in multi-window environments. And there may be no "current activity" at all (depending on what triggered the process to start). That's why we use things like event buses/reactive programming, so that we care less what the "current activity" is. – CommonsWare Jan 09 '17 at 21:35
  • 1
    Understood re. Team X comments, but there may be people who have a deeper understanding of this issue, which is what I'm trying to get at. By "current activity" I am referring to the root Activity in the task. I believe there is generally a single root Activity, unless that also exits and leaves just an empty process skeleton, which is likely to get reaped pretty quickly (I'm not sure about multi-window implementations -- good point... do you have a pointer to docs on that?). I care about the current Activity because (if one exists), I want my Activities to respond differently to broadcasts. – Barry Holroyd Jan 10 '17 at 00:09
  • "I believe there is generally a single root Activity" -- per task, yes. Your process may be associated with multiple tasks. "unless that also exits and leaves just an empty process skeleton, which is likely to get reaped pretty quickly" -- unless it has a service running, or has an exported provider that is in use, etc. "do you have a pointer to docs on that?" -- here is [Android 7.0's native multi-window](https://developer.android.com/guide/topics/ui/multi-window.html), but device manufacturers have rolled their own before that. – CommonsWare Jan 10 '17 at 00:14
  • Thanks for the multi-window pointer. From that doc: "In multi-window mode, only the activity the user has most recently interacted with is active at a given time. This activity is considered topmost. All other activities are in the paused state, even if they are visible." Based on that, I don't see any difference from single-window situations where an Activity gets paused if it is only partially visible. There is still only a single app currently "running" and it has a single root Activity, which is what I'm trying to get a handle for. – Barry Holroyd Jan 10 '17 at 17:01
  • Most applications have more than 1 `Activity`, so saying the "current Activity" is the "root Activity of the task" is nonsense. What you really want to know is the "topmost Activity" (ie: the one that is on the top of the task stack, the one that the user can currently see and interact with). Unfortunately there is a window during Activity-transition where the "current Activity" is a debatable issue (ie: it isn't clear which one it is). – David Wasser Jan 10 '17 at 17:28
  • @David -- good point. I should be saying "top Activity", not "root Activity". It still seems to me that there is only ever one (at most) topmost Activity, however. If A starts B, then A's onPause() gets called, followed by B's onCreate(), onStart() and onResume(), and only then by A's onStop(). I assume this is the window you are referring to -- correct? However, during this period presumably neither Activity is accepting input from the user, so neither would be "active", in that sense, and B would become "active" as of onResume(). My guess is B becomes the topmost Activity at that point. – Barry Holroyd Jan 10 '17 at 18:34
  • The problem isnt usually wirh user interaction. The problem is usually with background activities or asynchronous events (think server notifications) where the event needs to trigger some kibd of action on screen, which may be different depending on what Activity is on top of the stack. During activity transition it isnt clear which activity that is. Activity transition happens not only forward, but also backwards. One Activity gets paused and the underneath it gets resumed. But there is also a window here between onPause() and onResume(). – David Wasser Jan 10 '17 at 18:56
  • Good comments -- thanks. I probably need a better understanding of how the main thread looper works and how that affects Activity lifecycles. E.g.: when are lifecycle callbacks called and when does it update the display? And related: when are references to an Activity valid for use (e.g., a BroadcastReceiver gets the Activity instance from a static hook and executes a method on it) and which Activity(ies) will process messages sent to it(them)? It's probably not always the "top Activity" :-). Do you know of any good references describing that area? – Barry Holroyd Jan 12 '17 at 01:05

2 Answers2

0

If all you want you want to know is which Activity is foremost and accepting user interactions, just create a BaseActivity that extends Activity and override onResume() and save a reference to "this" in a static variable. All of your other activities should extend BaseActivity. You're done.

David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • Good point -- I was doing that, but using onStart() to set the static variable (because the UI becomes visible then) and clearing it in onStop() -- that approach caused problems because of the lifecycle overlap. Perhaps I have a fundamental misunderstanding. If I queue a Message to update a text field owned by Activity A, and A is in the Stopped state, will A still receive and process it, but the results simply won't be displayed until A's onStart() runs? Sorry for the slew of questions... any pointers to docs describing this stuff would be deeply appreciate :-)! – Barry Holroyd Jan 12 '17 at 01:17
  • You can set the variable in `onStart()` if you want, but you cannot clear it. Ever. It should only be "cleared" by being "set" by another `Activity`. I can't answet about the view update because it all depends how you "queue a Mesage to update". You'd need to show me the code you use and explain how that code is called. Then I can answer. Regarding documentation, there is none at that level of detail. You need to read the source code and/or do empirical analysis (ie: try it and discover). – David Wasser Jan 12 '17 at 10:36
  • Good advice wrt. to onStart() (I'd probably put it in onResume(), per your initial response). However, that seems insufficient to me. If the last Activity exits and a background Service or a BroadcastReceiver tries to locate the topmost Activity, it would get a handle to an Activity which no longer exists. As far as I can tell, a separate static variable would be needed to handle that aspect. Re. analysis and source code... yep, been doing lots of empirical exploration, looks like it's time for delving deeper into source code as well. – Barry Holroyd Jan 13 '17 at 19:23
-1

The short answer I would guess is that only one activity can ever be active at a time in a given app, and that activity obviously knows who it is (it is itself) -- so the only answer any activity can get to "what activity is currently active" can only ever be "you are, silly".

For simple apps with a clear division between the different activity classes, this works fine, and so that's a great percentage of most of the apps in the play store. It doesn't work so hot when you're getting real clever with encapsulation and polymorphism, as I'm sure you've discovered, but I don't think Google is really targeting those types of developers.

Just my $0.02, I don't think you'll get an "official" answer here.

alzee
  • 1,393
  • 1
  • 10
  • 21
  • 1
    Often true, but not when background threads or non-Activity components are involved. E.g., in my BluetoothChatDemo application I have a Chat activity which should exit if the user turns off Bluetooth via the Android Settings; control would return to the main "Chooser" Activity. In contrast, if ChooserActivity, which lists devices you can connect to for a chat, is running it should display a note (Toast, DialogFragment or TextView) saying that you need to turn on Bluetooth before continuing. A callback method (Handler?) implemented by both Activities is another possible solution I suppose. – Barry Holroyd Jan 10 '17 at 00:21
  • @BarryHolroyd Many activities can be "active" in a task, even if the user is only seeing/interacting with the topmost one. – David Wasser Jan 10 '17 at 17:31
  • @David -- Sorry. Clarification: by "active" I mean accepting user input, i.e., the topmost Activity. Per my comment above, I assume there is at most a single topmost Activity at any given instant and knowing what that is is often useful, precisely because it is the Activity currently interacting with the user. I remain mystified as to why doing so is so difficult. To your point(s), though, it would be nice to have broader access to the back stack, including the state of each Activity on it. – Barry Holroyd Jan 10 '17 at 18:40
  • @BarryHolroyd your example doesn't really make sense to me as far as needing to know which activity is active. The "Chat activity" can simply finish() / start the Chooser if it is notified that BT is turned off. The "Chooser" can decide what to do whenever it needs to. In neither of these cases does anyone need to "know" or "ask" what the current activity is. This is "the android way" of doing things, in general. – alzee Jan 10 '17 at 18:56
  • But how does the BroadcastReceiver know which Activity is currently active? Or am I off-base here? Can it simply check that each Activity exists (by getting its reference off a static hook which get nulled if it exits) and, if so, call its methods directly off the reference (e.g., even if it is in the Stopped state)? – Barry Holroyd Jan 12 '17 at 01:23
  • With a standard design every activity (and service) has it's own broadcast receiver; There isn't just one for the whole application. You activate and deactivate them with the activity itself. – alzee Jan 12 '17 at 13:05