23

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

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

リストの各行には、ProgressBarダウンロードTextView中のファイルのタイトル用の と進行状況の数値用の 、Buttonダウンロードの一時停止/再開、またはダウンロードが終了したときに保存されたショーの再生用の が含まれます。

問題は、一時停止/再開/再生Buttonが頻繁に応答しない (呼び出されない) ことです。これは、(データのチャンク、つまり 1024 バイトが受信されるたびonClick()に) リスト全体が非常に頻繁に更新されるためだと思います。notifyDataSetChanged()特に複数のダウンロード タスクが実行されている場合)。

ダウンロード タスクでデータ チャンクのサイズを大きくできると思いますが、私の方法はまったく最適ではないと思います。

非常に頻繁に呼び出すとnotifyDataSetChanged()ListViewUI が応答しなくなる可能性がありますか?

リスト全体を更新するを呼び出さずViewsに、ListView行の一部のみを更新する方法はありProgressBarますか?TextViewnotifyDataSetChanged()

のダウンロード タスクの進行状況を更新するには、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;
                }
            }

            //...
        }

        //...
    }
}

保存されたShowListFragment

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

            //...
        }
    };

    //...

}

保存されたShowAdapter

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

3 に答える 3

22

もちろん、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)同じコードを使用する必要がありますが、データセットを使用してパラメーターなしで更新する呼び出し。

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

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

ダウンロードサービスで

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);
}
于 2013-09-30T09:31:12.653 に答える
9

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

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

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

notifyDataSetChanged()1秒ごとに 電話をかけてみましたか?

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

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

とにかく、UI スレッドのブロックを回避するために、更新をHandler.

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

注: これはこれを行う 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(); 
}
于 2013-09-29T04:40:15.803 に答える
3

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

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);
于 2013-10-04T18:21:55.230 に答える