Android not killing activities from stack when memory is low

Viewed 4336

We've been developing an application that has a drop down dashboard that allows the users to navigate throughout the app. The navigation is not very standard since this menu can be accessed from almost every activity. After playing for a while opening activities using the menu, the stack starts to grow and grow.

All these activities contain listviews with several imageviews inside, and they take around 3mb each. If the user plays enough and creates more than 25 activities on the stack this is what happens:

  1. Out of memory error is thrown (Heap is increased till there's no more heap).
  2. A dialog is shown due to the exception (Unfortunately, %activity% has stopped.)
  3. The activity where the outofmemerror was thrown is finished.
  4. All the activities in the stack are finished, but the history is kept, so its possible to backup and each activity is recreated automaticall by the OS.

I was expecting the system to kill the oldest activities in the stack automatically BEFRORE the OutOfMemoryError was thrown...

Just to be sure the OS is not killing old activities, I created a test app that allocates 1mb each time. Guess what: The behavior is the same and outofmemerror is thrown:

The question is: How can we tell the Android OS that it is allowed to deallocate activities and its resources if needed so we don't get the "Unfortunately, your activity has stopped." dialog?

Proof of concept

    package com.gaspar.memorytest;

import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MemoryTestActivity extends Activity { /** Called when the activity is first created. */ private byte[] mData; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main1); ((Button)findViewById(R.id.button)).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i = new Intent(MemoryTestActivity.this, MemoryTestActivity.class); startActivity(i); } }); mData = new byte[1*1024*1024]; } }

6 Answers

CommonsWare's answer is correct, besides you can try this strategy to release the memory of the activity at the bottom stack:

  • Remove alls fragment and/or setContentView(null)
  • onResume: call Activity#recreate(), or just simply create new fragment instance and add it back to your view hierarchy.

One important thing, don't rely on these events onLowMemory, onTrimMemory. Because they're invoked only when the entire system runs on low memory, your app can run out of memory before that. You have to track memory usage of your process and take appropriate action if it ate too much memory:

val maxMemory = Runtime.getRuntime().maxMemory()
val freeMemory = Runtime.getRuntime().freeMemory()
val totalMemory = Runtime.getRuntime().totalMemory()
val remainMemoryPercent = (freeMemory + maxMemory - totalMemory) * 100f / maxMemory

// check remainMemoryPercent and take action

For the question, We can not tell the Android OS to do that. Let think, the system has many applications, if every tell that, it is bad. But the system gives us the notification, we can do that in this method.

//in applicaion class
public void onTrimMemory (int level) {
  //according to different level do different things.
  //for example TRIM_MEMORY_UI_HIDDEN to finish old activities 

}

we can also let the activity implement ComponentCallbacks, do it in its own class onLowMemeroy, then when the activity starts to register it, destroy unregister. we can use ActivityLifecycleCallbacks to auto realize it.

//in application class
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks(){

   void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);{
if(activity instanceOf ){
    registerComponentCallbacks((ComponentCallbacks)activity)
  }
}
 
...
void onActivityDestroyed(@NonNull Activity activity);{
  if(activity instanceOf ){
    unregisterComponentCallbacks((ComponentCallbacks)activity)
  }

}


})

// in activity implement ComponentCallbacks, override onlowMemeroy method
void onLowMemory();{
  //finish or remove some resouce
}

As for why Android OS can not allocate that, that is because OS has two ways for memory allocation.

One way is java garbage allocation. which does the object allocation.

The other way is low memory kill, to ensure the process has enough memory. when memory is not enough for running the application, it will kill other applications in the background.

Your activity in java garbage allocation way can not allocate, because it is held by ActivityThread. so it can not allocation.

To free memory, we can finish the old activities when receiving the low memory notification.

Related