id="twitter-widget-0" scrolling="no" frameborder="0" allowtransparency="true" class="twitter-follow-button twitter-follow-button-rendered" title="Twitter Follow Button" src="http://platform.twitter.com/widgets/follow_button.c6def25548e9590b13abaa1b3330b811.en.html#dnt=false&id=twitter-widget-0&lang=en&screen_name=hiBrianLee&show_count=false&show_screen_name=true&size=m&time=1475979877243" data-screen-name="hiBrianLee" style="display: inline-block; max-width: 100%; position: static; visibility: visible; width: 142px; height: 20px;">
原文:http://lab.tigerpenguin.com/2014/03/android-activity-and-low-memory.html
Android trivia of the day! Activity A starts Activity B, but there's not enough memory to start Activity B. What do you think will happen?
A) Activity B crashes with java.lang.OutOfMemoryError
B) Activity A will be destroyed to free up memory for Activity B .
Situations like above could arise at some point in your Android development life. Although, a more realistic case would be that there may be more Activities in between (A, B, C, …, G).
So what happens in this case? As you may have guessed with “trick” questions like this, both are correct, because it depends. Depends on what? It depends on whether Activity A and Activity B are running in the same process.
It is quite easy to overlook this aspect of Android, and just instinctively expect that Activity A will be destroyed in the above case to free up memory for Activity B , regardless of the situation. So where does the misconception come from?
The Misconception
The misconception may arise from the Android developer documentation itself. Here are a few examples.Once your activity is stopped, the system might destroy the instance if it needs to recover system memory.
A stopped activity is also still alive. However, it is no longer visible to the user and it can be killed by the system when memory is needed elsewhere.
If an activity is paused or stopped, the system can drop it from memory either by asking it to finish (calling its finish() method), or simply killing its process.
If an activity is completely obscured by another activity, it is stopped ... and it will often be killed by the system when memory is needed elsewhere.And then there are bunch more other references that talk about how an Activity can be killed in low memory situations after onStop() .
Well, are they all wrong then?
Technically, no, not really. Practically? I can see how it could be misleading. What do I mean by that?
If you read carefully, it just says a stopped Activity “can be killed” or “will often be killed” when memory is needed elsewhere. It never says that the Activity will be killed if another Activity from the same process needs more memory.
It would be a lot more clear if it stated that a stopped Activity won’t be killed if its hosting process is the foreground process, and the Activity that requires more memory is running in the same process... I take that back. I can’t even say that in one breath, so maybe it’s not so clear. Almost feels like Android will tell me, “Well, I never said THAT.”
Anyway, I am playing with words here. So what does all this mumbo jumbo mean?
The Clarification
Enter StackOverflow. This is where I find great insights from Android (ex)team members like Dianne Hackborn or Romain Guy. At the same time, this is where I sometimes find stuff that makes me wonder why it took me over a year to find it.Anyway, here’s an excerpt from Dianne Hackborn’s answer on StackOverflow:
The only memory management that impacts activity lifecycle is the global memory across all processes, as Android decides that it is running low on memory and so need to kill background processes to get some back.There’s a lot of great stuff in this answer, and a lot of implications as well. But first lets clarify this a bit more.
If your application is sitting in the foreground starting more and more activities, it is never going into the background, so it will always hit its local process memory limit before the system ever comes close to killing its process.
As you may know already, an Activity going into background is different from a process going into background. By default, all Activities in an Android Application run in the same process, as explained in the Android Developer page .
In this default setting, if Activity A started Activity B , Activity A will be in the background while Activity B will be in the foreground. However, since both Activity A and Activity B are hosted by the same process, the Application process itself is still in the foreground. So if your Application just kept on starting one Activity after another, your Application process will always be in the foreground.
Android Memory
Ok, then what’s this local process memory and global memory that Dianne Hackborn is talking about? The global memory is basically the RAM. If an Android device has 2 GB of RAM, that’s the global memory. You can see this at the bottom of the screen when you go to the device Settings -> Apps -> Running, as in the screenshot below.![]() |
Android Global Memory |
The Dalvik heap for each process is constrained to a single virtual memory range. This defines the logical heap size, which can grow as it needs to (but only up to a limit that the system defines for each app).The Dalvik heap limit is different on every device. In general, the newer the OS and the better the hardware, the bigger it usually is. It could be anywhere between 16 MB to 128 MB, maybe even more.
Now you can quickly see why Dianne Hackborn said a foreground process “will always hit its local process memory limit before the system ever comes close to killing its process.”
No, not really? Well that’s why I have an example in the next section =)
Example Setup
One of the great things about the Android emulator is that many things can be configured. In this case, I am going to configure the RAM size and the Dalvik heap size. I’ll set the Dalvik heap size to be 48 MB, and the RAM size to be 300 MB, as you can see below.![]() |
Emulator Setup |
The example app I wrote will have multiple Activities that basically do the same thing. Each Activity will load an 8 megapixel image, and when you click on the image, it will start the next Activity , which will also load another 8 megapixel image, and so on… So each Activity I open will take about 32 MB of memory.
As usual, the source code for the test app I used is available at https://github.com/hiBrianLee/ActivityMemory
Single Process Example
In this example, the Application will just be a default app, so everything will run in the same process. And as you can see below, when I click on Activity A to start Activity B , it will crash with an OutOfMemoryError .![]() |
OutOfMemoryError |
Multi Process Example
In Android, you can configure different Activities to run in different processes by modifying the manifest file, which is described here . In the Multi Process example, I am going to assign a different process to each Activity .<activity android:name="com.tigerpenguin.lab.activitymemory.ActivityA" android:label="Multi Process" android:process=":ProcessA"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.tigerpenguin.lab.activitymemory.ActivityB" android:label="Activity B" android:process=":ProcessB"/> <activity android:name="com.tigerpenguin.lab.activitymemory.ActivityC" android:label="Activity C" android:process=":ProcessC"/> <activity android:name="com.tigerpenguin.lab.activitymemory.ActivityD" android:label="Activity D" android:process=":ProcessD"/> <activity android:name="com.tigerpenguin.lab.activitymemory.ActivityE" android:label="Activity E" android:process=":ProcessE"/> <activity android:name="com.tigerpenguin.lab.activitymemory.ActivityF" android:label="Activity F" android:process=":ProcessF"/> <activity android:name="com.tigerpenguin.lab.activitymemory.ActivityG" android:label="Activity G" android:process=":ProcessG"/>
And this time, I am going to start up the Android Device Monitor tool to show you what happens.
When I load Activity A , only Process A is loaded.
![]() |
Activity A – Process List |
The global memory also looks like this, after loading Activity A .
![]() |
Activity A – System Memory |
Once I load Activity B , Process B also shows up.
![]() |
Activity B – Process List |
As you can see, it didn’t crash this time. This is because Activity A and Activity B are running in different processes, as shown in the process list. In the image below, you can also see that it took a big chunk of inactive system memory.
![]() |
Activity B – System Memory |
Once I load Activity C , there will be even less available system memory.
![]() |
Activity C – System Memory |
![]() |
Activity C – Process List |
At this point, when I open Activity D , something interesting happens.
![]() |
Activity D – Process List |
There was not enough system memory to load Activity D , so Android made more memory available by killing some of the other processes. You can see that android.process.acore , com.android.music , and com.android.dialer disappeared.
When I open Activity E , the list of other processes get even shorter.
![]() |
Activity E – Process List |
Wait, what just happened? As you may have noticed, Android killed Process A holding Activity A , because it determined that was necessary to open Activity E .
At this point, when I open Activity F , it will also kill the next oldest Activity that has a big chunk of memory, which will be Activity B in this case.
![]() |
Activity F – Process List |
And similarly, when I open Activity G , Activity C will get killed.
![]() |
Activity G – Process List |
Here’s another trivia. If I close Activity G with the back button and destroy it, what do you think will happen?
Lets see if what you expect will happen when I close Activity G .
![]() |
Activity G closed – Process List |
Nothing happens to Process G . Even though I killed Activity G , Process G remains, still taking up the same amount of memory.
Really? Are you sure it’s not a mistake? Well, lets try with Activity F and Activity E then. After closing Activity F and Activity E ,
![]() |
Activity F and Activity E closed – Process List |
Process E, F, and G are all still in memory. Why is this happening? This is because the process is cached . When I closed Activity G, F, and E , there were no other processes that required memory, since I was simply going back to another process that was already in memory. Since it didn’t create additional memory need, Android simply decided to keep them cached.
So at this point, if I close Activity D , what do you think will happen?
![]() |
Activity D closed – Process List |
When Activity D was closed, it had to go back to Activity C , since that's what was in the back stack . However, Activity C was previously killed due to low memory, so it was freshly created again. This created an additional memory constraint, so Android decided to kill the least important process, which at this point, was the oldest and biggest cached Process G .
So you can now probably guess what will happen when I close Activity C .
![]() |
Activity C closed – Process List |
Process F gets cleared, and Activity B is recreated, and Process C is cached.
And when I close Activity B , Activity A comes back in by kicking out Process E as expected, and Process B is cached.
![]() |
Activity B closed – Process List |
Now, if I close Activity A , the last remaining Activity , what do you think will happen?
![]() |
Activity A closed – Process List |
It doesn’t make a difference if it’s the last Activity of the app or not, the process will still just remain cached if there's no immediate need to kill it.
At this point, if I start the browser, what will happen?
![]() |
Browser – Process List |
As expected, Process D , the oldest and biggest cached process, gets killed, to make room for the browser.
Now if I search for images in the browser, can you guess what will happen?
![]() |
Browser Image Search – Process List |
Because the browser needed more memory, Android decided to kill Process C .
Conclusion
I hope the examples I showed above helped clarify when and how an Activity gets killed by the system. In general, you should always clean up any large objects in onStop() , since you can't assume that your Activity will always be killed to free up memory for your other Activity. Also, holding on to large objects makes your process a more likely candidate to be killed by the system.If your Activity is using large Bitmap objects, you can also optimize by sampling the image to minimize the memory footprint, and by releasing the Bitmap memory as well.
Thank you for reading and I would appreciate any feedbacks you may have! You can also follow me on Twitter below =)