2010-10-22 2 views
1

Я борюсь с утечкой памяти, связанной с ListView. Я создал следующую небольшую программу, которая демонстрирует такое поведение.Утечка памяти при использовании ListView в Android

Что мне нужно сделать, это создать 2 LinearLayouts. У первого есть кнопка и элемент управления GListView. Код для GListView приведен ниже, но он просто подклассы ListView и реализует интерфейс ListAdapter. Когда GListView создан, он устанавливает свой адаптер для себя.

Теперь, когда вы нажимаете кнопку, я переключаюсь на второй LinearLayout. Этот макет имеет только одну кнопку. Когда вы нажимаете эту кнопку, я создаю новый 1-й макет с новым GListView и устанавливаю его как активный вид.

Запустите программу и переключайтесь между двумя видами 20 раз. Затем поднимите DDMS и заставьте сбор мусора. Затем выгрузите память и используйте анализатор памяти, и вы найдете 21 объект GListView. То есть, 20 GListViews, которые больше не связаны ни с чем, НЕ были освобождены.

Если я дамп памяти и посмотреть на один из GListViews, которые должны были переработаны, и список ссылок Incomming с помощью анализатора памяти, я получаю следующее:

Class Name                | Shallow Heap | Retained Heap 
------------------------------------------------------------------------------------------------------- 
com.gabysoft.memoryleak.GListView @ 0x43e72270 Unknown     |   672 |   3,528 
|- host android.view.View$ScrollabilityCache @ 0x43e72560    |   80 |   584 
|- this$0 android.widget.AbsListView$RecycleBin @ 0x43e72830   |   40 |   160 
|- mCallback android.graphics.drawable.StateListDrawable @ 0x43e728a8 |   64 |   1,464 
|- this$0 android.widget.AdapterView$AdapterDataSetObserver @ 0x43e730b8|   16 |   16 
------------------------------------------------------------------------------------------------------- 

Теперь, если я закомментируйте функцию setAdapter (this) 'в конструкторе GListView и повторите выше, я обнаружил, что осталось только 1 GListView. То есть, в этом случае все неиспользуемые GListViews были надлежащим образом переработаны.

Кто-то предложил создать частный класс в моем GListView для обработки интерфейса ListAdapter, и я пробовал это, но это не помогло. Я также попытался создать полностью отдельный публичный класс для обработки ListAdapter, но, увы, это тоже не работает.

Несомненно, существует какой-то способ убрать эти объекты, когда они больше не используются нигде. (Разве это не то, что сбор мусора?)

Любая помощь будет оценена. Я действительно вытягиваю свои волосы на этом.

Спасибо.

/* 
* Activity 
*/ 

package com.gabysoft.memoryleak; 

import android.app.Activity; 
import android.os.Bundle; 
import android.view.View; 
import android.widget.Button; 
import android.widget.LinearLayout; 
import android.widget.ListView; 

public class MemoryLeak extends Activity implements android.view.View.OnClickListener 
{ 
    LinearLayout ll2; 
    boolean page2 = false; 

    private LinearLayout CreateLayout() 
    { 
     LinearLayout ll = new LinearLayout(this); 

     Button btn1 = new Button(this); 
     ListView lv = new GListView(this); 

     btn1.setText("Press"); 
     btn1.setLayoutParams(new LinearLayout.LayoutParams(100, 40)); 
     btn1.setOnClickListener(this); 

     ll.addView(btn1); 
     ll.addView(lv); 

     return(ll); 
    } 

    /** Called when the activity is first created. */ 
    @Override 
    public void onCreate(Bundle savedInstanceState) 
    { 
     super.onCreate(savedInstanceState); 

     CreateLayout(); 

     LinearLayout ll = CreateLayout(); 
     ll2 = new LinearLayout(this); 

     Button btn2 = new Button(this); 

     btn2.setText("Back"); 
     btn2.setLayoutParams(new LinearLayout.LayoutParams(100, 40)); 
     btn2.setOnClickListener(this); 

     ll2.addView(btn2); 

     setContentView(ll); 
    } 

    @Override 
    public void onClick(View v) 
    { 
     if (page2) 
     { 
      LinearLayout ll = CreateLayout(); 

      setContentView(ll); 

      page2 = false; 
     } 
     else 
     { 
      setContentView(ll2); 
      page2 = true; 
     } 
    } 

} 

/* 
* GListView 
*/ 
package com.gabysoft.memoryleak; 

import android.content.Context; 
import android.database.DataSetObserver; 
import android.view.View; 
import android.view.ViewGroup; 
import android.widget.AdapterView; 
import android.widget.ListAdapter; 
import android.widget.ListView; 
import android.widget.TextView; 

public class GListView extends ListView implements ListAdapter 
{ 
    Context m_context; 
    DataSetObserver m_observer = null; 

    public GListView(Context context) 
    { 
     super(context); 

     m_context = context; 

     setAdapter(this); 

     setChoiceMode(CHOICE_MODE_SINGLE); 
    } 

    /* 
    * ListAdapter 
    */ 


    @Override 
    public boolean areAllItemsEnabled() 
    { 
     return true; 
    } 

    @Override 
    public boolean isEnabled(int position) 
    { 
     return true; 
    } 

    @Override 
    public int getCount() 
    { 
     return(0); 
    }  

    @Override 
    public Object getItem(int position) 
    { 
     return null; 
    } 

    @Override 
    public long getItemId(int position) 
    { 
     return(position); 
    } 

    @Override 
    public int getItemViewType(int position) 
    { 
     return 0; 
    } 

    @Override 
    public View getView(int position, View convertView, ViewGroup parent) 
    { 
     TextView tv = new TextView(m_context); 

     tv.setText("Item"); 

     return(tv); 
    } 

    @Override 
    public int getViewTypeCount() 
    { 
     return 1; 
    } 

    @Override 
    public boolean hasStableIds() 
    { 
     return false; 
    } 

    @Override 
    public boolean isEmpty() 
    { 
     return false; 
    } 

    @Override 
    public void registerDataSetObserver(DataSetObserver observer) 
    { 
     m_observer = observer; 
    } 

    @Override 
    public void unregisterDataSetObserver(DataSetObserver observer) 
    { 
     m_observer = null; 
    } 
} 
+0

@John Gaby: Отправьте ссылку на ZIP-файл, содержащий полный проект с этим кодом, с которым я могу скачать и играть, и я посмотрю на него. – CommonsWare

+0

Спасибо, что посмотрели. Я не мог видеть, как я могу опубликовать файл вместе с этим комментарием, поэтому я загрузил его на свой сайт. Его можно скачать по адресу http://gabysoft.com/download/memoryleak.zip. Я запускаю его на эмуляторе (хотя я считаю, что это происходит и на реальном устройстве) с помощью Android 2.2 –

+0

@ Джона Габи: Да, вы не можете прикреплять файлы к SO-вопросам, поэтому то, что вы там делали, было прекрасным. Дайте мне несколько часов, так как у меня есть обновление книги, я хотел бы сначала выйти из комнаты. – CommonsWare

ответ

1

Ваши GListView объекты сборщиком мусора. В крайнем случае, они вызываются с finalize(), когда ручной GC выполняется из DDMS. К сожалению, файл DDSS HPROF кажется несовместимым с моей версией jhat, и я не являюсь пользователем Eclipse, поэтому у меня нет открытого доступа к MAT.

+0

Странно, это не то поведение, которое я вижу. Я добавил метод переопределения finalize к моему классу GListView, и я только когда-либо видел его один раз, хотя я создал много объектов GListView, которые необходимо перерабатывать. Я загрузил версию с журналом на http://gabysoft.com/download/MemoryLeak.zip. Возможно ли, что я вижу это, потому что я использую Eclipse, а вы нет? –

+0

@John Gaby: я загрузил ваш последний ZIP, скомпилировал и установил его в эмулятор 2.2, нажал кнопку 40 раз, сделал ручную GC из DDMS и получил 21 финализацию отладочных сообщений. Возможно, вы захотите попробовать запустить тест за пределами Eclipse один раз. Для этого перейдите в каталог проекта и запустите 'android update project -p .' (предполагается, что ваш каталог SDK' tools/'находится в вашем' PATH'). Установите Apache Ant, если у вас его нет, запустите эмулятор с помощью команды 'android' и запустите' ant clean install'. Запустите DDMS через 'ddms'. – CommonsWare

+0

Еще раз спасибо за помощь. Мне придется отследить Apache Ant и попробовать. Я могу сказать вам, что GListView является источником моей проблемы даже в моем реальном приложении. У меня есть страница, которая содержит GListView, имеет большую графику в качестве фона. Если я перезагружу эту страницу достаточно времени, программа выйдет из строя с ошибкой вне памяти. Я исправил его, чтобы удалить GListView из него родительской ViewGroup, и, хотя GListView не освобожден, родительский. После этого изменения я могу загрузить страницу много раз без сбоя. Я не понимаю, почему я вижу это с Eclipse. –