I have an application that allows a user to visit user profiles from a photo gallery (think of Instagram where you can visit a profile of a user from an image, or visit a profile of a user that left a comment on the image, etc).
PROBLEM:
If a user continues to visit profiles and photo galleries and repeats this cycle about 10 times there are too many instances of ViewProfile and Gallery on the backstack which results in an OutOfMemory error. This is due to the fact that ActivityA calls startActivity() to start ActivityB (who can start ActivityC and so on) so finish() is not called on ActivityA or ActivityB.
WHAT I WANT TO KNOW:
Since the activities call other activities it is possible that the application can go a long time without calling finish() on the activities that are piling up on the backstack.
So my first question is this:
1) Should my onPause() methods be cleaning up as much as possible to reduce the activities footprint in memory?
2) Is it possible to reduce an activity's footprint to zero (or close to zero) if finish (therefore onDestroy) is not called (or can't be expected to be called in a reasonable amount of time if the user decides to wander off and explore images and profiles of other users)?
3) Am I overlooking something when it comes to the management of activities?
4) Is there a way to completely kill the activity but save its state so that when a back button is pressed it can be rebuilt as the user would expect it?
EXAMPLE OF DESIRED BEHAVIOR:
In the Instagram application it seems that a user can endlessly click on user profiles (from comments or likes) and then load images of these users, then visit profiles until the end of time without a crash. Clearly they are doing something different but I'm not sure what, exactly. It would appear that they go long periods of time without calling finish on their activities as well since one activity calls another and they maintain a backstack that can be unwound as a user clicks on the back button.
I feel like I'm doing something wrong but I don't even know if I know the right questions to ask in order to fix it. My application works if I avoid opening lots of profiles of random users or go back "home" that will reset the state of the application. Any help would be greatly appreciated.
The main issue will be your bitmaps in your case. They use the most memory out of everything. Do not ever hold one directly in your class. Use an LRUCache, decide how much memory to use on Bitmaps in general, and only hold the cache keys in the Activity/fragments. This will make sure Bitmaps use a fixed amount of memory and the increase from additional activities is just the size of the key. A 2 level cache (memory cache and a disk cache) is a good idea. Depending on how bad the problem is, you may even want to go to the extreme of nulling out your ImageViews drawables in onPause so that reference isn't kept around either. It may be necessary in your case.
In more general solutions, you need to do just what you think- in onStop you need to jettison what you can from memory and reload it again in onStart, either from disk or from the network, preferring disk where possible. Data structures shared between activities should not be stored multiple places in memory, they should be shared via a service or singleton. You can't 100% stop this problem (the user can always decide to visit 1000 profiles), but if you prevent memory hogging resources from being replicated you can make it one they almost have to try to get to.
Related
I recently wrote a demo app, which just needed to display some data temporarily --- I meant for the data to disappear once the app was properly destroyed by the user. Toward this, I read the page
The Activity Lifecycle , which seems to recommend overriding the Activity methods
onRestoreInstanceState() and onSaveInstanceState().
It worked great! The data was preserved through screen rotations, and sending the app to the background.
But then I would leave the app running and walk away, and when I looked at it again, the data was gone.
I spent hours trying to de-bug my app, and re-reading that page.
Finally, I read
Saving UI States. It refers to overriding these methods as "ViewModel" approach, and explicitly states that data saved this way does not survive system-initiated process death --- which explains my observation.
My main question is: what on earth is the practical application of this "ViewModel" persistence approach? What is the use-case for a persistence mechanism that randomly disposes of data when the user isn't looking?
(I guess this is an old API left over from the times when apps didn't run in the background. But I don't see that reflected in the documentation.)
A second question is, reading the first page, how on earth was I supposed to understand this unfortunate behavior? Did I miss something? (It is very long.)
what on earth is the practical application of this "ViewModel" persistence approach?
It is not a persistence approach. A ViewModel is a way of holding onto state across configuration changes. Using a SavedStateHandle with ViewModel — which maps to onSaveInstanceState() and onRestoreInstanceState() — is also useful for a fairly narrow use case:
User is in your app and does something that you don't want to save to disk or the server (e.g., the user didn't click "Save" yet)
User turns off their phone screen or switches to another app (e.g., via system HOME navigation or the overview screen)
Time passes
Android terminates your app process to free up system RAM for other apps
Within ~30 minutes of having left your app, the user returns to your app
At this point, Android wants to pretend that your app had been around all along, despite the fact that your process had been terminated. So, Android will not only start up a fresh process for you, but it will recreate the last activity the user had been on... and you get your saved instance state back as part of this.
However, this is not a persistence approach. For data you want to have survive long term, you need to save it to disk (SQLite, SharedPreferences, JSON file, etc.) or to some server. Notably, if the user leaves your app for an extended period (over ~30 minutes), Android will not attempt to restore the instance state, and your app will be started normally.
You need to use a SavedStateHandle with a ViewModel to get data persistence when the system terminates your app in the background. Otherwise it's more about sharing data between components, and surviving Activity destruction e.g. on screen rotation without having to do a lot of boilerplate handling.
Just like with onSaveInstanceState, this is purely about persisting data when the system kills your running app to recover memory, so that when the user switches to the "running" app again, it can be recreated and restored exactly as it was. It doesn't save any data when the app is intentionally stopped, e.g. calling finish(), the user backing out or swiping it away etc.
This stuff should always just work - if you were seeing your data "go missing" and the app wasn't crashing in the background, it's possible your save/restore logic wasn't working. A good way to test that is going to Developer Options on your device (if you don't know how to get that do a search, it depends on your device) and enable Don't keep Activities. That will destroy them as soon as they go to the background and it should help you test how that's handled. The fact you were handling rotations ok suggests it was a background crash though, but that depends on how you were handling configuration changes
I got some issue when I developing an App.
After I minimized the app or turn the screen off, and open lot of other apps or reopen the phone after a long time.
When I restart my app, it try to resume and keep showing the same page(with fragment).
But the data I need was already been destroyed so it will be null.
The data is an object array, I know maybe I can store them in db.
But due to the data will update every time user click something.
So I don't want to save it into data base, I guess that means lot of storage I/O witch is not necessary.
I'm wondering if there is any solution to restart the hole app when things is destroyed?
Or the only way to make it happen is I handle the null array and do the reload myself?
I don't really want to do that cause I guess that will bring me many unexpected issues cause the data is related with many pages.
Too many situations I have to consider when do switching pages.
Are there any advice?
But the data I need was already been destroyed so it will be null
That is because your process was terminated and you did not save your state.
But due to the data will update every time user click something
Or, you could fork a thread to save the data as part of your onPause() or onStop() methods. There are many possibilities between "never save" and "save on every click".
So I don't want to save it into data base, I guess that means lot of storage I/O witch is not necessary
If you want the data to be there 30+ minutes after the user left the app, your choices are to save the data locally (file, database, SharedPreferences) or save the data on the Internet somewhere.
For small amounts of data over shorter time periods, you could put the data in the Bundle supplied to onSaveInstanceState() and then pull the data out of the Bundle again later (e.g., in onRestoreInstanceState() of your activity). You already should be doing this to handle screen rotations and other configuration changes.
I'm wondering if there is any solution to restart the hole app when things is destroyed?
You are welcome to add android:clearTaskOnLaunch="true" to your launcher activity, to indicate that you always want to start over from scratch whenever the user leaves your app and tries to come back to it. Users will not appreciate this, as this means that they will lose their state even for being out of your app briefly (e.g., a quick reply to a text message). This attribute does not terminate your process, but it will force the user back to the launcher activity and will eliminate any other activities that had been in your app previously.
Or the only way to make it happen is I handle the null array and do the reload myself?
That is what developers normally do, yes.
I'was wondering a simple thing. I'm making an android app and I started asking my self about memory usage.
What does the android OS make when I call a new intent??
Imagine i have an intent with only one button and this button onclickmethod is making a new intent of the same activity.
If on click I do this??
Intent activityN = new Intent(Activity.this,Activity.class);
startActivity(activityN);
is the firstActivity killed or does android keep it?
And if I click 50 times??
thanks for your answers
Activities life cycle is a tricky topic.
In most cases the activity will be kept in memory, but in some cases Android may decide to destroy it to reclaim resources. You have no control over this behaviour, which may change between OS versions and even hardware configurations. Don't try to fight it - embrace it.
Activity state is saved in onSaveInstanceState(Bundle), which is called before placing the activity in a background state.
When the Activity is about to be shown - but was destroyed to reclaim resources - it can be recreated using savedInstanceState in onCreate() method. You are expected to handle this situation. Most programmers don't care, which leads to strange errors on screen rotations and after longer periods of inactivity.
You may think about this mechanism as a serialization/deserialization scheme, that allows Android to optimize memory usage, discarding the data that can be recreated on demand (such as UI layout) and saving only things that cannot be recreated, such as UI state (entered text, checbox states, etc).
Since Activity destruction is rather unpredictable under normal conditions, Android provides special developer's option to always destroy activities when possible. This allows you to properly handle all corner cases around activity life cycle without too much effort. Explore your device's developer options.
You may want to check out those articles to learn more about the topic:
http://developer.android.com/training/basics/activity-lifecycle/index.html
http://developer.android.com/training/basics/activity-lifecycle/recreating.html
Going back to your question about clicking the button 50 times... it will probably create 50 instances of activity, stacked on top of each other. It may be the case that Android starts to destroy first activities to make room in memory for new ones. Let's say that the device have memory for only 49 activities. You start 49 - all are kept in memory. You start 50th one and the 1st is going to be destroyed. Her state is saved in Bundle, so when you press back 49 times, the first one will be re-created from this saved bundle.
That depends on the flags you are supplying with your Intent.
If no flags then every startActivity creates a new Activity.
However, there can be FLAG_ACTIVITY_CLEAR_TOP orFLAG_ACTIVITYY_SINGLE_TOP flags.
It's boring to repeat it all here, have a look at the docs:
http://developer.android.com/guide/components/tasks-and-back-stack.html
Android keeps the first Activity. That's why when the second Activity is finished you can return (with results even) to the same state. If you clicked the button 50 times, you'd start 50 new activities. YOu generally want to avoid that, so its a good idea to disable the button after the first press. Luckily your new activity should quickly start and cover up the button, so 50 is hard to do (although 2 or 3 isn't).
My app's main activity is a Activity that contains a Webview to load web pages.
I override the method shouldOverrideUrlLoading(WebView view, String url) to make every URL request call up an Intent and load in a new same activity containing WebView.
Doing this is to provide a better experience when BACK key is pressed, will just finish the current activity and back to the former activity, need no time to render the page again comparing to use goBack() in single webview.
But now the problem is that, after I open many URLs, creating a long queue of activitys in the background, the memory it uses became large.
When I go back to launcher and check the progresses, I can see my app caches more than 200M data. This is not acceptable...
And it's interesting that I can see my app used up my memory, but in the Heap view of DDMS in Eclipse I can see the app allocated no more than 10M memory. So I guess the 200M is webStorage cached by Webview?
Is there any way to control the memory?
I'm considering just save maybe 5 layers of activities at a time and when go back 5 times just jump back to home page. But still don't know how to release memory beside the 5 activities I need, which I'll never use again?
Or if it's because the WebView is keeping web page cached automatically, how can I manager this manually? Such as setting a limit of maximum cache size or page count?
Generally speaking I agree with Kevin's comment. I think keeping multiple Activity objects around to prevent reloading a WebView is counter-intuitive to a mobile environment with such limited resources.
That being said, you have a lot of different question and solution possibilities, so I don't have a single answer in mind. Look through these links to see if anything is helpful:
ActivityManager - has a ton of stuff you might be able to use, check out it's sub-classes.
ActivityManager.RunningTaskInfo - Never used it, but seems to have some useful stuff, especially with determining which Activity's are running.
ActivityManager.MemoryInfo - Can give you info on the available system memory as well as a bottom threshold of memory.
Application.onLowMemory() -Tells you when your app has been a memory hog. You could override this method and start destroying Activity's when this gets called. You probably need to call super.onLowMemory() to make sure the OS handles what it needs to.
One possible solution involving controlling the number of activities:
Override Application and create a public static ArrayList<Activity> that holds 5 Activity objects. Whenever you perform an onCreate() for an Activity you could add the Activity to the ArrayList and then check the size. If size is > 5 then send an intent to the Activity at position 0 that will cause it to handle the intent and call finish(). Then remove the object from the ArrayList.
Shouldn't be too much work, but the down-side is that you have to manually manage things. I am sure there is a more savvy solution possible.
I'm building an Android application that's based around an enhanced WebView (based on PhoneGap). I've enhanced the WebView so that from JavaScript running inside you can invoke the native contact picker to choose a phone number (which may be supplied by Facebook for example).
The problem I have is that the native contact picker runs in an activity in another process and the Android docs say that while another activity is open my activity may get destroyed due to memory constraints. I haven't actually seen this happen in my application but if it did then I'm guessing my WebView's state would be destroyed and the code that was waiting for the picked contact would be terminated.
It seems a bit crazy that the activity requesting a contact could be destroyed while the contact picker is open. Does anyone know if that does indeed happen? Is there a way to persist the state of the WebView if it does?
Thanks,
-Shaun
Does anyone know if that does indeed
happen?
You're looking at the problem too simply.
You have a WebView. You open the contacts application. While the user is in the contacts application, a phone call comes in. While on the phone call (using a Bluetooth headset), a text message comes in, so the user opens that up from its Notification. While still on the phone call and texting away, a text comes in with a link, so she taps it and brings up the Browser application.
By this time, your activity is surely destroyed, except on maybe some of the most recent phones that have a fair bit of RAM.
Now, is that common? No. However, this also has nothing to do with the contacts application -- if the user presses HOME, at some point in the future, your activity may be destroyed to free up RAM as well.
Is there a way to persist the state of
the WebView if it does?
That depends on what you consider "the state of the WebView" to be. This really is PhoneGap's job, if you are making a PhoneGap-based app. So, you might consider asking them.
There is no way to persist the DOM. There are trivial ways to persist the URL (see onSaveInstanceState()). And there may be stuff in between that you consider part of "the state of the WebView" that may or may not be possible to save.
The long and detailed answer is a bit complicated, but essentially it comes down to a few points:
If the Android OS has to go cleaning up Activities or Services, it knows how to prioritise which ones should go first. It does this based on whether they're currently in the foreground (last to go), in the middle of executing code, waiting for a result, or simply sitting inactive in the background (first to go). You can be reasonably certain that if your WebView Activity launched the contact picker using the startActivityForResult, it won't be killed
There's a whole system for saving data if an activity is killed, such as the onPause method (which are triggered as soon as your activity leaves the foreground), the onSaveInstanceState method which is called when your activity is about to die. Read up on those to get more information, and the configurationChanged method when the screen orientation changes. If you haven't at least skim read the Activity Lifecycle document on the developers page, you must do that.
Lastly, I'm sure this question has been addressed many times, but with slightly varying wording or situations. Have a look around, see what else you can find.