2012-06-27 14 views
5

Tengo FragmentActivity con FragmentMediaOverview que contiene una lista de MediaItemView s (cada uno con una vista de imagen y texto) y un clic en uno de los elementos que abren un Fragmento de detalle. Ahora cuando vuelvo (mediante el botón Atrás) y adelante (hago clic en el elemento de lista) varias veces desde el fragmento de la lista al detalle, finalmente me encuentro con OOM-Errors. Yo uso SoftReference s para los mapas de bits en los elementos de lista, así como en el fragmento de detalle. De acuerdo con MAT hay un creciente número de MediaItemView s, así como FragmentMediaOverview instancias, pero no puedo entender por qué.Listview en Fragment está causando Memory Leak

Leí esto Android: AlertDialog causes a memory leak, pero no lo pude resolver null ing out listeners.

Aquí está mi código:

FragmentMediaOverview.java

(Esto no es una ListFragment porque para una tableta de diseño de los MediaAdapter necesita conectarse a un gridview)

public class FragmentMediaOverview extends Fragment { 
    private static String TAG = FragmentMediaOverview.class.getSimpleName(); 

    private MediaAdapter adapter; 
    private OnMediaSelectedListener selListener; 
    private ArrayList<BOObject> mediaItems; 

    private ViewGroup layoutContainer;  
    private AdapterView itemContainer; // list or gridview 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
     Log.d(TAG, "onCreateView"); 
     layoutContainer = (ViewGroup) inflater.inflate(R.layout.fragment_media_overview, null); 

     return layoutContainer; 
    } 

    @Override 
    public void onAttach(Activity activity) { 
     super.onAttach(activity); 
     selListener = (OnMediaSelectedListener) activity; 
    } 

    @Override 
    public void onDestroy() { 
     super.onDestroy(); 
     itemContainer.setOnItemClickListener(null); 
     selListener = null; 
     adapter = null; 
    } 

    @Override 
    public void onActivityCreated(Bundle savedInstanceState) { 
     super.onActivityCreated(savedInstanceState); 
     initUi(layoutContainer); 
     displayMedia(); 
    } 

    private void initUi(ViewGroup layoutContainer) { 
     itemContainer = (AdapterView) layoutContainer.findViewById(android.R.id.list); 
     itemContainer.setOnItemClickListener(new OnItemClickListener() { 

      @Override 
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 
       BOMedia mediaItem = ((BOMedia) mediaItems.get(position)); 
//the FragmentActivity is coordinating the FragmentTransactions 
       selListener.onMediaSelected(mediaItem); 
      } 
     }); 
    } 

    private void displayMedia() { 
     Log.d(TAG, "Displaying List"); 
     if (mediaItems == null) { 
      loadMedia(); 
      return; 
     } 

     Log.d(TAG, "List: " + mediaItems.size() + ", adapter: " + itemContainer.getAdapter()); 

     if (adapter == null) { 
      Log.d(TAG, "Create Adapter with " + mediaItems.size()); 

      adapter = new MediaAdapter(getActivity(), mediaItems); 
     } 

     if (itemContainer.getAdapter() == null) { 
      itemContainer.setAdapter(adapter); 
     } else { 
      adapter.setItems(mediaItems); 
      adapter.notifyDataSetChanged(); 
     } 

    } 

    private void loadMedia() { 
     FragmentHelper.showProgressSpinner(layoutContainer, android.R.id.list); 
     DbHelper.getInstance().getMedia(mediaType, new DbQueryFinishListener() { 

      @Override 
      public void onDbCallFinish(ArrayList<BOObject> objects) { 
       if (!getActivity().isFinishing()) { 
        mediaItems = objects; 
        Collections.sort(mediaItems, new Comparator<BOObject>() { 
         final Collator c = Collator.getInstance(Locale.GERMAN); 
         @Override 
         public int compare(BOObject s1, BOObject s2) { 
          if (s2 != null && ((BOMedia) s2).getTitle() != null && s1 != null 
            && ((BOMedia) s1).getTitle() != null) { 
           return c.compare(((BOMedia) s1).getTitle(),((BOMedia) s2).getTitle()); 
          } else { 
           return 0; 
          } 
         } 
        }); 
        displayMedia(); 
        FragmentHelper.hideProgressSpinner(layoutContainer, android.R.id.list); 
       } 
      } 

      @Override 
      public void onDbCallException(Exception exception) { 
       if (!getActivity().isFinishing()) { 
        FragmentHelper.hideProgressSpinner(layoutContainer, android.R.id.list); 
       } 
      } 
     }); 

    } 
} 

MediaAdapter. java

public class MediaAdapter extends BaseAdapter { 
    private static final String TAG = MediaAdapter.class.getSimpleName(); 
    private Context context; 
    private ArrayList<BOObject> mediaItems; 

    public MediaAdapter(Context c, ArrayList<BOObject> mediaItems) { 
     super(); 
     context = c; 
     this.mediaItems = mediaItems; 
    } 

    @Override 
    public int getCount() { 
     return mediaItems.size(); 
    } 

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

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

    @Override 
    public View getView(int position, View convertView, ViewGroup parent) { 
     if (convertView == null) { 
      convertView = new MediaItemView(context); 
     } 
     ((MediaItemView)convertView).initialize((BOMedia) mediaItems.get(position));   
     return convertView; 
    } 

    public void setItems(ArrayList<BOObject> mediaItems) { 
     this.mediaItems = mediaItems; 
    } 
} 

MediaItemView.java

public class MediaItemView extends LinearLayout { 
    private static final String TAG = MediaItemView.class.getSimpleName(); 
    private BOMedia item; 
    private SoftReference<Bitmap> bm; 
    private ImageView iv; 
    private Context ctx; 

    public MediaItemView(Context context) { 
     super(context); 
     LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     layoutInflater.inflate(R.layout.view_media_item, this); 
     this.ctx = context; 
    } 

    /** Init the view with a new BOMedia object 
    * @param mediaItem 
    */ 
    public void initialize(BOMedia mediaItem) { 
     this.item = mediaItem; 
     initUI(); 
    } 

    private void initUI() { 
     TextView title = (TextView) findViewById(R.id.itemText); 
     iv = (ImageView) findViewById(R.id.itemImage); 

     title.setText(Html.fromHtml(item.getTitle())); 
     iv.setImageBitmap(null); 
     bm = null; 
     System.gc(); 
     iv.invalidate(); 
     if (item.getFilepathThumb() != null && !item.getFilepathThumb().equals("")) { 

      ExpansionPackManager.getInstance().getBitmapResource(item.getFilepathThumb(), false, 
        new BitmapReadListener() { 

         @Override 
         public void onFileRead(BitmapResponseMessage message) { 
          Log.d(TAG, "Bitmap read: " + message.getFilepath()); 
          Bitmap image = message.getBitmap(); 
          if (image != null && message.getFilepath().equals(item.getFilepathThumb())) { 
           bm = new SoftReference<Bitmap>(image); 
           iv.setImageBitmap(bm.get()); 
           Log.d(TAG, "image set"); 
          } else { 
           Log.d(TAG, "image too late: " + image); 
          } 
         } 

         @Override 
         public void onFileException(Throwable exception) { 
          Log.d(TAG, "image exception"); 
         } 
        }); 

     } 
    } 

} 
+0

Ideas: 'ExpansionPackManager' podría contener referencias a bitaps o bien' BitmapResponseMessage's si se mantienen en un grupo de reciclaje como 'Mensaje' de Android. Para este último, algún otro código debería llamar a 'message.recycle()'. – Gene

+0

Echa un vistazo .. http://stackoverflow.com/questions/9933783/double-checking-if-fragment-view-holder-pattern-is-implemented-properly Y te prefiero para ver la [presentación de Google IO ] (http://www.google.com/events/io/2010/sessions/world-of-listview-android.html) sobre 'ListViews'. Contiene técnicas de ahorro de memoria y te ayudó a resolver y comprender la lógica de 'ListViews'. Y también vea esto [Archivo PDF.] (Http://dl.google.com/googleio/2010/android-world-of-listview-android.pdf) –

Respuesta

1

Esto es debido a que el View para el niño en el rach ListView se recrea a medida que recorre. Esto es muy pesado en recursos. Para evitar esto, use una clase de soporte en los adaptadores getView() para mantener y reutilizar las vistas. Esto se llama Efficient Adapter. Por ejemplo, vea Efficient List Adapter en API demos. http://developer.android.com/tools/samples/index.html

+0

Gracias por la sugerencia. Esto podría hacerlo un poco más eficiente, pero no explicaría una fuga de memoria ¿o sí? Como estoy reutilizando el convertView, no debería haber ningún muerto flotando ... Tengo la sensación de que de alguna manera los elementos de la lista están siendo reinstalados en un currículum vitae/retroceden, pero los viejos se mantienen ... en alguna parte ... – Till

0

También puede utilizar:

android:hardwareAccelerated = true 

A partir de Android 3.0 (API level 11), la tubería de renderizado Android 2D está diseñado para una mejor aceleración de hardware de soporte. La aceleración de hardware lleva a cabo todas las operaciones de dibujo que se realizan en el lienzo de una vista con el GPU.

Para obtener más información http://developer.android.com/guide/topics/graphics/hardware-accel.html

2

En MediaItemView el tamaño de su mapa de bits debe ser demasiado grande. Si el mapa de bits es 600x600 y desea mostrar una imagen con un tamaño de 50x50, puede usar Bitmap.createScaledBitmap. También debe usar la memoria caché de mapa de bits mientras carga su mapa de bits.