In my Android app, when a user tries to transition from one activity to another, there may be some global state that indicates they need to complete some other action first.
To accomplish this, I've written a class with the following code:
private static WeakReference<Activity> oldActivityReference;
private static Intent waitingIntent;
public static void pushActivity(Activity currentActivity, Intent newActivityIntent) {
Intent blockingIntent = ThisClass.getBlockingActivity();
if (blockingIntent != null) {
ThisClass.oldActivityReference = new WeakReference<Activity>(currentActivity);
ThisClass.waitingIntent = newActivityIntent;
currentActivity.startActivity(blockingIntent);
return;
}
currentActivity.startActivity(newActivityIntent);
}
When the blocking activity finishes, it calls ThisClass.blockingActivityFinished(). That will check to see if the weak reference to the old activity still exists and, if so, launch the original intent from that activity. If not, it will launch the original intent from my application's context.
My question is,
Does this sound sane? Are there any potential memory leak issues with this technique? Is there a better way to accomplish this?
EDIT -
To be clear, the types of events that might trigger an interruption are 1) a server ping indicating that the current app version is deprecated 2) any server RPC indicating that the user's credentials are no longer valid. I do not want to add logic to every Activity to handle checking for these, and resuming business as usual once they complete. That is a violation of DRY, and error-prone in a team environment.
Does this sound sane?
I'd never use this technique. Mutable static data members are dangerous, WeakReference notwithstanding. In particular, I'd expect this to fail if the user does the unthinkable and, say, uses their phone as a phone, or otherwise leaves your application flow for an extended period of time. Your activities may be destroyed and your process terminated to free up RAM, yet the activities would remain in the task and might be reactivated. At that point, your state is whack, because the statics got nuked.
Are there any potential memory leak issues with this technique?
You're leaking an Intent.
Is there a better way to accomplish this?
For the purposes of the rest of this answer, I'm going to refer to your starting point as Activity A, the "some other action" as Activity B, and the desired end as Activity C. So, in your code, newActivityIntent is for Activity C, blockingIntent is for Activity B, and currentActivity is Activity A.
Option #1: Put the decision-making process in Activity C, rather than Activity A. Have Activity C check the condition in onCreate() and immediately calls startActivity() for Activity B if the conditions require Activity B to be shown.
Option #2: Leave the decision-making process in Activity A, but pass the boolean (e.g., true for "we gotta show Activity B") in an Intent extra for the startActivity() call for Activity C. Activity C checks the boolean in onCreate() and immediately calls startActivity() for Activity B if the boolean says so.
In these options, you avoid the statics.
Related
In my App there is three activities: A, B, and C. Activity A is the main activity.
When I start Activity B from A using Intent, A is automatically destroyed. However, I am not finished with A when I start B. Then, when I press the back button in the device the A is started using onCreate and I've lost all my data since I am not using an Intent to go back.
The same happens when opening C from B.
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
startActivity(new Intent(ActivityA.this,
ActivityB.class));
}
});
What's going on? How do I fix this?
This was a mistake by me due to this the Activities were automatically destroying. I Checked the option under Setting=>Developer Option=>Don't Keep Activities. Due to that destroyed every activity as soon as the user leave it.
By Unchecked this option my app working fine.
If you are using 'Activity B' to get some sort of one-off result then it sounds like you need to be using the startActivityForResult construct. See [the docs](http://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int))
However, to maintain state information about 'Activity A' without persisting it anywhere you want to be bundling all the information (fields the user has filled in etc.) into the Intent bundle passed to 'Activity B'. This bundle is delivered to 'Activity B's onCreate (and a few other lifecycle points) where you can continue populating any pertinent information from 'Activity B' and writing that information back to the intent bundle before restarting 'Activity A' with the modd'ed bundle when you are done with 'Activity B'. But here be dragons. This specific mechanism is employed in our app with a strict (wrong?) control over the back-stack. Specifically our 'Activity A' has the special manifest declaration of android:noHistory="true". This means that we also control what happens when the back button is pressed by overriding onBackPressed() at each activity and sending that same bundle through to a new instance of the activity we just came from. It looks like a normal back stack but since we are wanting to remember user input without persisting that information anywhere then it's a usable solution.
This allows for a wizard like input with each stage of the wizard input being it's own activity and only validated and correct information being persisted at the end.
An alternative would be to temporarily stored 'Activity A's data in the SharedPreferences area so that on restarting Activity A you can pull out the appropriate bits reassembling the original state of 'Actvity A' when you left it. Rest assured there is very little you can do when Android wants to kill your Activity A but you can accommodate it's very reasonable killing with the approaches outlined here.
Maybe it is not you that is destroying your activity.
From the page about Activity Life cycle: (Emphasis mine)
If an activity is paused or stopped, the system can drop the activity
from memory by either asking it to finish, or simply killing its
process. When it is displayed again to the user, it must be completely
restarted and restored to its previous state.
There explicit says that the system can kill your activity process.
Some possibilities is your device is low on memory, or your activities is memory hungry, making the system kill it.
Edit
In order to maintain your data, between the activities even if it gets destroyed. One way is to use the Application class, check this answer on Using the Android Application class to persist data, maybe that's what you're looking for.
As I understand it, an activity being destroyed is not equivalently to an activity being finished.
Finished
The activity is removed from the back stack.
It can be triggered by the program (e.g. by calling finish()), or by the user pressing the back key (which implicitly calls finish()).
Finishing an activity will destroy it.
Destroyed
The Android OS may destroy an invisible activity to recover memory. The activity will be recreated when the user navigates back to it.
The activity is destroyed and recreated when the user rotates the screen.
Reference: Recreating an Activity
So how do I finish a destroyed activity? The finish() method requires an Activity object, but if the activity is destroyed, I have no Activity object - I am not supposed to be holding a reference to a destroyed activity, am I?
Case study:
I have an activity a, which starts b, which in turn starts c (using Activity.startActivity()), so now the back stack is:
a → b → c
In c, the user fills out a form and tap the Submit button. A network request is made to a remote server using AsyncTask. After the task is completed, I show a toast and finish the activity by calling c.finish(). Perfect.
Now consider this scenario:
While the async task is in progress, the user switches to another app. Then, the Android OS decided to destroy all 3 activities (a, b, c) due to memory constraints. Later, the async task is completed. Now how do I finish c?
What I have tried:
Call c.finish():
Can't, because c is destroyed.
Call b.finishActivity():
Can't, because b is destroyed.
Use Context.startActivity() with FLAG_ACTIVITY_CLEAR_TOP so as to raise b to the top, thus finishing c:
// appContext is an application context, not an activity context (which I don't have)
Intent intent = new Intent(appContext, B.class); // B is b's class.
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
appContext.startActivity(intent);
Failed, appContext.startActivity() throws an exception:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
Edit: Clarification: I need to wait until the async task finishes and decide whether to finish c based on server's response.
android.util.AndroidRuntimeException: Calling startActivity() from
outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK
flag. Is this really what you want?
This exception used to occur when you are starting an activity from
the background thread or service. You need to pass
FLAG_ACTIVITY_NEW_TASK flag whenever you need the "launcher"
type of behavior.
Just add mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); to avoid this exception.
The way you are trying to kill the activity is not recommended, let the
android handle itself. There isn't any meaning to finish an activity
which is already destroyed.
Now, what you can do?
If you are facing problem in finishing activity when app is not in foreground, what you can do is to implement a security check which will finish the activity only when app is in foreground to go to back-stack activity or else just skip that step.
I think you are trying to kill the activity when app is in background. It seems a little bit difficult to do so, but you can make use of onUserLeaveHint to decide when app is going in the background in-order to finish the activity or you can finish the activity by adding finish(); in onStop(). Just make sure that asynctask's onPost() don't finish it again in-order to avoid the exception.
Have a look at android:clearTaskOnLaunch attribute and set it to true.
Google Doc says about this attribute is:
for example, that someone launches activity P from the home screen,
and from there goes to activity Q. The user next presses Home, and
then returns to activity P. Normally, the user would see activity Q,
since that is what they were last doing in P's task. However, if P set
this flag to "true", all of the activities on top of it (Q in this
case) were removed when the user pressed Home and the task went to the
background. So the user sees only P when returning to the task.
and i think this is the exact case which you want.
Hope this will give you some hint to achieve your desired task.
you can broadcast your action from the onPostExecute method in c and register a broadcast receiver to receive for that action in a and b. Then do finish in that receiver onRevice method
In c , AsyncTask,
void onPostExecute(Long result) {
----
Intent intent1 = new Intent("you custom action");
context.sendBroadcast(intent1);
}
In a and b
registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
finish();
}
},new IntentFilter("you custom action"));
Personally, I'd use the notification bar to notify the user of the status of his query.
This way, I'd avoid the entire issue of having an unfinished activity. And I'd only keep the activity unfinished only if the user had not clicked on the submit button yet.
Regarding android manual onDestroy() called exactly before activity is destroyed, so you can call finish in it (even you can stop your bg thread before killing the activity completly).
We can assume that if activity was killed we don't interested in bg thread either, and for example if bg thread is to download image or etc that needs to be completed - so you have to use service instead of asynctask.
Can't finish a destroyed activity directly, so just finish() it in its onCreate() (suggested by #Labeeb P). Here's how:
If the activity is already destroyed when trying to finish it, save a boolean flag somewhere instead.
if(activity != null)
{
// Activity object still valid, so finish() now.
activity.finish();
}
else
{
// Activity is destroyed, so save a flag.
is_activity_pending_finish = true;
}
If the flag needs to stay even if the app is destroyed, use persistent storage, e.g. SharedPreferences (suggested by #Labeeb P).
In the activity's onCreate(), check the flag and call finish().
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if(is_activity_pending_finish)
{
is_activity_pending_finish = false; // Clear the flag.
// This activity should have been finished, so finish it now.
finish();
return;
}
...
}
If there're multiple instances of the same Activity class, you may need something more than a boolean flag to identify the specific instance of activity to finish.
Calling finish() in onCreate() is actually a legimate operation, as it is mentioned in the doc:
... you might call finish() from within onCreate() to destroy the activity. In this case, the system immediately calls onDestroy() without calling any of the other lifecycle methods.
Other considerations:
It may not be a good idea to finish an activity while the app is in background, especially if it is the only activity. Make sure that you don't confuse the user.
For better user experience, if you finish an activity while the app is in background, you may want to inform the user. Consider using toasts (good for short notices) or notifications (good for long operations that the user may have forgotten)(suggested by #Stephan Branczyk and #dilix).
Of course, an activity being destroyed doesn't necessary mean that the app is in background (there might be another foreground activity). Still, the above solution (calling finish() in onCreate()) works.
When the system tries to destroy your Activity, it calls onSaveInstanceState. In here you can call finish(). That's it.
Warning: I've never tried this, so I don't know for sure if there are any issues with calling finish() from onSaveInstanceState. If you try this, please comment and let me know how it works out.
Sorry for answering this almost 10 years later.
In my understanding the premise of the question is wrong, mainly this part:
"While the async task is in progress, the user switches to another app. Then, the Android OS decided to destroy all 3 activities (a, b, c) due to memory constraints. Later, the async task is completed. Now how do I finish c?"
In my understanding if the operating system decides to destroy all three activities due to memory constraints, it won't destroy only them, but the whole process, and this should be including the AsyncTask in question. So, the async task won't be able to complete as well.
Resource: https://medium.com/androiddevelopers/who-lives-and-who-dies-process-priorities-on-android-cb151f39044f
mainly this line from the article: "Note that while we’ll talk about specific components (services, activities), Android only kills processes, not components."
In todays world, I guess that a WorkManager would be used for running work that needs to be guaranteed to run.
I have 2 activities. Main Activity A & Activity B
I do not want Activity A to destroy. I am starting Activity B in a new task.
public static void startActivity(Class<?> startClass) {
Intent intent = new Intent(Constants.getActivity(), startClass);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Constants.getActivity().startActivity(intent);
}`
Constants.getActivity() returns the Context on current activity
startClass is the either activity "A" or activity "B"
Thing is they create/destroy the activities and they leak. Am I doing it wrong? How can I start activity "B" from activity "A" and vice versa keep them both in background when I dont need them.
First of all, what are you trying to do? You should always separate things you want to do in the background from your UI. Think of your Activities are simply a container to show the UI, everything else can be stored and restored from persistent storage or savedinstance bundles.
It is very crucial that you understand the difference between Activity lifecycle and Service lifecycle here.
I'm going to refer to my answer from another question here:
Your app will be in the background, onResume and onPause will both be called, provided that the OS have enough memory to keep the new app and all the old apps.
If you have a long running process that you need while the user not looking at it, use a service.
If you need the user to return the app in the same state, you need to do the work in onResume and onPause to save the state information and initialize your UI again when the user comes back. If you are really worried that it can get killed (well, it shouldn't lose the bundle I think), you can store them in SharePreferences for your app.
If you want to know when the app returns from that specific share intent, use startActivityForResult
You cannot keep an activity "alive" as you said. When an activity is paused, Android system is always able to claim its memory and unload it, at any time.
But if you want to switch from A to B, then maybe A and B can be different views inside a single activity. Maybe you'll want to have a look at http://developer.android.com/reference/android/widget/ViewFlipper.html
When you use tasks, cleanup is very important. You need to cleanup all tasks in the activity. readthis
If the activity does not have a lot of crazy initialization, just use finish and onCreates. Else be aware that onResume will be called often as you switch between activity's. Cleanup will be crucial here. If you dont cleanup, its possible one of your activities (with dead object reference from the other cleaned up activity) may come back up from the activity stack and throw exceptions. Its very difficult to debug this kinda exception.
I've got four activities which the application cycles through. The first one fetches huge amount of data, so I don't wish to do that more than once. Thing is that if user presses back key on last activity, I want to return to the first one without reloading the activity. I'm currently thinking startActivityForResult methods and finish the two previous ones, but there may be a better solution?
Scenario:
Hasslarn,
The first thing that you must understand is that you have very little choice as to whether an Activity will be reloaded or not. That is determined by the system (largely). With that said, there are a number of things you can do to limit the system's desire to kill the Activity. Additionally, you may use a number of tools at your disposal to limit the impact of such a possible closure
Finish every child Activity as it becomes unimportant. This will free resources lowering the need to get rid of unused Activities (even temporarily).
Find a simple, but clever way to limit loads.
Based on the information provided, your proposed solution is a viable way to accomplish both. However, I would ask "Is Activity B required to be active while Activity C is open?" If not, you may want to do the following:
startActivityForResult(Activity B)
When Activity B is done, send result back to Activity A and startActivityForResult(Activity C)
Finally, when Activity C is done, you may startActivityForResult(Activity D) and back will work with no effort and you won't have to close the other activities.
Furthermore, if you press Back on Activity C, you can supply a cancelled result to Activity A forcing a restart of Activity B if you so desire. Depending on the required processing for Activity B, this may not be any hassle at all for your app.
Regardless of how you approach this, I recommend finding a way to cache this data such that onResume(), you may reload quickly if needed. This is because there is NO way to ensure that your original Activity will not be released to make way for the others.
Hope this helps,
FuzzicalLogic
Try using this:
Intent a = new Intent(this,A.class);
a.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(a);
return true;
Activity A will get reordered to front, without creating a new instance. If you want to pass extras through intent , you can get the intent extras in onNewIntent(intent) in Activity A.
My app is made of two activities, A and B. I'm considering this sequence of steps:
Activity A is started.
A launches B [A is paused, B is running].
B launches a map intent [A and B are both paused now].
Now the user is using the maps application and the system decides it needs more memory. Can the system kill only one of my activities for memory, or will it always kill all activities in a "process" in this situation?
Both activities share some static data like:
class Data {
public static String mName;
public void save() {
// write to file: mName;
}
public void load() {
// mName = read from file;
}
}
ActivityA.mTextView.setText(Data.mName);
ActivityB.mListView.addText(Data.mName);
so when any activity in my app gets onSaveInstanceBundleSate() called, I call Data.save() to write it to disk. Now the question is, in an Activity's onCreate() method, should I simply check to see if Data.mName == null, and if so, assume the Activity is returning from a kill state, and try restoring from disk again? I'm unclear when this restoring should be done, considering Activity A may or may not still be alive etc. - and I don't want to corrupt state if Activity A is still alive but B is coming back from a kill state,
Thanks
Thanks
Probably the best solution is to move your static data to a Service. That way the data can be saved and restored when android shuts down the Service rather than when Android shuts down either of the individual activities using the data.
Without using a Service (or alternately a Content Provider, or even overriding Application although that seems to be frowned upon), you have to manage that lifecycle yourself, which as you've seen can be tricky.