Cómo actualizar algunos datos en un ListView sin utilizar notifyDataSetChanged ()?

Estoy tratando de crear un ListView con una lista de tareas de descarga.

Las tareas de descarga se gestionan en un Service (DownloadService). Cada vez que se recibe un fragmento de datos, la tarea envía el progreso a través de una Broadcast , recibida por el Fragment contiene ListView (SavedShowListFragment). Al recibir el mensaje Broadcast , el SavedShowListFragment actualiza el progreso de las tareas de descarga en el adaptador y activa notifyDataSetChanged() .

Cada fila de la lista contiene un ProgressBar , un TextView para el título del archivo que se está descargando y uno para el valor numérico del progreso, y un Button para pausar / reanudar la descarga o reproducir el programa guardado cuando se termina la descarga.

El problema es que el Button pausa / reanudar / reproducir a menudo no responde ( onClick() no se llama), y creo que es porque toda la lista se actualiza muy a menudo con notifyDataSetChanged() (cada vez que un pedazo de datos, es decir, 1024 Bytes, que puede ser muchas veces por segundo, especialmente cuando hay varias tareas de descarga ejecutándose).

Supongo que podría aumentar el tamaño del fragmento de datos en las tareas de descarga, pero realmente creo que mi método no es óptimo en absoluto!

¿Podría llamar muy a menudo notifyDataSetChanged() hacer que la interfaz de usuario ListView no responda?

¿Hay alguna forma de actualizar sólo algunas Views en las filas ListView , es decir, en mi caso el ProgressBar y TextView con el valor numérico del progreso, sin llamar a notifyDataSetChanged() , que actualiza la lista completa?

Para actualizar el progreso de las tareas de descarga en el ListView , ¿existe alguna opción mejor que "getChunk / sendBroadcast / updateData / notifyDataSetChanged"?

A continuación se muestran las partes relevantes de mi código.

Descargar la tarea en el servicio de descarga

 public class DownloadService extends Service { //... private class DownloadTask extends AsyncTask<SavedShow, Void, Map<String, Object>> { //... @Override protected Map<String, Object> doInBackground(SavedShow... params) { //... BufferedInputStream in = new BufferedInputStream(connection.getInputStream()); byte[] data = new byte[1024]; int x = 0; while ((x = in.read(data, 0, 1024)) >= 0) { if(!this.isCancelled()){ outputStream.write(data, 0, x); downloaded += x; MyApplication.dbHelper.updateSavedShowProgress(savedShow.getId(), downloaded); Intent intent_progress = new Intent(ACTION_UPDATE_PROGRESS); intent_progress.putExtra(KEY_SAVEDSHOW_ID, savedShow.getId()); intent_progress.putExtra(KEY_PROGRESS, downloaded ); LocalBroadcastManager.getInstance(DownloadService.this).sendBroadcast(intent_progress); } else{ break; } } //... } //... } } 

SavedShowListFragment

 public class SavedShowListFragment extends Fragment { //... @Override public void onResume() { super.onResume(); mAdapter = new SavedShowAdapter(getActivity(), MyApplication.dbHelper.getSavedShowList()); mListView.setAdapter(mAdapter); //... } private ServiceConnection mDownloadServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // Get service instance DownloadServiceBinder binder = (DownloadServiceBinder) service; mDownloadService = binder.getService(); // Set service to adapter, to 'bind' adapter to the service mAdapter.setDownloadService(mDownloadService); //... } @Override public void onServiceDisconnected(ComponentName arg0) { // Remove service from adapter, to 'unbind' adapter to the service mAdapter.setDownloadService(null); } }; private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(action.equals(DownloadService.ACTION_UPDATE_PROGRESS)){ mAdapter.updateItemProgress(intent.getLongExtra(DownloadService.KEY_SAVEDSHOW_ID, -1), intent.getLongExtra(DownloadService.KEY_PROGRESS, -1)); } //... } }; //... } 

SavedShowAdapter

 public class SavedShowAdapter extends ArrayAdapter<SavedShow> { private LayoutInflater mLayoutInflater; private List<Long> mSavedShowIdList; // list to find faster the position of the item in updateProgress private DownloadService mDownloadService; private Context mContext; static class ViewHolder { TextView title; TextView status; ProgressBar progressBar; DownloadStateButton downloadStateBtn; } public static enum CancelReason{ PAUSE, DELETE }; public SavedShowAdapter(Context context, List<SavedShow> savedShowList) { super(context, 0, savedShowList); mLayoutInflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE ); mContext = context; mSavedShowIdList = new ArrayList<Long>(); for(SavedShow savedShow : savedShowList){ mSavedShowIdList.add(savedShow.getId()); } } public void updateItemProgress(long savedShowId, long progress){ getItem(mSavedShowIdList.indexOf(savedShowId)).setProgress(progress); notifyDataSetChanged(); } public void updateItemFileSize(long savedShowId, int fileSize){ getItem(mSavedShowIdList.indexOf(savedShowId)).setFileSize(fileSize); notifyDataSetChanged(); } public void updateItemState(long savedShowId, int state_ind, String msg){ SavedShow.State state = SavedShow.State.values()[state_ind]; getItem(mSavedShowIdList.indexOf(savedShowId)).setState(state); if(state==State.ERROR){ getItem(mSavedShowIdList.indexOf(savedShowId)).setError(msg); } notifyDataSetChanged(); } public void deleteItem(long savedShowId){ remove(getItem((mSavedShowIdList.indexOf(savedShowId)))); notifyDataSetChanged(); } public void setDownloadService(DownloadService downloadService){ mDownloadService = downloadService; notifyDataSetChanged(); } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; View v = convertView; if (v == null) { v = mLayoutInflater.inflate(R.layout.saved_show_list_item, parent, false); holder = new ViewHolder(); holder.title = (TextView)v.findViewById(R.id.title); holder.status = (TextView)v.findViewById(R.id.status); holder.progressBar = (ProgressBar)v.findViewById(R.id.progress_bar); holder.downloadStateBtn = (DownloadStateButton)v.findViewById(R.id.btn_download_state); v.setTag(holder); } else { holder = (ViewHolder) v.getTag(); } holder.title.setText(getItem(position).getTitle()); Integer fileSize = getItem(position).getFileSize(); Long progress = getItem(position).getProgress(); if(progress != null && fileSize != null){ holder.progressBar.setMax(fileSize); holder.progressBar.setProgress(progress.intValue()); holder.status.setText(Utils.humanReadableByteCount(progress) + " / " + Utils.humanReadableByteCount(fileSize)); } holder.downloadStateBtn.setTag(position); SavedShow.State state = getItem(position).getState(); /* set the button state */ //... /* set buton onclicklistener */ holder.downloadStateBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { int position = (Integer) v.getTag(); SavedShow.State state = getItem(position).getState(); if(state==SavedShow.State.DOWNLOADING){ getItem(position).setState(SavedShow.State.WAIT_PAUSE); notifyDataSetChanged(); mDownloadService.cancelDownLoad(getItem(position).getId(), CancelReason.PAUSE); } else if(state==SavedShow.State.PAUSED || state==SavedShow.State.ERROR){ getItem(position).setState(SavedShow.State.WAIT_DOWNLOAD); notifyDataSetChanged(); mDownloadService.downLoadFile(getItem(position).getId()); } if(state==SavedShow.State.DOWNLOADED){ /* play file */ } } }); return v; } } 

3 Solutions collect form web for “Cómo actualizar algunos datos en un ListView sin utilizar notifyDataSetChanged ()?”

Por supuesto, como pjco declaró, no se actualicen a esa velocidad. Yo recomendaría enviar emisiones a intervalos. Mejor aún, tener un contenedor para los datos como el progreso y actualizar cada intervalo de sondeo.

Sin embargo, creo que es bueno también actualizar el listview a veces sin notifyDataSetChanged . En realidad esto es más útil cuando la aplicación tiene una mayor frecuencia de actualización. Recuerde: No estoy diciendo que su mecanismo de activación de actualización es correcto.


Solución

Básicamente, usted querrá actualizar una posición en particular sin notifyDataSetChanged . En el ejemplo siguiente, he asumido lo siguiente:

  1. Su lista de vista se llama mListView.
  2. Sólo desea actualizar el progreso
  3. Su barra de progreso en su convertView tiene el ID R.id.progress

 public boolean updateListView(int position, int newProgress) { int first = mListView.getFirstVisiblePosition(); int last = mListView.getLastVisiblePosition(); if(position < first || position > last) { //just update your DataSet //the next time getView is called //the ui is updated automatically return false; } else { View convertView = mListView.getChildAt(position - first); //this is the convertView that you previously returned in getView //just fix it (for example:) ProgressBar bar = (ProgressBar) convertView.findViewById(R.id.progress); bar.setProgress(newProgress); return true; } } 

Notas

Este ejemplo, por supuesto, no está completo. Probablemente puede utilizar la siguiente secuencia:

  1. Actualizar sus datos (cuando reciba nuevos progresos)
  2. Llame a updateListView(int position) que debe usar el mismo código pero actualizar utilizando su conjunto de datos y sin el parámetro.

Además, acabo de notar que tiene algún código publicado. Puesto que usted está utilizando un titular, simplemente puede obtener el titular dentro de la función. No voy a actualizar el código (creo que es auto-explicativo).

Por último, sólo para enfatizar, cambie todo el código para activar las actualizaciones de progreso. Una manera rápida sería alterar su servicio: Envuelva el código que envía la transmisión con una instrucción if que comprueba si la última actualización había sido más de un segundo o medio segundo atrás y si la descarga ha terminado (no es necesario comprobar terminado pero Asegúrese de enviar la actualización cuando haya terminado):

En tu servicio de descarga

 private static final long INTERVAL_BROADCAST = 800; private long lastUpdate = 0; 

Ahora en doInBackground, envuelva el envío de la intención con una instrucción if

 if(System.currentTimeMillis() - lastUpdate > INTERVAL_BROADCAST) { lastUpdate = System.currentTimeMillis(); Intent intent_progress = new Intent(ACTION_UPDATE_PROGRESS); intent_progress.putExtra(KEY_SAVEDSHOW_ID, savedShow.getId()); intent_progress.putExtra(KEY_PROGRESS, downloaded ); LocalBroadcastManager.getInstance(DownloadService.this).sendBroadcast(intent_progress); } 

La respuesta corta: no actualizar la interfaz de usuario basada en velocidades de datos

A menos que esté escribiendo una aplicación de estilo de prueba de velocidad, no hay ningún beneficio para el usuario de actualizar de esta manera.

ListView está muy bien optimizado , (como parece saber ya que está utilizando el patrón ViewHolder).

¿Has intentado llamar a notifyDataSetChanged() cada 1 segundo?

Cada 1024 bytes es ridículamente rápido . Si alguien está descargando a 8Mbps que podría estar actualizando más de 1000 veces por segundo y esto definitivamente podría causar una ANR.

En lugar de actualizar el progreso basado en la cantidad descargada, debe realizar una encuesta en la cantidad en un intervalo que no provoca el bloqueo de la interfaz de usuario.

De todas formas, para evitar bloquear el hilo de la interfaz de usuario, puede publicar actualizaciones en un Handler .

Jugar con el valor de sleep para asegurarse de que no se están actualizando con demasiada frecuencia. Usted podría intentar ir tan bajo como 200ms pero no iría por debajo de 500ms para estar seguro. El valor exacto depende de los dispositivos que esté dirigiendo y del número de elementos que necesitarán pases de diseño.

NOTA: esto es sólo una forma de hacerlo, hay muchas maneras de realizar bucle como este.

 private static final int UPDATE_DOWNLOAD_PROGRESS = 666; Handler myHandler = new Handler() { @Override handleMessage(Message msg) { switch (msg.what) { case UPDATE_DOWNLOAD_PROGRESS: myAdapter.notifyDataSetChanged(); break; default: break; } } } private void runUpdateThread() { new Thread( new Runnable() { @Override public void run() { while ( MyFragment.this.getIsDownloading() ) { try { Thread.sleep(1000); // Sleep for 1 second MyFragment.this.myHandler .obtainMessage(UPDATE_DOWNLOAD_PROGRESS) .sendToTarget(); } catch (InterruptedException e) { Log.d(TAG, "sleep failure"); } } } } ).start(); } 

Aunque no es una respuesta a su pregunta, pero una optimización que se puede hacer en su método getView() es esto, en lugar de crear y establecer el oyente de clics cada vez como esto:

 holder.downloadStateBtn.setTag(position); holder.downloadStateBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { int position = (Integer) v.getTag(); // your current normal click handling } }); 

Usted puede apenas crearlo una vez como variable de la clase y fijarlo mientras que crea la View la fila:

 final OnClickListener btnListener = new OnClickListener() { @Override public void onClick(View v) { int position = (Integer) v.getTag(); // your normal click handling code goes here } } 

Y luego en getView() :

  if (v == null) { v = mLayoutInflater.inflate(R.layout.saved_show_list_item, parent, false); // your ViewHolder stuff here holder.downloadStateBtn.setOnClickListener(btnClickListener);//<<<<< v.setTag(holder); } else { holder = (ViewHolder) v.getTag(); } 

Oh y no se olvide de establecer la etiqueta en este botón en getView() como ya está haciendo:

 holder.downloadStateBtn.setTag(position); 
  • Adaptador de Android "java.lang.IndexOutOfBoundsException: Índice no válido 4, tamaño es 4"
  • Hace notificydatasetchanged llamada onCreateViewHolder al usar RecyclerView
  • Android ParseQueryAdapter notifyDataSetChanged no funciona
  • Cómo utilizar ListView, ArrayList, ArrayAdapter y notifyDataSetChanged junto con un runnable, y un Handler de mensaje en el hilo principal
  • Android ListView no se actualiza después de notifyDataSetChanged
  • El adaptador de RecyclerView notifyDataSetChanged no funciona
  • Cómo añadir sugerencias dinámicamente a autocompletetextview con preservar el estado de los caracteres
  • Error: No se puede resolver notifyDataSetChanged (); Androide
  • Cómo evitar la renovación de las celdas al llamar a notifyDataSetChanged () para PinterestLikeAdapterView?
  • Using notifyItemRemoved o notifyDataSetChanged con RecyclerView en Android
  • Android: ListFragment refresh utilizando notifyDataSetChanged () con el adaptador personalizado no funciona
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.