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