Android Activity and Low Memory

原文: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. 

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.

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.

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
On the other hand, each process in Android has a separate memory limit, which is usually referred to as the Dalvik heap limit. It’s described in the Android Developer Documentation as well:
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
I will be using a 2100 x 4000 image for this example. And if you’ve read  my previous post on Bitmap Memory Analysis , you’ll know that this will take about 32 MB of memory to load!

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
This is because we’ve hit the 48 MB Dalvik heap limit, since  Activity A  already loaded a 32 MB bitmap,  Activity B  didn’t have enough memory to load the second 32 MB bitmap. If you are wondering why Activity A didn’t release the 32 MB bitmap, you can find out more in  my previous Bitmap Memory Analysis post .

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 =)


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;">


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值