Подтвердить что ты не робот

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, однако мы, вероятно, уже расшифровываем следующий битмап, когда это происходит, и поэтому мы начали изменять растровые пиксели. Думаю ли я в правильном направлении?

У кого-нибудь есть какие-то советы о том, куда идти отсюда?

4b9b3361

Ответ 1

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

Ответ 2

Вы должны использовать метод 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 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();
            }
        });

    }
}

Ответ 3

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

  • Добавьте дополнительное растровое изображение, чтобы предотвратить ситуации, когда нить 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();
            }
        });
    }
}

Ответ 4

В BM.decode(ресурс... связана ли сеть?

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

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

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

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

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

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

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

Ответ 5

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

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