原諒地址:http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html ,英文原文在翻譯以後html
Android 應用至少,在T-Mobile G1這個型號,就有16MB的堆內存。這個容量對於手機來講是很大了,可是對於有些開發者來講是少了些。爲了全其餘應用能夠運行而不被系統殺掉,即便你沒有打算使用完全部分配的容量,你也應該儘可能少地使用這些容量。java
Android能保存越多的應用,用戶在切換應用的時候就會越快。我在工做中,遇到了內存泄露,它們出現的緣由不少時候是由於同一個錯誤:保存Context持有一個生命週期很長的引用android
安卓開發中,Context在不少操做中都須要,它大部分是用來加載和操做應用資源(resources). 這也是爲何一些組件會在它們的構造函數裏接收一個Context參數。通常在一個安卓應用中,一般有兩個種Context:Activity 和 Application. 一般你們都是把第一個Context(Activity)傳給須要這個參數的方法和類:代碼以下git
@Override protected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); label.setText("Leaks are bad"); setContentView(label); }
這意味着這些視圖(Views)對這整個Activity持有一個引用,所以也對這個Activity所持有的對象持有引用。這些對象一般是整個View的hierachy和它們所使用的資源(resources)。所以,一旦你的Context有內存泄露(leak:意思是你對Context引用着,致使它不能給GC回收),意思着你會泄露不少的內存。若是你不當心,Activity的內存泄露是很容易出現的。api
當屏幕的方向(orientation)改變,系統默認地,會銷燬當前的activity, 而後保存狀態(state)的時候,建立一個新的activity。在作這個的時候,安卓會從資源(resources)中從新加載UI。在這樣作的時候,Android會從新從資源(resources)里加載UI。想像一下,你在寫一個應用,有一張很大的圖片(bitmap),你不想在任何旋轉操做的時候加載。 那麼想要在任何旋轉時,保存它,不從新加載的最簡單方法就是把它聲明成'靜態成員變量'(static field):代碼以下app
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); //這個代碼的狀況就是,TextView持有一個Activity的Context的引用,而後又由於它持有一個靜態變量Drawable的引用,致使這個TextView與類的生命週期相同,從而致使Activity的Context也被長期持有,致使這個activity被人引用着而不能被GC回收 setContentView(label); }
這份代碼運行會很快,可是也是錯誤的。 它會泄露第一個在屏幕旋轉時建立的activity。當一個Drawable對象被分配到一個view上的時候, 這個view會被設成這個drawable的回調(callback)。在上面的代碼片中,這意味着drawable對象持有TextView的引用,而TextView又由於context的緣由持有Activity的引用,也所以持有不少其它的引用,具體看你代碼的狀況。ide
這份代碼是Context致使內存泄露的其中一個最簡單的例子。函數
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.ui
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 andApplication. It's usually the first one that the developer passes to classes and methods that need a Context
:this
@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 aDrawable 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, 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 callingContext.getApplicationContext() or Activity.getApplication().
In summary, to avoid context-related memory leaks, remember the following: