简体   繁体   中英

Picasso cache issues with AMAZON S3

Has anyone been able to solve the caching issue when loading images from AWS S3 ?

I'm using OKHttpClient and all that Jazz - but it looks like the cacheControl or whatever is not checking changes in ETag and LAST_MODIFIED headers. It is always taking the cached version.

I init Picasso like so:

OkHttpClient client = new OkHttpClient();
Picasso.Builder builder = new Picasso.Builder(appContext).downloader(new OkHttpDownloader(client));
Picasso built = builder.build();
Picasso.setSingletonInstance(built);

And the response headers look like so:

D/OkHttpDownloader: x-amz-id-2: gWhBHQvzDw1RFDteg8uxvq02XfaB6SySgMgKMk45x2E6An245Bl0OjgYgKsHMxzkVm2nF4GPwRI=<br/>
D/OkHttpDownloader: x-amz-request-id: D80E5602FF3E6002
D/OkHttpDownloader: Date: Tue, 17 Nov 2015 14:46:02 GMT
D/OkHttpDownloader: Last-Modified: Sun, 01 Nov 2015 15:39:41 GMT
D/OkHttpDownloader: ETag: "2a082d4f2d42de95cc1b673a7742fec3"
D/OkHttpDownloader: Accept-Ranges: bytes
D/OkHttpDownloader: Content-Type: image/png
D/OkHttpDownloader: Content-Length: 798624
D/OkHttpDownloader: Server: AmazonS3
D/OkHttpDownloader: OkHttp-Selected-Protocol: http/1.1
D/OkHttpDownloader: OkHttp-Sent-Millis: 1447771561871
D/OkHttpDownloader: OkHttp-Received-Millis: 1447771562179

Edit:

Ok. After adding a cache control header on the put object request ("must-revalidate" was my weapon of choice) it partially solved the issue.

In order to force the running application to update a replaced image I had to accompany that with the following:

// Remove from memory cache
Picasso.with(appContext).invalidate(imageUrl);
// Remove from disc cache
Iterator<String> it = okHttpClient.getCache().urls();
while(it.hasNext()){
  String currentUrl = it.next();

  if(currentUrl.equals(imageUrl)){
    it.remove();                        
    break;
  }
}
// Update cache with new image
Picasso.with(appContext).load(imageUrl).fetch();

Expires: or Cache-Control: headers are notably absent from your response, which suggests that when you uploaded the files to S3, you didn't set them -- so you are at the whim of the viewer for caching behavior.

Setting Cache-Control: max-age=3600 (or whatever lifetime you want) when you upload to S3 will result in these headers being returned with the object when it is downloaded. Or, you can go back, in the S3 console, and set them after you upload the object. You can also programmatically add them to already-uploaded objects by sending an API call to S3 to copy an object onto itself, with modified metadata.

After a few hours (maybe days) of trying to figure out how to cache images in Picasso, and not wanting to spin up my own solution, I simply switched libraries to Facebook's Fresco. They use a very interesting strategy where you replace your ImageViews with SimpleDraweeViews. All you have to do is set the URI on the DraweeView and Fresco will load the image into the view as soon as it is ready. It will also automatically cache the image for you. I'd highly recommend trying it out.

Here's the Quickstart guide: http://frescolib.org/docs/index.html#_

I'll paste my code below that gets everything working:

build.gradle :

dependencies {
  // your app's other dependencies
  compile 'com.facebook.fresco:fresco:0.7.0+'
}

Layout XML:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            xmlns:fresco="http://schemas.android.com/apk/res-auto"
            android:orientation="vertical"
            android:layout_margin="@dimen/margin_channel_item">

<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/iv_channel_image"
    android:scaleType="centerCrop"
    fresco:placeholderImage="@drawable/tct_banner"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</RelativeLayout>

Java Code:

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {

    // Get the current Channel
    final TctOnDemandChannel currentChannel = mODChannels.get(position);

    holder.channelImageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ((TctCoreApplication)mContext.getApplicationContext()).trackChannelClick(currentChannel);

            ActivityEpisodesView.startWithChannelExtra(currentChannel, mContext);
        }
    });

    // TODO: Handle errors by grabbing image from Internet
    Uri uri = Uri.parse(currentChannel.getImageUrl());
    holder.channelImageView.setImageURI(uri);
}

public class ViewHolder extends RecyclerView.ViewHolder {

    SimpleDraweeView channelImageView;

    public ViewHolder(View itemView) {
        super(itemView);
        channelImageView = (SimpleDraweeView) itemView.findViewById(R.id.iv_channel_image);
    }
}

On quick scrolling around before all the images load initially, there is a short time where you can still see the placeholder image. But once everything loads initially, the scrolling is flawless (YMMV) and images are cached exactly as I would expect them to be without me having to configure anything.

Let me know how that works for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM