项目做完终于有点自己的时间了,就想看点源码涨涨姿势,那就看看Picasso这个牛逼哄哄的图片加载框架吧,当然这个也是自己最喜欢的图片加载框架。
实际项目中可能有需求自己定制图片的缓存目录,那么就需要自定义下载器,如果我们使用with来构建Picasso 的实例的话,Picasso会通过Builder模式为我们初始化一个默认的下载器:
public Picasso build() { Context context = this.context; if (downloader == null) { downloader = Utils.createDefaultDownloader(context);//初始化默认下载器 } if (cache == null) { cache = new LruCache(context); } if (service == null) { service = new PicassoExecutorService(); } if (transformer == null) { transformer = RequestTransformer.IDENTITY; } Stats stats = new Stats(cache); Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled); }
而Picasso创建的下载器会优先使用OkHttp,如果项目没有添加OkHttp依赖的话会使用HttpUrlConnection,检查是否添加依赖使用了反射来检查:
static Downloader createDefaultDownloader(Context context) { if (SDK_INT >= GINGERBREAD) { try { Class.forName("okhttp3.OkHttpClient"); return OkHttp3DownloaderCreator.create(context); } catch (ClassNotFoundException ignored) { } try { Class.forName("com.squareup.okhttp.OkHttpClient"); return OkHttpDownloaderCreator.create(context); } catch (ClassNotFoundException ignored) { } } return new UrlConnectionDownloader(context); }
再跟踪下去,Picasso创建的默认下载器使用的缓存目录是:
static File createDefaultCacheDir(Context context) { File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE); if (!cache.exists()) { //noinspection ResultOfMethodCallIgnored cache.mkdirs(); } return cache; }
即data/data/packagename/...,那么我们想把图片缓存到SD卡就需要自己定义下载器并设置缓存目录,下面只是把源码稍微改了下:
import android.annotation.TargetApi; import android.content.Context; import android.net.Uri; import android.os.StatFs; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import com.squareup.picasso.Downloader; import com.squareup.picasso.NetworkPolicy; import java.io.File; import java.io.IOException; import okhttp3.Cache; import okhttp3.CacheControl; import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.ResponseBody; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; public final class OkHttp3Downloader implements Downloader { private final Call.Factory client; private final Cache cache; private boolean sharedClient = true; private static final String PICASSO_CACHE = "picasso-cache"; private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB private static File createDefaultCacheDir(Context context) { File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE); if (!cache.exists()) { //noinspection ResultOfMethodCallIgnored cache.mkdirs(); } return cache; } @TargetApi(JELLY_BEAN_MR2) private static long calculateDiskCacheSize(File dir) { long size = MIN_DISK_CACHE_SIZE; try { StatFs statFs = new StatFs(dir.getAbsolutePath()); //noinspection deprecation long blockCount = SDK_INT < JELLY_BEAN_MR2 ? (long) statFs.getBlockCount() : statFs.getBlockCountLong(); //noinspection deprecation long blockSize = SDK_INT < JELLY_BEAN_MR2 ? (long) statFs.getBlockSize() : statFs.getBlockSizeLong(); long available = blockCount * blockSize; // Target 2% of the total space. size = available / 50; } catch (IllegalArgumentException ignored) { } // Bound inside min/max size for disk cache. return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE); } /** * Create new downloader that uses OkHttp. This will install an image cache into your application * cache directory. */ public OkHttp3Downloader(final Context context) { this(createDefaultCacheDir(context)); } /** * Create new downloader that uses OkHttp. This will install an image cache into the specified * directory. * * @param cacheDir The directory in which the cache should be stored */ public OkHttp3Downloader(final File cacheDir) { this(cacheDir, calculateDiskCacheSize(cacheDir)); } /** * Create new downloader that uses OkHttp. This will install an image cache into your application * cache directory. * * @param maxSize The size limit for the cache. */ public OkHttp3Downloader(final Context context, final long maxSize) { this(createDefaultCacheDir(context), maxSize); } /** * Create new downloader that uses OkHttp. This will install an image cache into the specified * directory. * * @param cacheDir The directory in which the cache should be stored * @param maxSize The size limit for the cache. */ public OkHttp3Downloader(final File cacheDir, final long maxSize) { this(new OkHttpClient.Builder().cache(new Cache(cacheDir, maxSize)).build()); sharedClient = false; } /** * Create a new downloader that uses the specified OkHttp instance. A response cache will not be * automatically configured. */ public OkHttp3Downloader(OkHttpClient client) { this.client = client; this.cache = client.cache(); } /** * Create a new downloader that uses the specified {@link Call.Factory} instance. */ public OkHttp3Downloader(Call.Factory client) { this.client = client; this.cache = null; } @VisibleForTesting Cache getCache() { return ((OkHttpClient) client).cache(); } @Override public Response load(@NonNull Uri uri, int networkPolicy) throws IOException { CacheControl cacheControl = null; if (networkPolicy != 0) { if (NetworkPolicy.isOfflineOnly(networkPolicy)) { cacheControl = CacheControl.FORCE_CACHE; } else { CacheControl.Builder builder = new CacheControl.Builder(); if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) { builder.noCache(); } if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) { builder.noStore(); } cacheControl = builder.build(); } } Request.Builder builder = new okhttp3.Request.Builder().url(uri.toString()); if (cacheControl != null) { builder.cacheControl(cacheControl); } okhttp3.Response response = client.newCall(builder.build()).execute(); int responseCode = response.code(); if (responseCode >= 300) { response.body().close(); throw new ResponseException(responseCode + " " + response.message(), networkPolicy, responseCode); } boolean fromCache = response.cacheResponse() != null; ResponseBody responseBody = response.body(); return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength()); } @Override public void shutdown() { if (!sharedClient) { if (cache != null) { try { cache.close(); } catch (IOException ignored) { } } } } }
然后不要使用with实例化Picasso,而要使用builder来实例化,并设置自定义的下载器即可:
final File externalFilesDir = getExternalFilesDir(Environment.DIRECTORY_NOTIFICATIONS); BTN.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Picasso picasso = new Picasso.Builder(MainActivity.this).downloader(new OkHttp3Downloader(externalFilesDir)).build(); picasso.load(PATH).memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE).into(IMG); } });