Tengo un ListView en el que cada fila tiene algo de texto y dos ImageView: uno es el mismo para cada fila, el otro depende del elemento actual.

Este es mi adaptador:

mArrayAdapter(Context context, int layoutResourceId, ArrayList<Exhibition>  data) {
    super(context, layoutResourceId, data);
    this.context = context;
    this.layoutResourceId = layoutResourceId;
    this.list = data;
    this.originalList = data;
    viewHolder = new ViewHolder();
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            return bitmap.getByteCount() / 1024;
        }
    };

}

@Override
@NonNull
public View getView(final int position, View convertView, @NonNull final ViewGroup parent) {
    View row;
    final Exhibition ex;
    if(convertView==null){
        row = LayoutInflater.from(getContext()).inflate(R.layout.row,parent,false);

        viewHolder.expand = (ImageView)row.findViewById(R.id.expand);
        row.setTag(viewHolder);
    }
    else {
        row = convertView;
        viewHolder = (ViewHolder)row.getTag();
    }
    ex = list.get(position);


    descr = (TextView)row.findViewById(R.id.descr);
    ttl = (TextView)row.findViewById(R.id.title);
    city = (TextView)row.findViewById(R.id.city);
    dates = (TextView)row.findViewById(R.id.dates);
    museum = (TextView)row.findViewById(R.id.location);
    header = (ImageView)row.findViewById(R.id.hd);

    ttl.setText(ex.name);
    descr.setText(ex.longdescr);
    museum.setText(ex.museum);
    city.setText(ex.city);

    final Bitmap bitmap = getBitmapFromMemCache(ex.key);
    if (bitmap != null) {
        header.setImageBitmap(bitmap);
    } else {
        header.setImageBitmap(ex.getHeader());
        addBitmapToMemoryCache(ex.key,ex.header);
    }


    SimpleDateFormat myFormat = new SimpleDateFormat("dd/MM/yyyy", Locale.ITALY);
    Date start = new Date(ex.getStart()), end = new Date(ex.getEnd());

    String startEx = myFormat.format(start);
    String endEx = myFormat.format(end);

    String finalDate = getContext().getResources().getString(R.string.ex_date, startEx, endEx);

    dates.setText(finalDate);

    viewHolder.expand.setId(position);

    if(position == selectedId){
        descr.setVisibility(View.VISIBLE);
        ttl.setMaxLines(Integer.MAX_VALUE);
        dates.setMaxLines(Integer.MAX_VALUE);
        museum.setMaxLines(Integer.MAX_VALUE);
        city.setMaxLines(Integer.MAX_VALUE);
    }else{
        descr.setVisibility(View.GONE);
        ttl.setMaxLines(1);
        dates.setMaxLines(1);
        museum.setMaxLines(1);
        city.setMaxLines(1);
    }

    viewHolder.expand.setOnClickListener(this.onCustomClickListener);

    return row;
}

public void setDescr(int p){
    selectedId = p;
}

public void setOnCustomClickListener(final View.OnClickListener onClickListener) {
    this.onCustomClickListener = onClickListener;
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}


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

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

@Override
public Exhibition getItem (int pos){
    return list.get(pos);
}

void resetData() {

    list = originalList;
}

private class ViewHolder {

    ImageView expand,header;

}

@Override
@NonNull
public Filter getFilter() {
    if (valueFilter == null) {
        Log.d("SEARCH1","New filter");
        valueFilter = new ValueFilter();
    }
    return valueFilter;
}

private class ValueFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {

        FilterResults results = new FilterResults();
        if(constraint == null || constraint.length() == 0){
            results.values = originalList;
            results.count = originalList.size();
        }
        else {

            List<Exhibition> nExhList = new ArrayList<>();

            for(Exhibition e : list){
                Log.d("NAMEE",e.name + " " + constraint.toString());
                if (e.getName().toUpperCase().contains(constraint.toString().toUpperCase()) || e.getCity().toUpperCase().contains(constraint.toString().toUpperCase())
                        ||e.getMuseum().toUpperCase().contains(constraint.toString().toUpperCase()) || e.getLongDescription().toUpperCase().contains(constraint.toString().toUpperCase())
                        || e.getDescription().toUpperCase().contains(constraint.toString().toUpperCase()) || e.getCategory().toUpperCase().contains(constraint.toString().toUpperCase())){
                    nExhList.add(e);
                }
            }
            results.values= nExhList;
            results.count=nExhList.size();
        }
        return results;
    }

    @Override
    protected void publishResults(CharSequence constraint,
                                  FilterResults results) {
        if(results.count==0){
            notifyDataSetInvalidated();
        }
        else{
            list = (ArrayList<Exhibition>)results.values;
            notifyDataSetChanged();
        }
    }
}

El primer ImageView es un Bitmap almacenado en una variable Exhibition. El segundo cambia la visibilidad de un texto para obtener un efecto expansible (porque por ahora no puedo convertir el ListView en un ExpandableListView). Probé diferentes cosas como el caché, un AsyncTask, quitando el oyente de clics personalizado, puse todo en ViewHolder pero el desplazamiento está lleno de microlags. ¿Hay algún problema en el adaptador que no lo entiendo?

0
tuzzo 16 oct. 2018 a las 23:32

2 respuestas

La mejor respuesta

Hay un par de cosas que puede hacer para mejorar el rendimiento.

Averigua exactamente qué es lento

Obtenga información sobre la creación de perfiles, que puede indicarle qué funciones son las que más se llaman y / o las que más tardan en completarse. De esta manera, puede decidir dónde invertir tiempo para corregir o cambiar el código.

Consulte https://developer.android.com/studio/profile/android-profiler y https://developer.android.com/studio/profile/

Patrón ViewHolder

Estás haciendo un mal uso del patrón ViewHolder. En su código, tiene una única instancia de ViewHolder en el campo viewHolder del adaptador. Luego usa este campo dentro de la función getView() como una variable local regular.

Luego, llama a row.findViewById() varias veces, incluso si convertView no era null. Las llamadas a findViewById() son lentas y la ventaja del titular de la vista es que solo tienes que llamarlo una vez por vista después de la expansión (en la rama convertView == null de if).

En su lugar, debería tener 1 titular de vista por vista de fila. Tenga en cuenta que no está creando un nuevo ViewHolder para asignar con setTag(), pero está reutilizando el mismo. Entonces, en lugar de las variables como descr, ttl, city, deberían ser campos de ViewHolder y, por lo tanto, de referencia rápida.

Creando objetos innecesarios

La asignación de memoria también es lenta.

También está creando objetos cada vez que se llama a getView() que, en su lugar, puede crear una vez en total y simplemente reutilizar.

Un ejemplo de ello es el SimpleDateFormat que podría crearse una vez en el constructor del adaptador y simplemente usarse para producir el texto.

Vea también cómo puede evitar crear tantos objetos String. Formatear con un búfer de cadena o algo similar. No muestra el código fuente de la clase Exhibition, por lo que no está claro por qué es necesario crear un objeto Date con el resultado de llamar a getStart() y {{X4} }.

Si los campos 'inicio' y 'final' de los objetos Exhibition nunca se usan como long s, considere convertirlos en Date inmutables durante el análisis JSON en lugar de cada vez que se usado.

Posibles llamadas lentas en el hilo de la interfaz de usuario

El código fuente de la clase Exhibition no se muestra, por lo que no podemos decir qué hace la función Exhitition.getHeader(). Si hay descarga de mapa de bits y / o decodificación, moverlo a un hilo de fondo (y actualizarlo después de que el mapa de bits esté listo) mejorará el rendimiento de desplazamiento de ListView.

Llamadas innecesarias

Hay llamadas que se realizan incluso si no son necesarias. Por ejemplo, la asignación del oyente On Click al final de getView(). Puede salirse con la suya configurándolo solo una vez cuando realiza la inflación (cuando convertView es null) ya que todas las filas usan el mismo oyente.

Evita llenar la memoria

Mencionaste que cada objeto Exhibition tiene un campo Bitmap que se establece cuando se analiza el JSON. Esto significa que todos los mapas de bits están en la memoria todo el tiempo. Esto significa que en este caso la caché LRU no es necesaria, ya que siempre hay una fuerte referencia a los mapas de bits.

También significa que a medida que aumenta el número de elementos de la lista, crece la memoria necesaria. A medida que se usa más memoria, la recolección de basura (GC) debe ocurrir con más frecuencia, y la GC es lenta y puede causar tartamudeo o congelación. La creación de perfiles puede indicarle si los bloqueos que está experimentando se deben a GC.

La caché de mapa de bits sería útil si solo hay unos pocos mapas de bits en la memoria a la vez, los necesarios para los elementos que están visibles actualmente en la lista y algunos más. Si el mapa de bits necesario no se encuentra en la caché, debe cargarse desde el disco o descargarse de la red.

PD

Tenga en cuenta que tiene una función setOnCustomClickListener() pública que solo asigna la referencia al campo. Si llama a eso con un nuevo oyente, su código actual usará el antiguo oyente en todas las filas que no se actualizaron y actualizaron con la nueva referencia.

1
frozenkoi 18 oct. 2018 a las 04:40

Para que su lista sea fluida, puede probar las siguientes opciones,

  1. En lugar de utilizar su propia forma de almacenamiento en caché de mapas de bits, puede intentar utilizar la biblioteca popular como Glide, < a href = "https://github.com/square/picasso" rel = "nofollow noreferrer"> Picasso o algún otro código abierto
  2. Trate de evitar realizar la operación que consume mucho tiempo en getView, por ejemplo, la conversión de fecha puede pasar al nivel de objeto cuando construya el objeto modelo, será una vez por objeto.
  3. Puede probar Recyclerview en lugar de ListView
2
Muthukrishnan Rajendran 16 oct. 2018 a las 21:09