Context作為最基本的上下文,承載著Activity,Service等最基本組件。當有對象引用到Activity,並不能被回收釋放,必將造成大范圍的對象無法被回收釋放,進而造成內存洩漏。
下面針對一些常用場景逐一分析。
1. CallBack對象的引用
先看一段代碼:
@Override
protectedvoid onCreate(Bundle state){
super.onCreate(state);
TextView label =new TextView(this);
label.setText("Leaks are bad");
setContentView(label);
}
大家看看有什麼問題嗎?
沒問題是吧,繼續看:
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);
}
有問題嗎?
哈哈,先Hold住一下,先來說一下android各版本發布的歷史:
/*
2.2 2010-3-20,Froyo
2.3 2010-12-6, Gingerbread
3.0 2011-2-22, Honeycomb
4.0 2011-10-11 Ice Cream Sandwich
*/
了解源碼的歷史,是很有益於我們分析android代碼的。
好,開始分析代碼。
首先,查看setBackgroundDrawable(Drawable background)方法源碼裡面有一行代碼引起我們的注意:
public void setBackgroundDrawable(Drawable background) {
// ... ...
background.setCallback(this);
// ... ...
}
所以sBackground對view保持了一個引用,view對activity保持了一個引用。
當退出當前Activity時,當前Activity本該釋放,但是因為sBackground是靜態變量,它的生命周期並沒有結束,而sBackground間接保持對Activity的引用,導致當前Activity對象不能被釋放,進而導致內存洩露。
所以結論是:有內存洩露!
這是Android官方文檔的例子:http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
到此結束了嗎?
我發現網上太多直接抄或者間接抄這篇文章,一搜一大片,並且吸引了大量的Android初學者不斷的轉載學習。
但是經過本人深入分析Drawable源碼,事情發生了一些變化。
Android官方文檔的這篇文章是寫於2009年1月的,當時的Android Source至少是Froyo之前的。
Froyo的Drawable的setCallback()方法的實現是這樣的:
public final void setCallback(Callback cb) {
mCallback = cb;
}
在GingerBread的代碼還是如此的。
但是當進入HoneyComb,也就是3.0之後的代碼我們發現Drawable的setCallback()方法的實現變成了:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
也就是說3.0之後,Drawable使用了軟引用,把這個洩露的例子問題修復了。(至於軟引用怎麼解決了以後有機會再分析吧)
所以最終結論是,在android3.0之前是有內存洩露,在3.0之後無內存洩露!
如果認真比較代碼的話,Android3.0前後的代碼改進了大量類似代碼,前面的Cursor篇裡的例子也是在3.0之後修復了。
從這個例子中,我們很好的發現了內存是怎麼通過回調洩露的,同時通過官方代碼的update也了解到了怎麼修復類似的內存洩露。
2. System Service對象
通過各種系統服務,我們能夠做一些系統設計好的底層功能:
//ContextImpl.java
@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
static {
registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() {
public Object getService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
public Object getService(ContextImpl ctx) {
return new CaptioningManager(ctx);
}});
registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
IAccountManager service = IAccountManager.Stub.asInterface(b);
return new AccountManager(ctx, service);
}});
// ... ...
}
這些其實就是定義在Context裡的,按理說這些都是系統的服務,應該都沒問題,但是代碼到了各家廠商一改,事情發生了一些變化。
一些廠商定義的服務,或者廠商自己修改了一些新的代碼導致系統服務引用了Context對象不能及時釋放,我曾經碰到過Wifi,Storage服務都有內存洩露。
我們改不了這些系統級應用,我們只能修改自己的應用。
解決方案就是:使用ApplicationContext代替Context。
舉個例子吧:
// For example
mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
改成:
mStorageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);
3. Handler對象
先看一段代碼:
public class MainActivity extends QActivity {
// lint tip: This Handler class should be static or leaks might occur
class MyHandler extends Handler {
... ...
}
}
Handler洩露的關鍵點有兩個:
1). 內部類
2). 生命周期和Activity不一定一致
第一點,Handler使用的比較多,經常需要在Activity中創建內部類,所以這種場景還是很多的。
內部類持有外部類Activity的引用,當Handler對象有Message在排隊,則無法釋放,進而導致Activity對象不能釋放。
如果是聲明為static,則該內部類不持有外部Acitivity的引用,則不會阻塞Activity對象的釋放。
如果聲明為static後,可在其內部聲明一個弱引用(WeakReference)引用外部類。
public class MainActivity extends Activity {
private CustomHandler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new CustomHandler(this);
}
static class CustomHandlerextends Handler {
// 內部聲明一個弱引用,引用外部類
private WeakReference<MainActivity > activityWeakReference;
public MyHandler(MyActivity activity) {
activityWeakReference= new WeakReference<MainActivity >(activity);
}
// ... ...
}
}
第二點,其實不單指內部類,而是所有Handler對象,如何解決上面說的Handler對象有Message在排隊,而不阻塞Activity對象釋放?
解決方案也很簡單,在Activity onStop或者onDestroy的時候,取消掉該Handler對象的Message和Runnable。
通過查看Handler的API,它有幾個方法:removeCallbacks(Runnable r)和removeMessages(int what)等。
// 一切都是為了不要讓mHandler拖泥帶水
@Override
public void onDestroy() {
mHandler.removeMessages(MESSAGE_1);
mHandler.removeMessages(MESSAGE_2);
mHandler.removeMessages(MESSAGE_3);
mHandler.removeMessages(MESSAGE_4);
// ... ...
mHandler.removeCallbacks(mRunnable);
// ... ...
}
上面的代碼太長?好吧,出大招:
@Override
public void onDestroy() {
// If null, all callbacks and messages will be removed.
mHandler.removeCallbacksAndMessages(null);
}
有人會問,當Activity退出的時候,我還有好多事情要做,怎麼辦?我想一定有辦法的,比如用Service等等.
4. Thread對象
同Handler對象可能造成內存洩露的原理一樣,Thread的生命周期不一定是和Activity生命周期一致。
而且因為Thread主要面向多任務,往往會造成大量的Thread實例。
據此,Thread對象有2個需要注意的洩漏點:
1). 創建過多的Thread對象
2). Thread對象在Activity退出後依然在後台執行
解決方案是:
1). 使用ThreadPoolExecutor,在同時做很多異步事件的時候是很常用的,這個不細說。
2). 當Activity退出的時候,退出Thread。
第一點,例子太多,建議大家參考一下afinal中AsyncTask的實現學習。
第二點,如何正常退出Thread,我在之前的博文中也提到過。示例代碼如下:
// ref http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
private volatile Thread blinker;
public void stop() {
blinker = null;
}
public void run() {
Thread thisThread = Thread.currentThread();
while (blinker == thisThread) {
try {
thisThread.sleep(interval);
} catch (InterruptedException e){
}
repaint();
}
}
有人會問,當Activity退出的時候,我還有好多事情要做,怎麼辦?請看上面Handler的分析最後一行。