satohu20xx's diary

思ったことをつらつらと

画像ダウンロードでAsncTaskを立ち上げまくるのはやめましょう

ListViewでよくあるサンプルで画像をダウンロードするたびにAsyncTaskをたちあげてる奴があるけどそれはやめましょう

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.search_row, null);
            holder = new ViewHolder();
            holder.Title = (TextView)convertView.findViewById(R.id.textTitle);
            holder.Thumbnail = (ImageView)convertView.findViewById(R.id.imageThumbnail);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder)convertView.getTag();
        }

        item = getItem(position);
        if(item != null){
            holder.Title.setText(item.getTitle());
            holder.Thumbnail.setTag(item.getThumbnail());
            new DownLoadAsyncTask(holder.Thumbnail).execute(item.getThumbnail());
        }

        return convertView;
    }

こんなソースを書くとgetView()がよばれるたびにAsyncTaskが立ちがあることになっちゃう。
ListViewでスクロールするとそのぶんだけgetView()が呼ばれることになるから、めっちゃくっちゃAsyncTaskが立ち上がることにって端末の動きがめちゃくちゃ重くなるんだよね。

だから、Workerスレッド立ち上げて上手いこと書きましょう。

具体的には↓みたいな感じ。

/**
 * 画像ダウンローダークラス
 * @author satohu20xx
 */
public class LoadBitmapManager {

    private static final int THREAD_MAX_NUM = 3;

    private static BlockingQueue<LoadBitmapItem> downloadQueue;
    private static Handler handler;

    /**
     * 始めて使われるときに初期化される。
     */
    static {
        /*
         * 画像情報を貯めるためのキュー
         */
        downloadQueue = new LinkedBlockingQueue<LoadBitmapItem>();

        /*
         * スレッド最大数まで画像ダウンロードスレッドを作成
         */
        for(int i=0 ; i<THREAD_MAX_NUM ; i++) {
            new Thread(new DownloadWorker()).start();
        }

        /*
         * 画像ダウンロード後にメッセージを受信するハンドラーを作成
         */
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                /*
                 * 取得したメッセージから画像情報を取得
                 */
                LoadBitmapItem item = (LoadBitmapItem)msg.obj;

                /*
                 * 画像ダウンロードがうまくいっていた場合はイメージビューに設定
                 */
                if(item.getImgView().getTag() == item.getUrl() && item.getBitmap() != null) {
                    item.getImgView().setImageBitmap(item.getBitmap());
                }
            }
        };
    }

    /**
     * 引数として渡されたurlで画像をダウンロードしてImageViewに対して
     * 画像を設定する。
     * @param imgView
     * @param url
     */
    public static void doDownloadBitmap(ImageView imgView, String url) {
        /*
         * ダウンロードキューに入れる
         */
        LoadBitmapItem item = new LoadBitmapItem();
        item.setImgView(imgView);
        item.setUrl(url);
        downloadQueue.offer(item);

        return;
    }

    /**
     * 実際に画像をダウンロードするワーカー
     * @author satohu20xx
     */
    private static class DownloadWorker implements Runnable {

        @Override
        public void run() {

            /*
             * 画像ダウンロードスレッドは常に動き続けるから無限ループ
             */
            for(;;) {
                Bitmap bitmap;
                LoadBitmapItem item;

                try {
                    /*
                     * キューに値が入ったら呼び出される
                     * nullの状態ではwaitしている
                     */
                    item = downloadQueue.take();
                } catch (Exception ex){
                    Log.e("ERROR", "", ex);
                    continue;
                }

                /*
                 * ダウンロード
                 */
                try{
                    BufferedInputStream in = new BufferedInputStream(
                                    (InputStream) (new URL(item.getUrl())).getContent());
                    bitmap = BitmapFactory.decodeStream(in);
                    in.close();
                } catch (Exception ex){
                    Log.e("ERROR", "", ex);
                }
                item.setBitmap(bitmap);

                /*
                 * 取得した画像情報でメッセージを作って投げる
                 */
                Message msg = new Message();
                msg.obj = item;
                handler.sendMessage(msg);
            }
        }
    }
}

エラー処理とかは端折ってるけど、こんな感じで書けば立ち上がるスレッド数の上限が決まるから端末に負担がかからない。
タスクを立ち上げれば立ち上げるほど早くなると思ってるかもしれないけど、CPUのパワーには上限があるからそんなことはなくて、うまーくうごく閾値を探してうまく動かしてあげないと動きが重すぎてイライラ感が募るだけかと。

なにか突っ込みがあればお待ちしております。