毕加索可以为我排队吗?

14

这是关于毕加索行为的一个我不了解的关键点。

想象一下,您正在展示10个幻灯片。假设每个幻灯片在屏幕上停留10秒钟。

理想情况下的行为应该是这样的:在幻灯片演示开始时,我只需要执行以下操作:

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 )

实际上,毕加索会一个接一个地做

如果我让它预先加载10个url,毕加索的行为是什么?

是否有可能使毕加索只按顺序一个接一个地执行,是否有这样的选项?

(引发的其他问题是,您是否可以取消队列等?)


Fresco

感谢此页面上@alicanozkara的精彩回答,我第一次了解到

https://github.com/facebook/fresco

(13k星)不管好坏,我认为毕加索时代可能已经结束了。


我不是皮卡索专家,但我在想,难道定时器不能解决你的问题吗? https://developer.android.com/reference/java/util/Timer.html - sabsab
嘿@sabsab,不,那与问题并不相关。 - Fattie
2
我觉得仅使用 Picasso 是不可能实现的,但下面 RxJava 的解决方案是解决您问题的好方法。 - Bobbake4
4个回答

6

有没有可能让Picasso按顺序逐一执行操作-是否有这样的选项?

我不确定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中的输出结果:

enter image description here

这是理想情况下的输出,每个图像都成功加载。但是,这段代码无法处理一个图像无法加载的情况。一旦Picasso无法加载其中的一个图像 - 流将被中断,并调用onError()


虽然这非常令人钦佩,值得获得1000票,但我认为下面的 Gut 已经在 Picasso 内给出了答案。 - Fattie
@Fattie,非常接近真相,除了时间方面,我在评论中已经提到了。 - azizbekian
.flatMap replace to .concatMap - NickUnuchek

5

如果我没记错的话,这就是答案! - Fattie
1
我已经测试了这个,并得到了这个的输出:顺序被保留,但时间很有趣:有4个资源在完全相同的时间下载,这是不可能的。看起来有一个窗口,在这个窗口中,resourceN与resourceN-1同时被下载。 - azizbekian
1
@Fattie,我在我的回答中已经提到了ExecutorService部分! - Sarthak Mittal
1
这在更大的数据集上看起来甚至更奇怪 - 见此处,而使用rx方法可以得到有意义的输出 - 见此处 - azizbekian
1
@Fattie 谢谢!:D 我在实际项目中使用过几乎所有著名的图像加载库。Fresco很好,但它有最多的方法约14k,Picasso是最轻量级的,而且它的工作非常整洁!我建议您先定义您的用例,然后再决定使用哪个库!:) 如果您有任何想要讨论的内容,请随意提出!:) - Sarthak Mittal
显示剩余5条评论

3

使用 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) 如果您要按顺序将图像加载到队列中,可以使用Picasso.Builder中提供的executor(ExecutorService)方法,并传递您自己的ExecutorService(在此情况下是SingleThreadExecutor)。

您还可以使用Picasso.Builder类中的downloader(Downloader)方法更改磁盘缓存的大小,以及使用memoryCache(Cache)方法更改内存缓存的大小。

其他出色的库:

Glide

Fresco


1
看起来是一个非常棒的答案,值得一百万个赞。 - Fattie
@Fattie 顺便说一句,你也应该看看 Glide 库!大多数在 IO 会议上展示的 Google 应用程序都在使用它! :) - Sarthak Mittal
@Fattie,您能否将其标记为正确,以帮助未来的读者。 - Sarthak Mittal

-1

使用 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清除缓存的时候凭记忆写下这段话的,所以你可能需要稍作调整。

你有多确定 Picasso.with(iv.getContext()).load(urls[index]).into(iv) 会比下一个 displayNextSlide() 更快成功? - azizbekian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接