update studio refrescar editar dinamico como actualizar android listview notifydatasetchanged

android - studio - ¿Cómo actualizar algunos datos en una vista de lista sin usar notifyDataSetChanged()?



reload listview android studio (3)

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

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

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

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

Creo 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 la llamada con mucha frecuencia notifyDataSetChanged() hacer que la IU de ListView no responda?

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

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

A continuación están las partes relevantes de mi código.

Descargar tarea en 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; } }


La respuesta corta: no actualice la interfaz de usuario según las velocidades de datos

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

ListView está muy bien optimizado (como parece que ya sabes porque estás usando 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, esto definitivamente podría causar un ANR.

En lugar de actualizar el progreso en función de la cantidad descargada, debe sondear la cantidad en un intervalo que no cause bloqueo de UI.

De todos modos, para ayudar a evitar el bloqueo de la interfaz de usuario, puede publicar actualizaciones en un Handler .

Juegue con el valor de sleep para asegurarse de que no se actualiza con demasiada frecuencia. Podrías intentar ir tan bajo como 200ms pero no bajaría menos de 500ms para estar seguro. El valor exacto depende de los dispositivos a los que apunta y del número de elementos que necesitarán pases de disposición.

NOTA: esta es solo una forma de hacerlo, hay muchas formas de lograr un 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 la siguiente, en lugar de crear y configurar oyente de clic cada vez de esta manera:

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 } });

Puedes simplemente crearlo una vez como variable de clase y configurarlo al crear la View de 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(); }

Ah, y no te olvides de configurar la etiqueta en este botón en getView() como ya estás haciendo:

holder.downloadStateBtn.setTag(position);


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

Sin embargo, creo que también es bueno actualizar la notifyDataSetChanged sin notifyDataSetChanged . En realidad, esto es más útil cuando la aplicación tiene una frecuencia de actualización más alta. Recuerde: no estoy diciendo que su mecanismo de activación de actualizaciones sea correcto.

Solución

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

  1. Su vista de lista se llama mListView.
  2. Solo quieres 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 puedas usar la siguiente secuencia:

  1. Actualiza tus Datos (cuando recibes un nuevo progreso)
  2. Llame a updateListView(int position) que debe usar el mismo código, pero actualice usando su conjunto de datos y sin el parámetro.

Además, acabo de notar que tienes un código publicado. Como está utilizando un Soporte, simplemente puede colocar el soporte dentro de la función. No actualizaré el código (creo que es autoexplicativo).

Por último, solo para enfatizar, cambie todo su código para activar actualizaciones de progreso. Una manera rápida sería modificar su Servicio: Envuelva el código que envía la transmisión con una declaración if que verifique si la última actualización fue hace más de un segundo o medio segundo y si la descarga ha finalizado (no es necesario verificar que haya terminado, asegúrese de enviar la actualización cuando termine):

En tu servicio de descarga

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

Ahora en doInBackground, envuelva el intento de envío con una declaració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); }