Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android學習系列(36)--App調試內存洩露之Context篇(上)

Android學習系列(36)--App調試內存洩露之Context篇(上)

編輯:關於Android編程

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的分析最後一行。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved