ฉันจะใช้การแคชดิสก์ใน Picasso ได้อย่างไร


119

ฉันใช้ Picasso เพื่อแสดงภาพในแอป Android ของฉัน:

/**
* load image.This is within a activity so this context is activity
*/
public void loadImage (){
    Picasso picasso = Picasso.with(this); 
    picasso.setDebugging(true);
    picasso.load(quiz.getImageUrl()).into(quizImage);
}

ฉันเปิดใช้งานการดีบักและมันจะแสดงเป็นสีแดงและสีเขียวเสมอ แต่ไม่เคยแสดงสีเหลือง

ตอนนี้ถ้าฉันโหลดภาพเดียวกันในครั้งต่อไปและไม่มีอินเทอร์เน็ตรูปภาพจะไม่โหลด

คำถาม:

  1. ไม่มีแคชดิสก์ในเครื่องหรือไม่?
  2. ฉันจะเปิดใช้งานการแคชดิสก์ได้อย่างไรเนื่องจากฉันจะใช้ภาพเดียวกันหลาย ๆ ครั้ง
  3. ฉันต้องเพิ่มสิทธิ์ดิสก์ในไฟล์รายการของ Android หรือไม่?

ฉันมีปัญหาเดียวกัน มันจะไม่แคช!
Jonathan

พวกคุณควรดูที่ Facebook ของ Fresco lib การจัดการแคชนั้นยอดเยี่ยมมาก
Michel Fortes

คำตอบ:


229

นี่คือสิ่งที่ฉันทำ ทำได้ดี.

ขั้นแรกให้เพิ่ม OkHttp ลงในไฟล์สร้าง gradle ของโมดูลแอป:

compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.10.0'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'

จากนั้นทำการขยายชั้นเรียน Application

import android.app.Application;

import com.jakewharton.picasso.OkHttp3Downloader;
import com.squareup.picasso.Picasso;

public class Global extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        Picasso.Builder builder = new Picasso.Builder(this);
        builder.downloader(new OkHttp3Downloader(this,Integer.MAX_VALUE));
        Picasso built = builder.build();
        built.setIndicatorsEnabled(true);
        built.setLoggingEnabled(true);
        Picasso.setSingletonInstance(built);

    }
}

เพิ่มลงในไฟล์ Manifest ดังนี้:

<application
        android:name=".Global"
        .. >

</application>

ตอนนี้ใช้ Picasso ตามปกติ ไม่มีการเปลี่ยนแปลง.

แก้ไข:

หากคุณต้องการใช้รูปภาพแคชเท่านั้น เรียกห้องสมุดแบบนี้ ฉันสังเกตเห็นว่าหากเราไม่เพิ่ม networkPolicy รูปภาพจะไม่ปรากฏในการเริ่มต้นออฟไลน์โดยสมบูรณ์แม้ว่าจะถูกแคชก็ตาม รหัสด้านล่างช่วยแก้ปัญหาได้

Picasso.with(this)
            .load(url)
            .networkPolicy(NetworkPolicy.OFFLINE)
            .into(imageView);

แก้ไข # 2

ปัญหาเกี่ยวกับรหัสด้านบนคือถ้าคุณล้างแคช Picasso จะค้นหามันแบบออฟไลน์ในแคชต่อไปและล้มเหลวตัวอย่างโค้ดต่อไปนี้จะดูที่แคชในเครื่องหากไม่พบแบบออฟไลน์มันจะออนไลน์และเติมแคช

Picasso.with(getActivity())
.load(imageUrl)
.networkPolicy(NetworkPolicy.OFFLINE)
.into(imageView, new Callback() {
    @Override
    public void onSuccess() {

    }

    @Override
    public void onError() {
        //Try again online if cache failed
        Picasso.with(getActivity())
                .load(posts.get(position).getImageUrl())
                .error(R.drawable.header)
                .into(imageView, new Callback() {
            @Override
            public void onSuccess() {

            }

            @Override
            public void onError() {
                Log.v("Picasso","Could not fetch image");
            }
        });
    }
});

@ArtjomB ฉันตอบคำถาม และวิธีแก้ปัญหาได้ผล แต่มีคำชี้แจงเล็กน้อยที่ฉันสามารถใช้ได้ ฉันอ่านเอกสาร OkHttp และพวกเขาไม่ได้พูดถึงหน่วยของ "แคช" ดังนั้นหากใครต้องการแบ่งปันภูมิปัญญา ... นี่เป็นโอกาสดี
Sanket Berde

@ArtjomB ใช่มันสมเหตุสมผล แก้ไขแล้ว!
Sanket Berde

5
@SanketBerde: ขอบคุณสำหรับบันทึกย่อ แต่ฉันคิดว่าภาพนั้นมาจากหน่วยความจำก็ต่อเมื่อแอปทำงานในพื้นหลัง (เมื่อออฟไลน์) ถ้าฉันปิดแอพนั่นคือการล้างแอพที่กำลังทำงานอยู่จากนั้นเปิดแอพของฉันอีกครั้งรูปภาพไม่โหลดจากแคช ฉันได้ตั้งค่าข้อผิดพลาดเริ่มต้นในการโหลดรูปภาพที่กำลังจะเกิดขึ้น มีอะไรผิดปกติที่นี่?
TheDevMan

1
บางที Picasso อาจเปลี่ยนวิธีการทำงานของสิ่งต่าง ๆ เพราะสำหรับฉันมันทำงานได้ดีหากไม่มี okhttp และนโยบายเครือข่าย ในการเริ่มต้นใหม่มันจะรับภาพจากดิสก์ทันทีและเมื่อเครือข่ายออฟไลน์อยู่ก็ยังแสดงได้ดี
zeeshan

1
ด้วยokhttp3.OkHttpClientห้องสมุดคุณต้องใช้OkHttp3Downloaderแบบฟอร์มชั้นเรียน compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
Chuck

46

1)คำตอบของคำถามแรก: ตามวิธีPicasso Doc for With ()

อินสแตนซ์ Picasso เริ่มต้นส่วนกลางที่ส่งคืนจากด้วย () จะเริ่มต้นโดยอัตโนมัติด้วยค่าเริ่มต้นที่เหมาะสมกับการใช้งานส่วนใหญ่

  • แคชหน่วยความจำ LRU 15% ของ RAM แอปพลิเคชันที่มีอยู่
  • ดิสก์แคชของพื้นที่จัดเก็บ 2% สูงสุด 50MB แต่ไม่น้อยกว่า 5MB

แต่ Disk Cacheการดำเนินการสำหรับ Picasso เริ่มต้นทั่วโลกจะพร้อมใช้งานบน API 14+ เท่านั้น

2)คำตอบของคำถามที่สอง: Picassoใช้HTTPคำขอของลูกค้าในDisk Cacheการดำเนินการดังนั้นคุณสามารถสร้างhttp request headerคุณสมบัติของคุณเองCache-Controlด้วยmax-age และสร้างอินสแตนซ์ Picasso แบบคงที่ของคุณเองแทน Picasso เริ่มต้นโดยใช้

1] HttpResponseCache (หมายเหตุ: ใช้ได้เฉพาะกับ API 13+)
2] OkHttpClient (ใช้ได้กับ API ทั้งหมด)

ตัวอย่างสำหรับใช้OkHttpClientสร้างคลาส Picasso แบบคงที่ของคุณเอง:

  • ขั้นแรกให้สร้างคลาสใหม่เพื่อรับpicassoวัตถุเดี่ยวของคุณเอง

    import android.content.Context;
    import com.squareup.picasso.Downloader;
    import com.squareup.picasso.OkHttpDownloader;
    import com.squareup.picasso.Picasso;
    
    public class PicassoCache {
    
        /**
         * Static Picasso Instance
         */
        private static Picasso picassoInstance = null;
    
        /**
         * PicassoCache Constructor
         *
         * @param context application Context
         */
        private PicassoCache (Context context) {
    
            Downloader downloader   = new OkHttpDownloader(context, Integer.MAX_VALUE);
            Picasso.Builder builder = new Picasso.Builder(context);
                builder.downloader(downloader);
    
            picassoInstance = builder.build();
        }
    
        /**
         * Get Singleton Picasso Instance
         *
         * @param context application Context
         * @return Picasso instance
         */
        public static Picasso getPicassoInstance (Context context) {
    
            if (picassoInstance == null) {
    
                new PicassoCache(context);
                return picassoInstance;
            }
    
            return picassoInstance;
        }
    
    } 
  • ใช้picassoวัตถุเดี่ยวของคุณเองแทนPicasso.With()

PicassoCache.getPicassoInstance(getContext()).load(imagePath).into(imageView)

3)คำตอบสำหรับคำถามที่สาม: คุณไม่จำเป็นต้องมีสิทธิ์ดิสก์ใด ๆ สำหรับการดำเนินการแคชดิสก์

ข้อมูลอ้างอิง : ปัญหา Github เกี่ยวกับดิสก์แคชคำถามสองข้อได้รับคำตอบโดย@ jake-wharton -> Question1และQuestion2


4
ไม่วิธีนี้ใช้ไม่ได้หากปิดแอป หลังจากที่แอปถูกบังคับให้หยุดภาพทั้งหมดหายไป
nbumakov

2
นี่ทำให้ฉันเกิดข้อผิดพลาด:FATAL EXCEPTION: main java.lang.NoClassDefFoundError: com.squareup.okhttp.OkHttpClient
CIRCLE

@CIRCLE ขออภัยที่ล่าช้าในการใช้ตัวอย่างคุณต้องดาวน์โหลดแพ็กเกจ[okhttp] ( square.github.io/okhttp ) และแพ็กเกจ [okio] ( github.com/square/okio ) ซึ่งใช้โดยokhttp
ahmed hamdy

@CIRCLE คุณอาจต้องดาวน์โหลด [okhttp-urlconnection] ( mvnrepository.com/artifact/com.squareup.okhttp/… ) แพ็คเกจด้วย
ahmed hamdy

นี่ใช้ไม่ได้สำหรับฉัน มีการโหลดรูปภาพใหม่ทุกครั้งเมื่อฉันเลื่อนไปที่ตำแหน่งใน viewPager
Charu

21

สำหรับการแคชฉันจะใช้ OkHttp interceptorsเพื่อควบคุมนโยบายการแคช ลองดูตัวอย่างนี้ที่รวมอยู่ในไลบรารี OkHttp

RewriteResponseCacheControl.java

นี่คือวิธีที่ฉันใช้กับ Picasso -

OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.networkInterceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Response originalResponse = chain.proceed(chain.request());
            return originalResponse.newBuilder().header("Cache-Control", "max-age=" + (60 * 60 * 24 * 365)).build();
        }
    });

    okHttpClient.setCache(new Cache(mainActivity.getCacheDir(), Integer.MAX_VALUE));
    OkHttpDownloader okHttpDownloader = new OkHttpDownloader(okHttpClient);
    Picasso picasso = new Picasso.Builder(mainActivity).downloader(okHttpDownloader).build();
    picasso.load(imageURL).into(viewHolder.image);

1
ไม่ทำงานอีกต่อไป networkInterceptors () ส่งคืนรายการที่ไม่เปลี่ยนรูป
noev

1
@noev ใน OkHttp 3.x คุณสามารถใช้รูปแบบตัวสร้าง (ดูgithub.com/square/okhttp/wiki/Interceptors ) เพื่อเพิ่มตัวสกัดกั้น
Gaurav B

10

สำหรับเวอร์ชันล่าสุด 2.71828 นี่คือคำตอบของคุณ

Q1 : ไม่มีดิสก์แคชภายในเครื่องหรือไม่?

A1 : มีการแคชเริ่มต้นภายใน Picasso และขั้นตอนการร้องขอเช่นนี้

App -> Memory -> Disk -> Server

เมื่อใดก็ตามที่พวกเขาพบรูปภาพของพวกเขาก่อนพวกเขาจะใช้รูปภาพนั้นจากนั้นจึงหยุดขั้นตอนการร้องขอ กระแสตอบรับเป็นอย่างไร ไม่ต้องกังวลนี่คือ

Server -> Disk -> Memory -> App

ตามค่าเริ่มต้นพวกเขาจะจัดเก็บลงในดิสก์ภายในเครื่องก่อนสำหรับการเก็บรักษาแคชแบบขยาย จากนั้นหน่วยความจำสำหรับการใช้งานอินสแตนซ์ของแคช

คุณสามารถใช้ตัวบ่งชี้ในตัวใน Picasso เพื่อดูว่ารูปภาพอยู่ที่ใดโดยเปิดใช้งานสิ่งนี้

Picasso.get().setIndicatorEnabled(true);

จะปรากฏธงที่มุมบนซ้ายของรูปภาพของคุณ

  • ธงสีแดงหมายถึงภาพที่มาจากเซิร์ฟเวอร์ (ไม่มีการแคชในการโหลดครั้งแรก)
  • ธงสีน้ำเงินหมายถึงภาพถ่ายมาจากดิสก์ภายในเครื่อง (เก็บเอาไว้)
  • ธงสีเขียวหมายถึงภาพที่มาจากหน่วยความจำ (การแคชอินสแตนซ์)

Q2 : ฉันจะเปิดใช้งานการแคชดิสก์ได้อย่างไรเนื่องจากฉันจะใช้ภาพเดียวกันหลาย ๆ ครั้ง

A2 : คุณไม่จำเป็นต้องเปิดใช้งาน เป็นค่าเริ่มต้น

สิ่งที่คุณต้องทำคือปิดการใช้งานเมื่อคุณต้องการให้ภาพของคุณสดใหม่อยู่เสมอ มี 2-way of disabled caching.

  1. ตั้งค่า.memoryPolicy()เป็นNO_CACHEและ / หรือNO_STOREและโฟลว์จะมีลักษณะดังนี้

NO_CACHEจะข้ามการค้นหาภาพจากหน่วยความจำ

App -> Disk -> Server

NO_STOREจะข้ามการจัดเก็บภาพในหน่วยความจำเมื่อโหลดภาพครั้งแรก

Server -> Disk -> App
  1. ตั้งค่า.networkPolicy()เป็นNO_CACHEและ / หรือNO_STOREและโฟลว์จะมีลักษณะดังนี้

NO_CACHEจะข้ามการค้นหาภาพจากดิสก์

App -> Memory -> Server

NO_STOREจะข้ามการจัดเก็บภาพในดิสก์เมื่อโหลดภาพครั้งแรก

Server -> Memory -> App

คุณไม่สามารถปิดใช้งานได้หากไม่มีภาพแคช นี่คือตัวอย่าง

Picasso.get().load(imageUrl)
             .memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE)
             .networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)
             .fit().into(banner);

การไหลของการไม่มีแคชและไม่มีการจัดเก็บจะมีลักษณะเช่นนี้

App -> Server //Request

Server -> App //Response

ดังนั้นคุณอาจต้องใช้สิ่งนี้เพื่อลดการใช้พื้นที่เก็บข้อมูลแอปของคุณด้วย

Q3 : ฉันต้องเพิ่มการอนุญาตดิสก์ให้กับไฟล์รายการของ Android หรือไม่

A3 : ไม่ได้ แต่อย่าลืมเพิ่มสิทธิ์อินเทอร์เน็ตสำหรับคำขอ HTTP ของคุณ


6

1) โดยค่าเริ่มต้น Picasso มีแคช (ดูคำตอบของ ahmed hamdy)

2) หากคุณต้องใช้ภาพจากดิสก์แคชจริงๆจากนั้นเครือข่ายฉันขอแนะนำให้เขียนตัวดาวน์โหลดของคุณเอง:

public class OkHttpDownloaderDiskCacheFirst extends OkHttpDownloader {
    public OkHttpDownloaderDiskCacheFirst(OkHttpClient client) {
        super(client);
    }

    @Override
    public Response load(Uri uri, int networkPolicy) throws IOException {
        Response responseDiskCache = null;
        try {
            responseDiskCache = super.load(uri, 1 << 2); //NetworkPolicy.OFFLINE
        } catch (Exception ignored){} // ignore, handle null later

        if (responseDiskCache == null || responseDiskCache.getContentLength()<=0){
            return  super.load(uri, networkPolicy); //user normal policy
        } else {
            return responseDiskCache;
        }

    }
}

และใน Application singleton ใน method OnCreate ใช้กับ picasso:

        OkHttpClient okHttpClient = new OkHttpClient();

        okHttpClient.setCache(new Cache(getCacheDir(), 100 * 1024 * 1024)); //100 MB cache, use Integer.MAX_VALUE if it is too low
        OkHttpDownloader downloader = new OkHttpDownloaderDiskCacheFirst(okHttpClient); 

        Picasso.Builder builder = new Picasso.Builder(this);

        builder.downloader(downloader);

        Picasso built = builder.build();

        Picasso.setSingletonInstance(built);

3) ไม่จำเป็นต้องมีสิทธิ์สำหรับโฟลเดอร์แคชของแอปพลิเคชันเริ่มต้น


1

เพิ่มโค้ดติดตามApplication.onCreateแล้วใช้งานได้ตามปกติ

    Picasso picasso = new Picasso.Builder(context)
            .downloader(new OkHttp3Downloader(this,Integer.MAX_VALUE))
            .build();
    picasso.setIndicatorsEnabled(true);
    picasso.setLoggingEnabled(true);
    Picasso.setSingletonInstance(picasso);

หากคุณแคชรูปภาพก่อนให้ทำสิ่งนี้ใน ProductImageDownloader.doBackground

final Callback callback = new Callback() {
            @Override
            public void onSuccess() {
                downLatch.countDown();
                updateProgress();
            }

            @Override
            public void onError() {
                errorCount++;
                downLatch.countDown();
                updateProgress();
            }
        };
        Picasso.with(context).load(Constants.imagesUrl+productModel.getGalleryImage())
                .memoryPolicy(MemoryPolicy.NO_CACHE).fetch(callback);
        Picasso.with(context).load(Constants.imagesUrl+productModel.getLeftImage())
                .memoryPolicy(MemoryPolicy.NO_CACHE).fetch(callback);
        Picasso.with(context).load(Constants.imagesUrl+productModel.getRightImage())
                .memoryPolicy(MemoryPolicy.NO_CACHE).fetch(callback);

        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(errorCount == 0){
            products.remove(productModel);
            productModel.isDownloaded = true;
            productsDatasource.updateElseInsert(productModel);
        }else {
            //error occurred while downloading images for this product
            //ignore error for now
            // FIXME: 9/27/2017 handle error
            products.remove(productModel);

        }
        errorCount = 0;
        downLatch = new CountDownLatch(3);

        if(!products.isEmpty() /*&& testCount++ < 30*/){
            startDownloading(products.get(0));
        }else {
            //all products with images are downloaded
            publishProgress(100);
        }

และโหลดภาพของคุณเหมือนปกติหรือด้วยการแคชดิสก์

    Picasso.with(this).load(Constants.imagesUrl+batterProduct.getGalleryImage())
        .networkPolicy(NetworkPolicy.OFFLINE)
        .placeholder(R.drawable.GalleryDefaultImage)
        .error(R.drawable.GalleryDefaultImage)
        .into(viewGallery);

บันทึก:

สีแดงสีบ่งชี้ว่าภาพจะมาจากเครือข่าย

สีเขียวสีบ่งชี้ว่าภาพจะถูกดึงข้อมูลจากหน่วยความจำแคช

สีฟ้าสีบ่งชี้ว่าภาพจะถูกดึงข้อมูลจากหน่วยความจำดิสก์

ก่อนที่จะปล่อยแอปลบหรือตั้งค่าfalse picasso.setLoggingEnabled(true);, picasso.setIndicatorsEnabled(true);ถ้าไม่จำเป็น thankx


1

ฉันไม่รู้ว่าวิธีแก้ปัญหานั้นดีแค่ไหน แต่มันเป็นวิธีที่ง่ายที่ฉันเพิ่งใช้ในแอพของฉันและมันก็ใช้งานได้ดี

คุณโหลดภาพแบบนั้น

public void loadImage (){
Picasso picasso = Picasso.with(this); 
picasso.setDebugging(true);
picasso.load(quiz.getImageUrl()).into(quizImage);
}

คุณจะได้รับbimapเช่นนั้น

Bitmap bitmap = Piccaso.with(this).load(quiz.getImageUrl()).get();

ตอนนี้แอบแฝงBitmapไว้ในJPGไฟล์และเก็บไว้ในแคชด้านล่างนี้เป็นรหัสที่สมบูรณ์สำหรับการรับ bimap และแคช

 Thread thread = new Thread() {
                            public void run() {
                                File file = new File(getActivity().getExternalCacheDir().getAbsolutePath() + "/" +member.getMemberId() + ".jpg");

                                try {
                                    Bitmap bitmap = Picasso.with(getActivity())
                                            .load(uri).get();
                                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100,new FileOutputStream(file));

                                } catch (Exception e) {
                                   e.printStackTrace();
                                }
                            }
                        };
                        thread.start();
                    })

get()วิธีการPiccassoด้วยเหตุผลบางอย่างที่จำเป็นจะต้องเรียกว่าในหัวข้อที่แยกจากกันฉันบันทึกภาพที่ยังอยู่ในหัวข้อเดียวกันว่า

เมื่อบันทึกภาพแล้วคุณจะได้ไฟล์ทั้งหมดเช่นนั้น

List<File> files = new LinkedList<>(Arrays.asList(context.getExternalCacheDir().listFiles()));

ตอนนี้คุณสามารถค้นหาไฟล์ที่ต้องการได้ด้านล่าง

for(File file : files){
                if(file.getName().equals("fileyouarelookingfor" + ".jpg")){ // you need the name of the file, for example you are storing user image and the his image name is same as his id , you can call getId() on user to get the file name
                    Picasso.with(getActivity()) // if file found then load it
                            .load(file)
                            .into(mThumbnailImage);
                    return; // return 
                }
        // fetch it over the internet here because the file is not found
       }

0

ฉันใช้รหัสนี้และได้ผลอาจเป็นประโยชน์สำหรับคุณ:

public static void makeImageRequest(final View parentView,final int id, final String imageUrl) {

    final int defaultImageResId = R.mipmap.user;
    final ImageView imageView = (ImageView) parentView.findViewById(id);
    Picasso.with(context)
            .load(imageUrl)
            .networkPolicy(NetworkPolicy.OFFLINE)
            .into(imageView, new Callback() {
                @Override
                public void onSuccess() {
                Log.v("Picasso","fetch image success in first time.");
                }

                @Override
                public void onError() {
                    //Try again online if cache failed
                    Log.v("Picasso","Could not fetch image in first time...");
                    Picasso.with(context).load(imageUrl).networkPolicy(NetworkPolicy.NO_CACHE)
                            .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE).error(defaultImageResId)
                            .into(imageView, new Callback() {

                                @Override
                                public void onSuccess() {
                                    Log.v("Picasso","fetch image success in try again.");
                                }

                                @Override
                                public void onError() {
                                  Log.v("Picasso","Could not fetch image again...");
                                }

                            });
                }
            });

}

-3

ฉันมีปัญหาเดียวกันและใช้ห้องสมุด Glide แทน แคชอยู่นอกกรอบที่นั่น https://github.com/bumptech/glide


Glide มีรหัสมากกว่า Picasso 5 เท่า เมื่อใช้ okhttp อยู่แล้วควรใช้ Picasso
Alexander Farber

2
ไลบรารีทั้งสองใช้การแคช แต่แคชต่างกัน: medium.com/@multidots/glide-vs-picasso-930eed42b81d
Orri
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.