web-dev-qa-db-fra.com

Comment mettre à jour des données dans une liste de réception sans utiliser NotifyDatastEquée ()?

J'essaie de créer un ListView avec une liste de tâches de téléchargement.

Les tâches de téléchargement sont gérées dans un Service (downloadservice). Chaque fois qu'un morceau de données est reçu, la tâche envoie la progression via un Broadcast, reçu par le Fragment contenant le ListView (SavedShowlistfragment). Lors de la réception du message Broadcast, le fichier SavedShowlistFraGment met à jour la progression des tâches de téléchargement dans l'adaptateur et des déclencheurs notifyDataSetChanged().

Chaque ligne de la liste contient un ProgressBar, un TextView pour le titre du fichier téléchargé et un pour la valeur numérique de la progression, et un Button pour mettre en pause/reprendre le téléchargement ou lire le spectacle enregistré lorsque le téléchargement est terminé.

Le problème est que la pause/reprise/la lecture Button n'est souvent pas réactive (onClick() n'est pas appelée), et je pense que c'est parce que toute la liste est mise à jour très fréquemment avec notifyDataSetChanged() (Chaque fois qu'un morceau de données, c'est-à-dire 1024 octets, qui peut être plusieurs fois par seconde, en particulier lorsqu'il existe plusieurs tâches de téléchargement en cours d'exécution).

Je suppose que je pourrais augmenter la taille du morceau de données dans les tâches de téléchargement, mais je pense vraiment que ma méthode n'est pas optimale du tout!

Pourrait appeler très fréquemment notifyDataSetChanged() rendre l'interface utilisateur ListView insensible?

Y a-t-il un moyen de mettre à jour seulement certains Views dans les lignes ListView, c'est-à-dire dans mon cas le ProgressBar et le TextView avec la valeur numérique de la progression de la progression , sans appeler notifyDataSetChanged(), qui met à jour toute la liste?

Pour mettre à jour la progression des tâches de téléchargement dans ListView, y a-t-il une meilleure option que "getChunk/sendbroadcast/mise à jour de mise à jourata/notifierdatasched"?

Vous trouverez ci-dessous les parties pertinentes de mon code.

Tâche de téléchargement dans le service de téléchargement

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

            //...
        }

        //...
    }
}

Sauvédshowlistfragment

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

            //...
        }
    };

    //...

}

Sauvéhowadapter

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;
    }
} 
23
jul

Bien sûr, comme PJCO a déclaré, ne mettez pas à jour à cette vitesse. Je recommanderais d'envoyer des émissions à intervalles. Mieux encore, j'ai un conteneur pour les données telles que la progression et la mise à jour de chaque intervalle par interrogation.

Cependant, je pense que c'est une bonne chose aussi bien de mettre à jour la liste de réception sans notifyDataSetChanged. En réalité, cela est le plus utile lorsque l'application a une fréquence de mise à jour plus élevée. N'oubliez pas: je ne dis pas que votre mécanisme de déclenchement de mise à jour est correct.


solution

Fondamentalement, vous voudrez mettre à jour une position particulière sans notifyDataSetChanged. Dans l'exemple suivant, j'ai supposé ce qui suit:

  1. Votre liste de liste est appelée mlistview.
  2. Vous voulez seulement mettre à jour les progrès
  3. Votre barre de progression dans votre convertieView a l'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;
    }
}

Notes

Cet exemple n'est bien sûr pas complet. Vous pouvez probablement utiliser la séquence suivante:

  1. Mettez à jour vos données (lorsque vous recevez de nouveaux progrès)
  2. Call updateListView(int position) Cela devrait utiliser le même code mais la mise à jour à l'aide de votre jeu de données et sans paramètre.

De plus, je viens de remarquer que vous avez du code posté. Puisque vous utilisez un support, vous pouvez simplement obtenir le support dans la fonction. Je ne mettrai pas à jour le code (je pense qu'il est explicite de soi).

Enfin, juste pour souligner, changez votre code entier pour déclencher des mises à jour de progrès. Un moyen rapide serait de modifier votre service: enveloppez le code qui envoie la diffusion avec une instruction IF qui vérifie si la dernière mise à jour avait été plus d'une seconde ou une demi-seconde et si le téléchargement est terminé (pas besoin de vérifier la fin du téléchargement. Assurez-vous d'envoyer la mise à jour lorsque vous avez terminé):

Dans votre service de téléchargement

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

Maintenant, dans DoIndbackground, enveloppez l'intention d'envoyer avec une déclaration si

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);
}
22
Sherif elKhatib

Bien que ce n'est pas une réponse à votre question, mais une optimisation pouvant être effectuée dans votre méthode getView() est ceci, au lieu de créer et de définir cliquez sur l'auditeur à chaque fois comme celui-ci:

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

Vous pouvez simplement la créer une fois en tant que variable de classe et la définir tout en créant la ligne View:

final OnClickListener btnListener = new OnClickListener() {

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

puis 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 et n'oubliez pas de définir une balise sur ce bouton dans getView() Comme vous le faites déjà:

holder.downloadStateBtn.setTag(position);
3
M-WaJeEh