web-dev-qa-db-ja.com

notifyDataSetChanged()を使用せずにリストビューの一部のデータを更新する方法は?

ダウンロードタスクのリストを含むListViewを作成しようとしています。

ダウンロードタスクはService(DownloadService)で管理されます。データのチャンクが受信されるたびに、タスクは、Broadcast(SavedShowListFragment)を含むFragmentによって受信されたListViewを介して進行状況を送信します。 Broadcastメッセージを受信すると、SavedShowListFragmentはアダプターのダウンロードタスクの進行状況を更新し、notifyDataSetChanged()をトリガーします。

リストのすべての行には、ProgressBarTextViewがダウンロードされているファイルのタイトル、1つが進行状況の数値、そしてButtonが一時停止/ダウンロードが再開したら、ダウンロードを再開するか、ダウンロードが完了したら保存済みの番組を再生します。

問題は、一時停止/再開/再生Buttonが応答しないことがよくあり(onClick()が呼び出されない)、リスト全体がnotifyDataSetChanged()(特に、いくつかのダウンロードタスクが実行されている場合は、データのチャンク、つまり1024バイトが毎秒何回も受信される可能性があります)。

ダウンロードタスクでデータチャンクのサイズを増やすことができると思いますが、私の方法は本当に最適ではないと思います!

非常に頻繁にnotifyDataSetChanged()を呼び出すと、ListView UIが応答しなくなりますか?

Views行の一部のListViewのみを更新する方法はありますか。つまり、私の場合、ProgressBarTextViewは進行状況の数値で更新されます、notifyDataSetChanged()を呼び出さずに、リスト全体を更新しますか?

ListViewのダウンロードタスクの進行状況を更新するには、「getChunk/sendBroadcast/updateData/notifyDataSetChanged」よりも優れたオプションがありますか?

以下は、私のコードの関連部分です。

ダウンロードサービスでのダウンロードタスク

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

もちろん、pjcoで述べたように、その速度で更新しないでください。間隔をあけてブロードキャストを送信することをお勧めします。さらに良いのは、進捗状況などのデータのコンテナーを用意し、ポーリングによって間隔ごとに更新することです。

ただし、notifyDataSetChangedなしでリストビューを更新することも良いことだと思います。実際、これはアプリケーションの更新頻度が高い場合に最も役立ちます。覚えておいてください:私はあなたの更新トリガーメカニズムが正しいと言っているのではありません。


ソリューション

基本的に、notifyDataSetChangedなしで特定の位置を更新する必要があります。次の例では、次のことを前提としています。

  1. リストビューはmListViewと呼ばれます。
  2. 進行状況のみを更新したい
  3. ConvertViewのプログレスバーの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;
    }
}
_

メモ

もちろん、この例は完全ではありません。おそらく次のシーケンスを使用できます。

  1. データを更新する(新しい進捗状況を受け取ったとき)
  2. 同じコードを使用する必要があるupdateListView(int position)を呼び出しますが、パラメーターなしでデータセットを使用して更新します。

さらに、いくつかのコードが投稿されていることに気づきました。ホルダーを使用しているため、関数内でホルダーを取得できます。私はコードを更新しません(自明だと思います)。

最後に、強調するために、進行状況の更新をトリガーするためにコード全体を変更します。高速な方法はサービスを変更することです:ブロードキャストを送信するコードをifステートメントでラップし、最後の更新が1秒または0.5秒以上前かどうか、およびダウンロードが終了したかどうかをチェックします(終了を確認する必要はありませんが、終了したら必ず更新を送信してください):

ダウンロードサービス

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

次にdoInBackgroundで、送信するインテントを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);
}
_
22
Sherif elKhatib

短い答え:データ速度に基づいてUIを更新しないでください

速度テストスタイルのアプリを作成しているのでない限り、この方法で更新してもユーザーにメリットはありません。

ListView非常に適切に最適化されています、(ViewHolderパターンを使用しているため、すでに知っているようです)。

1秒ごとにnotifyDataSetChanged()を呼び出してみましたか?

1024バイトごとに途方もなく速いです。誰かが8Mbpsでダウンロードしている場合、更新される可能性があります1秒に1000回以上これは確実にANRを引き起こす可能性があります。

ダウンロード量に基づいて進行状況を更新するのではなく、UIブロックを引き起こさない間隔で量をポーリングする必要があります。

とにかく、UIスレッドのブロックを回避するために、Handlerに更新を投稿できます。

sleepの値をいじって、頻繁に更新しないようにしてください。 200msのように低くすることもできますが、500ms以下にはなりません。正確な値は、ターゲットデバイスとレイアウトパスを必要とするアイテムの数によって異なります。

注:これはこれを行う方法の1つにすぎません。このようにループを実行するには多くの方法があります。

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(); 
}
9
pjco

それはあなたの質問への答えではありませんが、getView()メソッドで実行できる最適化の1つはこれです。毎回クリックリスナーを作成して設定する代わりに、次のようにします。

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

クラス変数として一度作成して、行のViewを作成するときに設定できます。

_final OnClickListener btnListener = new OnClickListener() {

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

そして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();
    }
_

ああ、あなたはすでにやっているので、getView()のこのボタンにタグを設定することを忘れないでください:

_holder.downloadStateBtn.setTag(position);
_
3
M-WaJeEh