Пикассо может встать в очередь за мной?

Вот критический момент, которого я не знаю о поведении Пикассо.

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

Идеальным поведением было бы следующее: в начале слайд-шоу я просто выполняю следующее:

picasso.get( url1 )
picasso.get( url2 )
picasso.get( url3 )
picasso.get( url4 )
picasso.get( url5 )
picasso.get( url6 )
picasso.get( url7 )
picasso.get( url8 )
picasso.get( url9 )
picasso.get( url10 )

И на самом деле Пикассо делал их по одному, в очереди.

Как поведет себя Picasso, если я скажу ему предварительно разогреть 10 URL сразу?

Можно ли, чтобы Пикассо делал вещи только по одному, по порядку - есть ли такой вариант?

(Другие возникающие вопросы: можете ли вы отменить очередь или ...?)


фреска

благодаря потрясающему ответу @alicanozkara на этой странице я впервые узнал о

https://github.com/facebook/fresco

(13 тысяч звезд), хорошо это или плохо, я думаю, что эпоха Пикассо, вероятно, закончилась.


person Fattie    schedule 09.09.2017    source источник
comment
Я не эксперт по Пикассо, но я блуждаю. Таймер не может решить вашу проблему? developer.android.com/reference/java/util/Timer.html   -  person sabsab    schedule 11.09.2017
comment
эй @sabsab, нет, это не совсем относится к делу   -  person Fattie    schedule 12.09.2017
comment
Я не думаю, что это возможно только с использованием Picasso, но приведенный ниже ответ RxJava - хороший подход к решению вашей проблемы.   -  person Bobbake4    schedule 12.09.2017


Ответы (4)


Используя только Picasso, я думаю, вы можете добиться:

1) Загрузите все изображения асинхронно в кэш, используя fetch() следующим образом:

Picasso.with(context).load(URL).fetch();

Вы также можете добавить приоритет для изображений, которые вы хотите загрузить раньше: (можно упомянуть высокий приоритет для первых нескольких изображений слайда)

Picasso.with(context)
.load(URL)
.priority(Picasso.Priority.HIGH) // Default priority is medium
.fetch();

2) Что касается отмены очереди, вы можете добавить общий tag() к своим изображениям, и вы можете приостановить/отменить/возобновить в любое время!

private static final Object TAG_OBJECT = Object();

Picasso.with(context)
.load(URL)
.tag(TAG_OBJECT) 
// can be any Java object, must be the same object for all requests you want to control together.

Затем мы можем управлять тегом следующим образом:

Picasso.with(context)
.pauseTag(TAG_OBJECT)
//.resumeTag(TAG_OBJECT)
//.cancelTag(TAG_OBJECT)

3) Еще одна важная вещь, которую я хотел бы предложить, это когда вы предварительно загружаете свои изображения, сохраняйте их только в кэш-память на диске и загружайте их в кэш-память только во время отображения. Это предотвратит сброс других важных изображений из кеша памяти:

Picasso  
.with(context)
.load(URL)
.memoryPolicy(MemoryPolicy.NO_STORE) //Skips storing the final result into memory cache.
.fetch()

4) Для последовательной загрузки ваших изображений в очередь вы можете передать свой собственный ExecutorService (в вашем случае SingleThreadExecutor) с помощью метода executor(ExecutorService), присутствующего в Picasso.Builder

Вы даже можете изменить размер кэша диска, используя метод downloader(Downloader), и кэш памяти, используя метод memoryCache(Cache), которые находятся в классе Picasso.Builder.

Другие замечательные библиотеки:

Скольжение

Fresco

person Sarthak Mittal    schedule 16.09.2017
comment
Похоже, отличный ответ, который заслуживает одного миллиона голосов - person Fattie; 18.09.2017
comment
@Fattie кстати, тебе тоже стоит взглянуть на библиотеку glide! Большинство приложений Google, представленных на IO-сессиях, используют его! :) - person Sarthak Mittal; 18.09.2017
comment
@Fattie, не могли бы вы также отметить это как правильное, чтобы помочь будущим читателям. - person Sarthak Mittal; 19.09.2017

Можно ли, чтобы Пикассо делал вещи только по одному, по порядку - есть ли такой вариант?

Я не уверен, что это можно сделать с самим Picasso, но, по крайней мере, RxJava может быть применим к этой проблеме.

Я опубликую фрагмент кода с комментариями:

public class MainActivity extends AppCompatActivity {

    public static final List<String> urlList = Arrays.asList(
            "http://i.imgur.com/UZFOMzL.jpg",
            "http://i.imgur.com/H981AN7.jpg",
            "http://i.imgur.com/nwhnRsZ.jpg",
            "http://i.imgur.com/MU2dD8E.jpg"
    );

    List<Target> targetList = new ArrayList<>();
    List<Completable> completables = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final long start = System.currentTimeMillis();
        // emit each url separately
        Observable.fromIterable(urlList)
                // flatmap to an Observable<Completable>
                .flatMap(url ->
                        // fromCallable ensures that this stream will emit value as soon as it is subscribed
                        // Contrary to this, Observable.just() would emit immediately, which we do not want
                        Observable.fromCallable(() ->
                                // We need to know whether either download is
                                // completed or no, thus we need a Completable
                                Completable.create(e -> {
                                    Target target = new Target() {
                                        @Override
                                        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                                            Log.i("vvv", "downloaded " + url + ", " + (System.currentTimeMillis() - start));
                                            e.onComplete();
                                        }

                                        @Override
                                        public void onBitmapFailed(Drawable errorDrawable) {
                                            e.onError(new IllegalArgumentException("error happened"));
                                        }

                                        @Override
                                        public void onPrepareLoad(Drawable placeHolderDrawable) {

                                        }
                                    };
                                    // need to keep a strong reference to Target, because Picasso holds weak reference
                                    targetList.add(target);
                                    Picasso.with(MainActivity.this)
                                            .load(url)
                                            .into(target);
                                })))
                // collecting all Completables into a list
                .collectInto(completables, List::add)
                // flatmap-ing this Observable into a Completable, concatenating each completable
                // to next, thus they will be downloaded in order
                .flatMapCompletable(Completable::concat)
                // clearing the strong reference we retained earlier
                .doFinally(() -> {
                    targetList.clear();
                    targetList = null;
                })
                .subscribe(
                        () -> Log.i("vvv", "done: " + (System.currentTimeMillis() - start)),
                        throwable -> Log.e("vvv", "err " + throwable.getMessage()
                        ));
    }

}

Это будет вывод в logcat:

введите здесь описание изображения

Это идеальный сценарий, когда каждое изображение успешно загружается. Этот фрагмент не обрабатывает случай, когда одно из изображений не может быть загружено. Как только Picasso не сможет загрузить один из них - поток будет прерван и будет вызван onError().

person azizbekian    schedule 12.09.2017
comment
хотя это очень достойно восхищения и заслуживает 1000 голосов, я полагаю, что Гут ниже дал ответ внутри Пикассо? - person Fattie; 18.09.2017
comment
@Fattie, довольно близко к истине, ожидайте аспекта времени, о котором я упоминал в комментарий. - person azizbekian; 18.09.2017
comment
.flatMap заменить на .concatMap - person NickUnuchek; 18.11.2019

В Picasso.Builder вы можете указать конкретный ExecutorService: https://square.github.io/picasso/2.x/picasso/com/squareup/picasso/Picasso.Builder.html#executor-java.util.concurrent.ExecutorService-

Если вы укажете новый исполнитель одного потока, https://developer.android.com/reference/java/util/concurrent/Executors.html#newSingleThreadExecutor() Picasso будет загружать все ваши изображения по одному.

person Gut    schedule 18.09.2017
comment
Если я не ошибаюсь, это ответ! - person Fattie; 18.09.2017
comment
Я проверил это и получил этот вывод: порядок сохранен, но интересно время: есть 4 ресурса, которые загружаются в одно и то же время, что невозможно. Вроде есть окно, где ресурс N скачивается одновременно с ресурсом N-1. - person azizbekian; 18.09.2017
comment
@Fattie Я уже упоминал часть ExecutorService в своем ответе! - person Sarthak Mittal; 18.09.2017
comment
Это выглядит даже странно на большом наборе данных — см. здесь, где подход rx дает осмысленный результат — см. здесь. - person azizbekian; 18.09.2017
comment
@SarthakMittal ваш ответ потрясающий! - person Fattie; 18.09.2017
comment
это бесценное испытание @azizbekian - person Fattie; 18.09.2017
comment
одна проблема @SarthakMittal и azizbekian, похоже, теперь очень мало причин использовать Пикассо (который был королем в течение 10 лет) теперь, когда Fresco здесь: O - person Fattie; 18.09.2017
comment
@Fattie Спасибо! :D Я использовал почти все известные библиотеки загрузки изображений в живых проектах. Fresco хорош, но у него больше всего методов ~14k, Picasso самый легкий и делает свою работу очень аккуратно! Я бы порекомендовал вам сначала определить свой вариант использования, а затем решить, какую библиотеку использовать! :) не стесняйтесь обсуждать все, что вы хотели бы! :) - person Sarthak Mittal; 18.09.2017
comment
Привет @SarthakMittal - это здорово; да, абсолютная проблема, с которой я столкнулся, это очередь. Итак, представьте слайд-шоу из 20 изображений. Скажем, каждое изображение показывается в течение 5 секунд. Конечно, идеальным было бы следующее поведение: как только пользователь доходит до определенного слайд-шоу, он начинает загружать их по одному, по порядку. если пользователь покидает это слайд-шоу, в идеале вы должны иметь возможность очистить очередь. Основываясь на вашем прекрасном опыте, какой из трех вы считаете лучшим!?!? невероятное спасибо - person Fattie; 18.09.2017
comment
@Fattie Правда в том, что для этого вы можете использовать любую библиотеку, но, поскольку picasso самая легкая, я бы порекомендовал ее. Если вы действительно хотите использовать какую-то другую библиотеку, я бы порекомендовал использовать Glide, а не Fresco (Fresco тоже неплоха, но у них другой подход и они могут не подходить для всех нужд). вот очень хороший пост, показывающий различия ч/б Пикассо и Глайда: inthecheesefactory.com/blog/ - person Sarthak Mittal; 18.09.2017

Не существует исправления вызова одного метода для цепочки с Picasso, но вы можете создать помощник следующим образом:

public PicassoSlideshow {

    private static PicassoSlideshow instance;
    private WeakReference<ImageView> view;
    private Handler handler;
    private int index;
    private String[] urls;
    private long delay;

    private PicassoSlideshow() {
       //nothing
    }

    public static PicassoSlideshow with(ImageView view) {
        if (instance == null) {
            instance = new PicassoSlideshow();
        }
        instance.setView(view);
    }

    private void setView(ImageView view) {
        this.view = new WeakReference<>(view);
    }

    //Note: I'm only suggesting varargs because that's what you seem to have in the question  
    public void startSlideshow(long interval, String... urls) {
        if (handler == null) {
            handler = new Handler();
        }
        index = 0;
        this.urls = urls;
        delay = interval;
        displayNextSlide();
    }

    private void displayNextSlide() {
        //display one 
        ImageView iv = view.get();
        if (iv != null) {
            Picasso.with(iv.getContext())
                   .load(urls[index]).into(iv);
            index++;
            if (index < urls.length) {
                //preload next
                Picasso.with(iv.getContext()).fetch(urls[index]); 
                //on timer switch images
                handler.postDelayed(PicassoSlideshow::displayNextSlide, delay); 
            }
        }
    }

}

Применение:

PicassoSlideshow.with(view).startSlideshow(10000, url1, url2, url3, url9);

Обратите внимание, я только что написал это из головы, в то время как моя IDE аннулирует свои кэши, поэтому вам, возможно, придется немного изменить это

person Nick Cardoso    schedule 18.09.2017
comment
Насколько вы уверены, что Picasso.with(iv.getContext()).load(urls[index]).into(iv) добьется успеха раньше, чем следующий displayNextSlide()? - person azizbekian; 18.09.2017