Avoid memory leaks on Android

我们平常开发的时候,会经常遇到Out of Memorry这样的问题,我这两天在关注这方面的解决方案,下面就把一些宝贵的资料和大家一起分享一下。

1,Google I/O大会上也有这个专题Google I/O: Memory Management for Android Apps,请了Patrick developer进行详细介绍:可以通过下面的url去看其视频:[url]http://www.youtube.com/user/googledevelopers?blend=1&ob=4#p/search/0/_CruQY55HOk[/url]

Patrick的博客:[url]http://dubroy.com/blog/google-io-memory-management-for-android-apps/[/url]

2,a blogpost by Romain Guy:Avoid memory leaks on Android,[url]http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/[/url]

Avoid memory leaks on Android
Thursday, December 18th, 2008 at 1:57 pm, Android.

Android applications are, at least on the T-Mobile G1, limited to 16 MB of heap. It’s both a lot of memory for a phone and yet very little for what some developers want to achieve. Even if you do not plan on using all of this memory, you should use as little as possible to let other applications run without getting them killed. The more applications Android can keep in memory, the faster it will be for the user to switch between his apps. As part of my job, I ran into memory leaks issues in Android applications and they are most of the time due to the same mistake: keeping a long-lived reference to a Context.

On Android, a Context is used for many operations but mostly to load and access resources. This is why all the widgets receive a Context parameter in their constructor. In a regular Android application, you usually have two kinds of Context, Activity and Application. It’s usually the first one that the developer passes to classes and methods that need a Context:

@Override
protected void onCreate(Bundle state) {
super.onCreate(state);

TextView label = new TextView(this);
label.setText("Leaks are bad");

setContentView(label);
}
This means that views have a reference to the entire activity and therefore to anything your activity is holding onto; usually the entire View hierarchy and all its resources. Therefore, if you leak the Context (“leak” meaning you keep a reference to it thus preventing the GC from collecting it), you leak a lot of memory. Leaking an entire activity can be really easy if you’re not careful.

When the screen orientation changes the system will, by default, destroy the current activity and create a new one while preserving its state. In doing so, Android will reload the application’s UI from the resources. Now imagine you wrote an application with a large bitmap that you don’t want to load on every rotation. The easiest way to keep it around and not having to reload it on every rotation is to keep in a static field:

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
super.onCreate(state);

TextView label = new TextView(this);
label.setText("Leaks are bad");

if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);

setContentView(label);
}
This code is very fast and also very wrong; it leaks the first activity created upon the first screen orientation change. When a Drawable is attached to a view, the view is set as a callback on the drawable. In the code snippet above, this means the drawable has a reference to the TextView which itself has a reference to the activity (the Context) which in turns has references to pretty much anything (depending on your code.)

This example is one of the simplest cases of leaking the Context and you can see how we worked around it in the Home screen’s source code (look for the unbindDrawables() method) by setting the stored drawables’ callbacks to null when the activity is destroyed. Interestingly enough, there are cases where you can create a chain of leaked contexts. I can’t remember an exact case right now, but I have fixed a couple of those, and they are bad. They make you run out of memory rather quickly.

There are two easy ways to avoid context-related memory leaks. The most obvious one is to avoid escaping the context outside of its own scope. The example above showed the case of a static reference but inner classes and their implicit reference to the outer class can be equally dangerous. The second solution is to use the Application context. This context will live as long as your application is alive and does not depend on the activities life cycle. If you plan on keeping long-lived objects that need a context, remember the application object. You can obtain it easily by calling Context.getApplicationContext() or Activity.getApplication().

In summary, to avoid context-related memory leaks, remember the following:

Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
Try using the context-application instead of a context-activity
Avoid non-static inner classes in an activity if you don’t control their life cycle, use a static inner class and make a weak reference to the activity inside
And remember that a garbage collector is not an insurance against memory leaks. Last but not least, we try to make such leaks harder to make happen whenever we can.

3,对Romain Guy的那篇文章进行分析和理解消化的文章:[url]http://stackoverflow.com/questions/6567647/avoid-memory-leaks-on-android[/url]和[url]http://blog.csdn.net/zhanghw0917/article/details/5659353[/url]

但后来发现了问题。就是网上很多的关于Drawable会使用setCallback保留对组件(ImageButton )的引用,而组件又会保留对activity的引用,这样activity上的计数器不会0,虚拟机也不能回收已经不用的activity对象,再次引发内存溢出。

//View.java
public void setBackgroundDrawable(Drawable d) {  
boolean requestLayout = false;

mBackgroundResource = 0;

/*
* Regardless of whether we're setting a new background or not, we want
* to clear the previous drawable.
*/
if (mBGDrawable != null) {
mBGDrawable.setCallback(null);
unscheduleDrawable(mBGDrawable);
}

if (d != null) {
Rect padding = sThreadLocal.get();
if (padding == null) {
padding = new Rect();
sThreadLocal.set(padding);
}
if (d.getPadding(padding)) {
setPadding(padding.left, padding.top, padding.right, padding.bottom);
}

// Compare the minimum sizes of the old Drawable and the new. If there isn't an old or
// if it has a different minimum size, we should layout again
if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() ||
mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) {
requestLayout = true;
}

d.setCallback(this);
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setVisible(getVisibility() == VISIBLE, false);
mBGDrawable = d;

if ((mPrivateFlags & SKIP_DRAW) != 0) {
mPrivateFlags &= ~SKIP_DRAW;
mPrivateFlags |= ONLY_DRAWS_BACKGROUND;
requestLayout = true;
}
} else {
/* Remove the background */
mBGDrawable = null;

if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) {
/*
* This view ONLY drew the background before and we're removing
* the background, so now it won't draw anything
* (hence we SKIP_DRAW)
*/
mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND;
mPrivateFlags |= SKIP_DRAW;
}

/*
* When the background is set, we try to apply its padding to this
* View. When the background is removed, we don't touch this View's
* padding. This is noted in the Javadocs. Hence, we don't need to
* requestLayout(), the invalidate() below is sufficient.
*/

// The old background's minimum size could have affected this
// View's layout, so let's requestLayout
requestLayout = true;
}

computeOpaqueFlags();

if (requestLayout) {
requestLayout();
}

mBackgroundSizeChanged = true;
invalidate();
}



这里面的d.setCallback(this)可能会导致保留对组件的引用。当重新调用setBackgroundDrawable时,Drawable调用mBGDrawable.setCallback(null)先将原来的引用设置为null。



回到我的项目,我们在切换横竖屏时并没有重新生成activity,也没有需要销毁的activity,而且最初我们没有设置为static,那么这些成员变量的生命周期和activity是相同的,不会引发上述问题。(理论推测)我们目前的做法是在onConfigurationChanged()中增加了这么句话:

mWaterDrawable .setCallback(null)做个双保险。



那当将Drawable的变量设置为static后,应该在onDestroy中也调用mWaterDrawable .setCallback(null),保证activity可以被正常回收。

4,对Memorry查看的文章:[url]http://www.greatereader.org/posts/developer/memory-analysis-for-android-applications_20783.html[/url]和[url]http://stackoverflow.com/questions/2298208/how-to-discover-memory-usage-of-my-application-in-android/2299813#2299813[/url]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值