Управление несколькими асинхронными задачами для загрузки нескольких изображений из html-кода, утечка оперативной памяти, какие-либо идеи?

Я разрабатываю приложение для Android. Теперь я разбираю bbcode в html и отображаю его внутри текстового представления, текстовое представление находится внутри пользовательского списка. Я использую Html.ImageGetter() для отображения изображений, загруженных из AsyncTask.

Он отлично работает для небольшого количества изображений. Но если приложение просят загрузить 40-50 картинок, создается 40-50 заданий и становится каша. Каждая задача открывает поток для загрузки изображений. После этого он декодирует байты в растровые изображения, изменяет их размер, сохраняет их на SD-карту и перерабатывает растровые изображения.

Теперь, если приложение загружает все эти изображения одновременно, оно использует огромное количество оперативной памяти. Мне удалось заставить его пройти 48 мб. Существует большой разрыв между 16 и 48 :(. Я искал, как это решить. Я скачал код AsyncTask из Google:

http://google.com/codesearch/p?hl=en&sa=N&cd=2&ct=rc#uX1GffpyOZk/core/java/android/os/AsyncTask.java&q=lang:java%20AsyncTask

И установите размер пула на 3. Но это не помогло. Я действительно не могу понять, где я теряю баран. Как только я ставлю большую очередь задач, мой баран сходит с ума. После получения нескольких изображений становится хуже. Я не думаю, что это изображения, так как я могу получить до 30 МБ, прежде чем отобразится какое-либо изображение. Само приложение, включая вид, информацию и сервис, занимает 13 Мб, все остальное просочилось сюда.

Выделяет ли сама очередь большие объемы оперативной памяти? Или Html.ImageGetter() каким-то образом пропускает огромное количество памяти? Есть лучший способ сделать это?

Здесь я загружаю изображения:

public void LoadImages(String source) {

    myurl = null;
    try {
        myurl = new URL(source);
    } catch (MalformedURLException e) {
        e.printStackTrace();
    }

    new DownloadImageFromPost().execute(myurl);
}

private class DownloadImageFromPost extends AsyncTask<URL, Integer, Bitmap> {

    @Override
    protected Bitmap doInBackground(URL... params) {
        URL url;
        Log.d(TAG, "Starting new image download");
        try {
            url = params[0];
            HttpURLConnection connection = (HttpURLConnection) url
            .openConnection();
            int length = connection.getContentLength();
            InputStream is = (InputStream) url.getContent();
            byte[] imageData = new byte[length];
            int buffersize = (int) Math.ceil(length / (double) 100);
            int downloaded = 0;
            int read;
            while (downloaded < length) {
                if (length < buffersize) {
                    read = is.read(imageData, downloaded, length);
                } else if ((length - downloaded) <= buffersize) {
                    read = is.read(imageData, downloaded, length
                            - downloaded);
                } else {
                    read = is.read(imageData, downloaded, buffersize);
                }
                downloaded += read;
                publishProgress((downloaded * 100) / length);
            }
            Bitmap bitmap = BitmapFactory.decodeByteArray(imageData, 0,
                    length);
            if (bitmap != null) {
                Log.i(TAG, "Bitmap created");
            } else {
                Log.i(TAG, "Bitmap not created");
            }
            is.close();
            return bitmap;

        } catch (MalformedURLException e) {
            Log.e(TAG, "Malformed exception: " + e.toString());

        } catch (IOException e) {
            Log.e(TAG, "IOException: " + e.toString());

        } catch (Exception e) {

        }


    return null;

}

protected void onPostExecute(Bitmap result) {

        String name = Environment.getExternalStorageDirectory() + "/tempthumbs/" + myurl.toString().hashCode() +".jpg";
        String rname = Environment.getExternalStorageDirectory() + "/tempthumbs/" + myurl.toString().hashCode() +"-t.jpg";
        try {
            if (result != null) {
                    hasExternalStoragePublicPicture(name); 
                    ImageManager manager = new ImageManager(context);
                    Bitmap rezised = manager.resizeBitmap(result, 300, 300);
                    saveToSDCard(result, name,  myurl.toString().hashCode() +".jpg");
                    saveToSDCard(rezised, rname, myurl.toString().hashCode() +"-t.jpg");
                    result.recycle();
                    rezised.recycle();

            } else {

            }
        } catch(NullPointerException e) {
        }

    Log.d(TAG, "Sending images loaded announcement");
    Intent i = new Intent(IMAGE_LOADED);
    i.putExtra("image",  name);
    i.putExtra("source", myurl.toString());
    i.putExtra("class", true);
    context.sendBroadcast(i);

}

}


    private boolean hasExternalStoragePublicPicture(String name) {
    File file = new File(name);
    if (file != null) {
        file.delete();
    }

        try {
            file.createNewFile();

        } catch (IOException e) {
        e.printStackTrace();


        }

    return file.exists();
}

public void saveToSDCard(Bitmap bitmap, String name, String nam) {
    boolean mExternalStorageAvailable = false;
    boolean mExternalStorageWriteable = false;
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        mExternalStorageAvailable = mExternalStorageWriteable = true;
        Log.v(TAG, "SD Card is available for read and write "
                + mExternalStorageAvailable + mExternalStorageWriteable);
        saveFile(bitmap, name, nam);
    } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        mExternalStorageAvailable = true;
        mExternalStorageWriteable = false;
        Log.v(TAG, "SD Card is available for read "
                + mExternalStorageAvailable);
    } else {
        mExternalStorageAvailable = mExternalStorageWriteable = false;
        Log.v(TAG, "Please insert a SD Card to save your Video "
                + mExternalStorageAvailable + mExternalStorageWriteable);
    }
}

private void saveFile(Bitmap bitmap, String fullname, String nam) {

    ContentValues values = new ContentValues();

    File outputFile = new File(fullname);
    values.put(MediaStore.MediaColumns.DATA, outputFile.toString());
    values.put(MediaStore.MediaColumns.TITLE, nam);
    values.put(MediaStore.MediaColumns.DATE_ADDED, System
            .currentTimeMillis());
    values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
    Uri uri = context.getContentResolver().insert(
            android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,

            values);;

    try {
        OutputStream outStream = context.getContentResolver()
                .openOutputStream(uri);
        bitmap.compress(Bitmap.CompressFormat.JPEG, 95, outStream);

        outStream.flush();
        outStream.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    bitmap.recycle();
}

И здесь я вызываю Html.ImageGetter(), это внутри списка getView:

holder.content.setText(Html.fromHtml(
   processor.preparePostText(posts.get(position).post_content),
   new Html.ImageGetter() {

      @Override public Drawable getDrawable(String source) {

         Log.d("Forum Service", "image source: " + source);
         if (imageSources.contains(source)) {
            for (int x = 0; x < imageSources.size(); x++) {
               if (source.equals(imageSources.get(x))) {

                  String tmp = oImages.get(x);
                  tmp = tmp.substring(0, tmp.length() - 4);
                  tmp = tmp + "-t.jpg";
                  Drawable d = Drawable.createFromPath(tmp);
                  try {
                     d.setBounds(0, 0, d.getIntrinsicWidth(),
                        d.getIntrinsicHeight());
                  } catch (NullPointerException e) {

                  }
                  Log.d("Forum Service", "Loaded image froms sdcard");
                  return d;
               }
            }

         } else if (notLoadedImages.contains(source)) {
            Log.d("Forum Service", "Waiting for image");
            return null;
         } else {
            notLoadedImages.add(source);
            LoadAllIcons loader = new LoadAllIcons(context);
            loader.LoadImages(source);
            Log.d("Forum Service", "Asked for image");
            return null;
         }
         return null;
      }

   }, null));

Спасибо!

Наконец, проблема заключалась в том, что все задачи загружались одновременно. Таким образом, при загрузке в оперативной памяти было выделено 40 изображений. Мне удалось ограничить количество запущенных задач, выполнив следующие изменения в AsyncTask:

private static final int CORE_POOL_SIZE = 2;
private static final int MAXIMUM_POOL_SIZE = 2;
private static final int KEEP_ALIVE = 1;

private static final BlockingQueue<Runnable> sWorkQueue =
        new LinkedBlockingQueue<Runnable>(100);

person Mateo    schedule 07.02.2011    source источник
comment
К вашему сведению, приведенный выше код по-прежнему блокирует поток пользовательского интерфейса при загрузке с SD-карты (не рекомендуемое поведение). Также похоже, что если вы не дождетесь загрузки всех изображений перед отображением ListView, ListView не будет обновляться даже после того, как изображения будут готовы, пока пользователь не прокрутит экран и строки не будут воссозданы. См. мой связанный с этим вопрос.   -  person Jeff Axelrod    schedule 30.07.2012


Ответы (1)


Матео, вот ваш ответ http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html

И вы сделали!

person 100rabh    schedule 07.02.2011
comment
Спасибо!! Я присматриваюсь к нему последние дни. Я пытаюсь понять, как использовать слабые ссылки на изображения, загруженные в html-код textview; а также если у меня есть утечка или если во входном потоке есть какое-то переполнение, которое занимает оперативную память. Как только я это выясню, я отмечу ваш ответ как ответ :) - person Mateo; 10.02.2011
comment
Спасибо, это помогло мне, так как он использует меньше оперативной памяти, чтобы использовать поток flushedinputstream для декодирования растрового изображения при его загрузке; вместо того, чтобы загружать байты и декодировать их в растровое изображение. Хотя я наконец заметил, что моя основная проблема заключалась в том, что все задачи начали загружаться одновременно. Мне удалось ограничить количество задач до 2 загрузок и поставить все остальные в очередь. Я обновлю ветку, чтобы изменения в AsyncTask работали так. - person Mateo; 13.02.2011