Android Activity Memory - android

I am doing a lot of reading on this currently but thought it never hurts to ask a knowledgeable group of developers as well.
This is a popular issue and there seem to be a lot of different ways of handling it.
I have an Android application. It uses OpenGL so when it starts up does a fair amount of data loading both from disk and then in VRAM. When the screen is rotated as well as when the power button is pressed (to put the phone into sleep mode) I lose all data and the activity is killed.
I don't want this. The app takes about 7 seconds to boot up and load its data and it is really annoying if you just sleep your phone for a second then have to wait the app to load all again.
I am familiar with the app lifecycle as outlined by Google but having a hard time implementing a solution that works for my particular case.
Any suggestions? My overall goal is to preserve the following in memory when the phone sleeps are is rotated:
1. App memory on heap
2. App memory in VRAM
Thanks!
So far:
public class MainActivity
private FH_SurfaceView fGLSurfaceView;
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
}
#Override
public void onRestoreInstanceState(Bundle savedState)
{
super.onRestoreInstanceState(savedState);
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Check if the system supports OpenGL ES 2.0.
final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;
if (savedInstanceState != null) {
/* NEED TO DO SOMETHING HERE?? */
return;
}
else{
this.fGLSurfaceView = new FH_SurfaceView(this);
setContentView(fGLSurfaceView);
}
}
#Override
protected void onResume()
{
super.onResume();
}
#Override
protected void onPause()
{
super.onPause();
}
}

The problem is that your Activity is holding a large amount of data. When the phone sleeps, it will likely target it to be destroyed for a variety of reasons, primarily to make sure there's memory for other tasks, but also because it might eating up processing cycles and Android knows it is not visible. Hence your problem. This does not happen to all apps, only "heavy" ones.
There may be nothing that you can do about this, but here are some suggestions:
Analyze your data loading. It might be that some parts are computationally expensive and therefore they are slowing the process, while other parts are heavy on memory but light on processing. Then in onPause dump the heavy memory part to improve the odds that Android won't kill your app. You might be able to get a 1-2 second load time instead of your current 7.
Consider creating a Service where the data resides and then binding to it in order to access it. Android does not expect a Service to be visible, and you can signal processing to cease in the onPause or onStop of your Activity to prevent Android from killing the service. I realize this is "tricky" as your data is currently in a custom SurfaceView class - and storing display data not on the UI thread is a pain.
Also, you might put the data into the Application class for similar reasons. The benefit of this is that you do not need binders to access the data. The drawback is that Android is more likely to destroy this than a Service I think (but that's experience talking, not testing - so, educated guess?). Also, messing with the Application class should be done with caution.
If all else fails, then try to provide an entertaining transition that acknowledges the silliness of the situation. In other words, communicating that you are aware of an unpleasant situation to a user is far better than ignoring it and hoping they don't mind.

Related

Detect whether application is quit by the OS because of low RAM

In the application I'm building, I need to detect the application quitting if and only if the application has been quit when its in the background because the OS is reclaiming memory.
From my own experimentation, the onDestroy is called on EVERY instance. I've tried checking for isFinishing but I'm not 100% sure which situations this isolates it to.
#Override
public void onDestroy()
{
super.onDestroy();
Log.i("V LIFECYCLE", "onDestroy");
if (!isFinishing())
{
// are we here because the OS shut it down because of low memory?
ApplicationPreferences pref = new ApplicationPreferences(this);
// set persistant flag so we know next time that the user
// initiated the kill either by a direct kill or device restart.
pref.setThePersistantFlag(true);
Log.i("DEBUG", "onDestroy - ensuring that the next launch will result in a log out..");
}
}
Can anyone shed light on my issue here? Thankyou.
Through trial and error I have worked out a solution that works perfectly for anyone thats interested. I have narrowed down the case when the application state is being resumed (onResume) in the case of the OS reclaiming memory.
public boolean wasJustCollectedByTheOS = false;
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
super.onSaveInstanceState(savedInstanceState);
// this flag will only be present as long as the task isn't physically killed
// and/or the phone is not restarted.
savedInstanceState.putLong("semiPersistantFlag", 2L);
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
long semiPersistantFlag = savedInstanceState.getLong("semiPersistantFlag");
if (semiPersistantFlag == 2L)
{
savedInstanceState.putLong("semiPersistantFlag", 0L);
this.wasJustCollectedByTheOS = true;
}
}
// this gets called immediately after onRestoreInstanceState
#Override
public void onResume() {
if (this.wasJustCollectedByTheOS){
this.wasJustCollectedByTheOS = false;
// here is the case when the resume is after an OS memory collection
}
}
I don't know whether its help you or not,
From Android Activity class,
public void onLowMemory ()
This is called when the overall system is running low on memory, and would like actively running process to try to tighten their belt. While the exact point at which this will be called is not defined, generally it will happen around the time all background process have been killed, that is before reaching the point of killing processes hosting service and foreground UI that we would like to avoid killing.
Applications that want to be nice can implement this method to release any caches or other unnecessary resources they may be holding on to. The system will perform a gc for you after returning from this method.
And Since: API Level 14
public abstract void onTrimMemory (int level)
Called when the operating system has determined that it is a good time for a process to trim unneeded memory from its process. This will happen for example when it goes in the background and there is not enough memory to keep as many background processes running as desired. You should never compare to exact values of the level, since new intermediate values may be added -- you will typically want to compare if the value is greater or equal to a level you are interested in.

Activity gets destroyed when PowerManager.goToSleep is called

I have an activity which sometimes gets destroyed by the system as a response to PowerManager.goToSleep(...) being called. The behavior is not consistent and I can't figure out the reason for this.
Here is the relevant code for my activity's onCreate which is the most relevant part of code:
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
IntentFilter userprsentfilter = new IntentFilter(Intent.ACTION_USER_PRESENT);
mUserPresentReceiver = new UserPresentReceiver();
registerReceiver(mUserPresentReceiver,userprsentfilter);
Log.d(TAG, "created.");
}
protected void onDestroy() {
Log.d(TAG, "finished.");
}
Somewhere, on some distant service and sometimes by the activity itself,
PowerManager.goToSleep(SystemClock.uptimeMillis())
is called causing the activity the get destroyed.
Can anyone please shed some light on why the system will try to sometimes destroy an activity when PowerManager.goToSleep is called? Also, is there a way to make the sure or lower the chances of the activity getting destroyed by the system? I can say with certainty that resources are not scarce when this happens.
All Android apps are subject to being shut down at any time. This can happen when the app is backgrounded, when it is consuming too many system resources, or when the phone sleeps. You can't (and shouldn't try to) alter that behavior: it does this on purpose and for very good reasons.
If you have an operation that needs to continue running under these circumstances, you need to implement it as a Service. This will allow it to run even when the phone is sleeping.

Activities not destroyed by the system before out of memory on Galaxy Nexus

I have a problem in some devices like Galaxy Nexus, where if you keep opening activities, you hit out of memory error. I thought I had some memory leaks which prevent activities from collected, but I couldn't find it. So I wrote this small activity (purely for test purpose.)
public class Test extends Activity {
private byte[] imageData = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button iv = new Button(this);
imageData = new byte[1024 * 1024 * 2];
iv.setText("Open");
iv.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(Test.this, Test.class));
}
});
setContentView(iv);
}
}
So basically, it allocates 2MB of memory, and you can open another instance of the same activity. On Galaxy S and Kindle Fire, if you keep opening, memory usage increases to a certain point, and then it starts destroying old activities for new activities.
However on Galaxy Nexus, it just goes up until 64MB and crashes with out of memory error.
So is there something I do not know about android memory management, or is this a bug on some devices? If it's a bug, how can I work around it?
Thank you.
I asked Dianne Hackborn about this (one of the Android framework engineers), and here's the advice she gave:
The point at which old activities are destroyed is an arbitrary
number, based on the total number of activities across the entire
system. This has always been the case. Relying on activities being
destroyed to reclaim memory like that is never going to be stable; the
activity manager doesn't know anything about the amount of RAM in a
process or the limit on its RAM or how much RAM a new activity is
going to take to actually have an idea when it should try to destroy
activities in each process.
In other words: You can't rely on the system destroying Activities as a means of memory management within your own app. You still need to be frugal about memory usage, and have your activities clean up their own memory usage when possible.
In this case, it might be wise to deallocate your memory once onStop() or onPause() is called.
As far as I know, Android Provides specific Memory to each Application, if you try to run more than that you'll get VMBudgetOutOfMemory error. Nexus Series uses generic Build of Android OS with minimum customization, and it totally depends OEM that how much they customize their device OS and hardware (in your case along with Memory Management). So it's better to handle control of Activities by yourself,if not then Android will handle it.The behaviour will totally depend on particular Device Specification.

Android lock screen behaviour

If I press home and come back to my app a little later I will find that the state has been preserved perfectly. For some reason however if I lock the phone and then unlock it, my app has been returned to the original state bar a few things here and there. When I looked into the logs I found that onCreate had been called while the phone was in a locked state. Because locking the phone is quite an off hand thing to do, having your game reset every time you do so is not desirable to the user. How can this be avoided at least for a longer period of time than a few seconds after locking the phone?
This is how Android OS works, it decides by it's own when to destroy your view. To avoid loosing this information there is a Method that can be reimplemented in your activity
#Override
public void onSaveInstanceState(Bundle outState){
iGameContent.saveGame(outState);
}
Save all your needed data into outState, and in the onCreate method, check if its a new instance or saved instance, like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
if (savedInstanceState!=null){
iGameContent.loadGame(savedInstanceState);
}else{
// Normal initialization
}
}
An example of the save/load to a Bundle is the following:
public void loadGame(Bundle aBundle){
iBadsHit = aBundle.getInt("iBadsHits",0);
}
public void saveGame(Bundle aBundle){
aBundle.putInt("iBadsHit", iBadsHit);
}
If your log is showing that onCreate has been called then that means your apps process was killed.
Do you know the Android Activity Lifecycle? If not, read up on it here: Android Activities
The behavior on screen lock could vary from one device to other. Some events could cause the destruction of the app. You can try to handle some of this events to avoid this situation specifying it on the AndroidManifest.xml:
android:configChanges="keyboardHidden|orientation"
These two are the most problematic in screen lock. Yo can find more information on the last chapter of this nvidia document

Determining if app is running in background

How can I tell that my application has been put in the background (i.e. none of my app's activities are visible anymore)? I need to determine this so that I can release resources shared among several of my activities (graphics, sound clips and a socket connection).
I've thought about keeping a global counter that's incremented in the activities' onStart() method, and decremented in onStop(). If the counter reaches zero, then all activities have been stopped and my app is running in the background. However I'm not sure if this is going to be 100% reliable. Also, I can't help but think that there must be a better way of doing this.
You shouldn't need to know this, but to answer you:
in your main activity:
public boolean inBackground;
#Override
public void onPause()
{
inBackground=true;
super.onPause();
}
#Override
public void onResume()
{
inBackground=false;
super.onResume();
}
Aren't you after the OnPause()/OnResume() events?
http://developer.android.com/reference/android/app/Activity.html#onPause()
http://developer.android.com/reference/android/app/Activity.html#onResume()
See http://developer.android.com/guide/topics/fundamentals.html#lcycles for an overview.
You could use a global counter assuming it is kept in persistent storage. Always keep in mind the system is free to unload and reload activities from device RAM based on pressure from other apps so instance variables of activities are probably not a good choice to house that data.
I think the Android way of handling a scenario like yours would be to manage your connection state in a service and use persistent storage to monitor application state.
If you need this functionality maybe your architecture is not well designed. Each activity must be in some way "standalone" so when it's stopped release any data associate with it. If you need to store some persistant data between activities use sql or some other data storage and if you need some shared resources between activities put them in service. Try to isolate any coupling between activities.
I haven't tried this myself yet, but I think the following would work.
Create your own custom Activity class that extends Activity (as suggested in this SO question).
Ensure all your activites extend your custom Activity class.
#Override the onStop() method (as per the Activity life cycle docs).
Have onStop() call the utility method below (code based on Droid-Fu project) to figure out if your app is now in the background.
My worry is there could be some timing windows when your last activity closes before its new activity (also i your app) launches, but hopefully that is avoidable.
public static boolean isApplicationInBackground(Context context)
{
ActivityManager am =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasks = am.getRunningTasks(1);
if (!tasks.isEmpty())
{
ComponentName topActivity = tasks.get(0).topActivity;
if (!topActivity.getPackageName().equals(context.getPackageName()))
{
return true;
}
}
return false;
}
Let us know how you get on as I might need to implement this feature too!
Update: I've done some quick testing of this code.
It seems to work fine when pressing the home key to exit the application, as the running task changes to ComponentInfo{com.motorola.blur.home/com.motorola.blur.home.HomeActivity} on my device. However, the code doesn't detect the app as in the background when using the back button to exit the application (it still thinks my app is running).
This scenario can be detected by overriding onDestroy() in your top level activities, and checking isFinished() to confirm that the app is getting destroyed (rather than the activity being recreated, e.g. for an orientation change).

Categories

Resources