BitmapFactory.Options.inBitmap вызывает разрывы при частом переключении растрового изображения ImageView

Я столкнулся с ситуацией, когда мне приходится показывать изображения в слайд-шоу, которое очень быстро переключает изображение. Огромное количество изображений заставляет меня хотеть хранить данные JPEG в памяти и декодировать их, когда я хочу их отображать. Чтобы облегчить сборщик мусора, я использую BitmapFactory.Options.inBitmap для повторного использования растровых изображений.

К сожалению, это приводит к довольно сильному разрыву, я пробовал различные решения, такие как синхронизация, семафоры, чередующиеся между 2-3 растровыми изображениями, однако ни одна из них не устраняет проблему.

Я создал пример проекта, который демонстрирует эту проблему в GitHub; https://github.com/Berglund/android-tearing-example

У меня есть поток, который декодирует растровое изображение, устанавливает его в потоке пользовательского интерфейса и спит в течение 5 мс:

Runnable runnable = new Runnable() { @Override public void run() { while(true) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 1; if(bitmap != null) { options.inBitmap = bitmap; } bitmap = BitmapFactory.decodeResource(getResources(), images.get(position), options); runOnUiThread(new Runnable() { @Override public void run() { imageView.setImageBitmap(bitmap); } }); try { Thread.sleep(5); } catch (InterruptedException e) {} position++; if(position >= images.size()) position = 0; } } }; Thread t = new Thread(runnable); t.start(); 

Моя идея состоит в том, что ImageView.setImageBitmap (Bitmap) рисует растровое изображение на следующем vsync, однако мы, вероятно, уже расшифровываем следующий растровый файл, когда это происходит, и поэтому мы начали изменять растровые пиксели. Думаю ли я в правильном направлении?

Кто-нибудь получил какие-либо советы о том, куда идти отсюда?

Solutions Collecting From Web of "BitmapFactory.Options.inBitmap вызывает разрывы при частом переключении растрового изображения ImageView"

В качестве альтернативы вашему текущему подходу вы можете сохранить данные JPEG так, как вы это делаете, но также создать отдельный битмап для каждого из ваших изображений и использовать флаги inPurgeable и inInputShareable . Эти флаги выделяют резервную память для ваших растровых изображений в отдельной куче, которая напрямую не управляется сборщиком мусора Java, и позволяет Android самостоятельно отбрасывать данные растрового изображения, когда у него нет места для него, и при необходимости повторно декодировать ваши JPEG-файлы по требованию , Android имеет весь этот специальный код для управления растровыми данными, поэтому почему бы не использовать его?

Вы должны использовать метод onDraw () для ImageView, поскольку этот метод вызывается, когда представление нужно нарисовать его содержимое на экране.

Я создаю новый класс с именем MyImageView, который расширяет ImageView и переопределяет метод onDraw (), который инициирует обратный вызов, чтобы слушатель знал, что это представление закончило его рисование

 public class MyImageView extends ImageView { private OnDrawFinishedListener mDrawFinishedListener; public MyImageView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mDrawFinishedListener != null) { mDrawFinishedListener.onOnDrawFinish(); } } public void setOnDrawFinishedListener(OnDrawFinishedListener listener) { mDrawFinishedListener = listener; } public interface OnDrawFinishedListener { public void onOnDrawFinish(); } } 

В MainActivity определите 3 растровых изображения: одну ссылку на растровое изображение, которое используется ImageView для рисования, одно для декодирования и одна ссылка на растровое изображение, которое перерабатывается для следующего декодирования. Я повторно использую синхронизированный блок из ответа vminorov, но помещаю его в разные места с объяснением в комментарии к коду

 public class MainActivity extends Activity { private Bitmap mDecodingBitmap; private Bitmap mShowingBitmap; private Bitmap mRecycledBitmap; private final Object lock = new Object(); private volatile boolean ready = true; ArrayList<Integer> images = new ArrayList<Integer>(); int position = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); images.add(R.drawable.black); images.add(R.drawable.blue); images.add(R.drawable.green); images.add(R.drawable.grey); images.add(R.drawable.orange); images.add(R.drawable.pink); images.add(R.drawable.red); images.add(R.drawable.white); images.add(R.drawable.yellow); final MyImageView imageView = (MyImageView) findViewById(R.id.image); imageView.setOnDrawFinishedListener(new OnDrawFinishedListener() { @Override public void onOnDrawFinish() { /* * The ImageView has finished its drawing, now we can recycle * the bitmap and use the new one for the next drawing */ mRecycledBitmap = mShowingBitmap; mShowingBitmap = null; synchronized (lock) { ready = true; lock.notifyAll(); } } }); final Button goButton = (Button) findViewById(R.id.button); goButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Runnable runnable = new Runnable() { @Override public void run() { while (true) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 1; if (mDecodingBitmap != null) { options.inBitmap = mDecodingBitmap; } mDecodingBitmap = BitmapFactory.decodeResource( getResources(), images.get(position), options); /* * If you want the images display in order and none * of them is bypassed then you should stay here and * wait until the ImageView finishes displaying the * last bitmap, if not, remove synchronized block. * * It's better if we put the lock here (after the * decoding is done) so that the image is ready to * pass to the ImageView when this thread resume. */ synchronized (lock) { while (!ready) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } ready = false; } if (mShowingBitmap == null) { mShowingBitmap = mDecodingBitmap; mDecodingBitmap = mRecycledBitmap; } runOnUiThread(new Runnable() { @Override public void run() { if (mShowingBitmap != null) { imageView .setImageBitmap(mShowingBitmap); /* * At this point, nothing has been drawn * yet, only passing the data to the * ImageView and trigger the view to * invalidate */ } } }); try { Thread.sleep(5); } catch (InterruptedException e) { } position++; if (position >= images.size()) position = 0; } } }; Thread t = new Thread(runnable); t.start(); } }); } } 

Вам нужно сделать следующее, чтобы избавиться от этой проблемы.

  1. Добавьте дополнительный битмап, чтобы предотвратить ситуации, когда поток ui рисует растровое изображение, а другой поток его модифицирует.
  2. Внедрить синхронизацию потоков для предотвращения ситуаций, когда фоновый поток пытается декодировать новый растровый рисунок, но предыдущий не был показан нитью ui.

Я немного изменил ваш код, и теперь он отлично работает для меня.

 package com.example.TearingExample; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ImageView; import java.util.ArrayList; public class MainActivity extends Activity { ArrayList<Integer> images = new ArrayList<Integer>(); private Bitmap[] buffers = new Bitmap[2]; private volatile Bitmap current; private final Object lock = new Object(); private volatile boolean ready = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); images.add(R.drawable.black); images.add(R.drawable.blue); images.add(R.drawable.green); images.add(R.drawable.grey); images.add(R.drawable.orange); images.add(R.drawable.pink); images.add(R.drawable.red); images.add(R.drawable.white); images.add(R.drawable.yellow); final ImageView imageView = (ImageView) findViewById(R.id.image); final Button goButton = (Button) findViewById(R.id.button); goButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Runnable runnable = new Runnable() { @Override public void run() { int position = 0; int index = 0; while (true) { try { synchronized (lock) { while (!ready) { lock.wait(); } ready = false; } BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 1; options.inBitmap = buffers[index]; buffers[index] = BitmapFactory.decodeResource(getResources(), images.get(position), options); current = buffers[index]; runOnUiThread(new Runnable() { @Override public void run() { imageView.setImageBitmap(current); synchronized (lock) { ready = true; lock.notifyAll(); } } }); position = (position + 1) % images.size(); index = (index + 1) % buffers.length; Thread.sleep(5); } catch (InterruptedException ignore) { } } } }; Thread t = new Thread(runnable); t.start(); } }); } } 

В BM.decode (ресурс … есть сеть?

Если да, то вам нужно оптимизировать перспективное соединение и передачу данных по сетевому соединению, а также оптимизировать растровые изображения и память работы. Это может означать, что вы будете искусны при низкой задержке или асинхронном транспорте, используя протокол подключения (http i guess). Убедитесь, что вы не переносите больше данных, чем вам нужно? Растровое декодирование может часто отбрасывать 80% пикселей при создании оптимизированного объекта для заполнения локального представления.

Если данные, предназначенные для растровых изображений, уже являются локальными, и нет проблем с задержкой в ​​сети, просто сосредоточьтесь на резервировании типа коллекции DStructure (listArray), чтобы удерживать фрагменты, которые пользовательский интерфейс будет обменивать на событиях пересылки страницы, обратной страницы.

Если ваши jpegs (pngs без потерь с растровыми операциями IMO) составляют около 100 тыс. Каждый, вы можете просто использовать STD-адаптер для загрузки их на фрагменты. Если они намного больше, вам нужно будет разобраться с растровым «компрессионным» вариантом для использования с декодированием, чтобы не тратить много памяти на структуру данных фрагмента.

Если вам нужен aadpool, чтобы оптимизировать создание растрового изображения, тогда сделайте это, чтобы удалить любую задержку, участвующую в этом шаге.

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

IMO – после того, как у вас есть структура, переключение транзакций между фрагментами по мере того, как вы должны быть очень быстрыми. У меня есть непосредственный опыт работы с примерно 6 фотографиями в памяти, каждый размером около 200 тыс., И быстрый поиск на странице-fwd, обратная связь с страницами.

Я использовал это приложение в качестве рамки, сосредоточившись на примере «page-viewer».

Это связано с кэшированием изображений, обработкой asycTask, загрузкой фона из сети и т. Д. Пожалуйста, прочитайте эту страницу: http://developer.android.com/training/displaying-bitmaps/index.html

Если вы загружаете и просматриваете образец проекта bitmapfun на этой странице, я надеюсь, что он решит всю вашу проблему. Это идеальный образец.