网站建设与网页设计从入门到精通 素材下载,上海seo网站设计,临海企业网站设计,wordpress图片云盘本文采用研究生论文格式编写#xff0c;方便大家阅读。作者#xff1a;谭东 摘要 Picasso是美国SQUARE移动支付公司开源的图片缓存加载库。可以实现图片下载和缓存功能#xff0c;效率和性能都很不错。 Square公司官方博客#xff1a;http://square.github.io/
Square公司… 本文采用研究生论文格式编写方便大家阅读。作者谭东 摘要 Picasso是美国SQUARE移动支付公司开源的图片缓存加载库。可以实现图片下载和缓存功能效率和性能都很不错。 Square公司官方博客http://square.github.io/
Square公司Github地址https://github.com/square
Square公司Picasso的Wiki地址http://square.github.io/picasso/
Square公司Picasso的Github地址https://github.com/square/picasso 1 绪论
Picasso的用法是链式调用和Glide用法很像。当然也支持个性化设置是目前比较流行的图片缓存库之一。 1.1 Picasso特点
Picasso是全尺寸下载图片也就是把要加载的图片原图下载缓存下来。Picasso默认的缓存格式为ARGB_888相对于RGB_565内存占用高了一半左右当然图片质量显示要高一些。下面看下Picasso的主要特点
①链式调用使用简单
②具有一般图片框架的基础功能
③方便的图片转换
④加载过程监听和错误处理
⑤自动添加磁盘和内存二级缓存
⑥支持多种数据源加载。
Picasso默认不支持Gif图片加载。Picasso库很小类也很少库仅118KB大小。最新版目前是2.5.2。 2 Picasso基本用法 2.1 引入Picasso
Jar包下载https://search.maven.org/remote_content?gcom.squareup.picassoapicassovLATEST
Gradle引用 [java] view plain copy compile com.squareup.picasso:picasso:2.5.2 Maven引用 [java] view plain copy dependency groupIdcom.squareup.picasso/groupId artifactIdpicasso/artifactId version2.5.2/version /dependency 2.2 基本加载图片 图2.1 Picasso加载图片 链式调用逻辑上比较清晰。 [java] view plain copy Picasso.with(context).load(http://i.imgur.com/DvpvklR.png).into(imageView); 改变图片尺寸和填充方式 [java] view plain copy Picasso.with(context) .load(url) .resize(50, 50) .centerCrop() .into(imageView) 图片个性化转换例如把图片显示加载成正方形那么你就要自己写具体逻辑。继承Transformation很简单只需要重新按照你的要求去绘制和创建个Bitmap即可 [java] view plain copy public class CropSquareTransformation implements Transformation { Override public Bitmap transform(Bitmap source) { int size Math.min(source.getWidth(), source.getHeight()); int x (source.getWidth() - size) / 2; int y (source.getHeight() - size) / 2; Bitmap result Bitmap.createBitmap(source, x, y, size, size); if (result ! source) { source.recycle(); } return result; } Override public String key() { return square(); } } 加入加载中图片和加载出错图片 [java] view plain copy Picasso.with(context) .load(url) .placeholder(R.drawable.user_placeholder) .error(R.drawable.user_placeholder_error) .into(imageView); 针对不同来源的图片加载如文件、网络图片、Assets目录、Resource、Content Provider等 [java] view plain copy Picasso.with(context).load(R.drawable.landing_screen).into(imageView1); Picasso.with(context).load(file:///android_asset/DvpvklR.png).into(imageView2); Picasso.with(context).load(new File(...)).into(imageView3); 图片的Log日志模式和显示图片的缓存来源的标识设置 [java] view plain copy Picasso picasso Picasso.with(this); picasso.setIndicatorsEnabled(true);//蓝色从本地缓存读取的图片红色从网络加载的图片绿色从内存缓存加载的图片 picasso.setLoggingEnabled(true);//日志调试模式 图2.2 Picasso加载图片来源标识 图片缓存颜色格式配置 [java] view plain copy Picasso.with(context).load(http://square.github.io/picasso/static/sample.png).config(Bitmap.Config.RGB_565) .into(imageView); ARGB_8888要比RGB_565占用内存大一半左右但图像质量差距不大一般。 3 Picasso高级用法
下面的代码是实现
1.自定义Picasso全局线程池 2.自定义Picasso默认缓存目录 3.自定义监听Picasso加载图片进度,结合OKHTTP [java] view plain copy package com.tandong.picassodemo; import android.app.Application; import com.tandong.picassodemo.utils.OkHttp3Downloader; import com.tandong.picassodemo.utils.ProgressListener; import com.squareup.picasso.Picasso; import java.io.IOException; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import okhttp3.Cache; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Response; /** * Created by Administrator on 2017/4/12. */ /** * 1.自定义Picasso全局线程池 * 2.自定义Picasso默认缓存目录 * 3.自定义监听Picasso加载图片进度,结合OKHTTP */ public class BaseApplication extends Application { private ThreadPoolExecutor threadPoolExecutor; private int cpu 0; private Picasso picasso; private OkHttpClient okHttpClient; public static ProgressListener progress; Override public void onCreate() { super.onCreate(); initPicasso(); } private void initPicasso() { okHttpClient new OkHttpClient.Builder().addNetworkInterceptor(new Interceptor() { Override public Response intercept(Chain chain) throws IOException { Response response chain.proceed(chain.request()); return response.newBuilder().body(new ProgressResponseBody(progress, response.body())).build(); } }).cache(new Cache(getExternalCacheDir(), 10 * 1024 * 1024)).build(); cpu Runtime.getRuntime().availableProcessors(); threadPoolExecutor new ThreadPoolExecutor(cpu 1, cpu * 2 1, 1, TimeUnit.MINUTES, new PriorityBlockingQueueRunnable()); picasso new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//默认3个线程 Picasso.setSingletonInstance(picasso); } public static void setProgressListener(ProgressListener progressListener) { progress progressListener; } } build.gradle引用 [java] view plain copy compile com.squareup.picasso:picasso:2.5.2 compile com.squareup.okhttp3:okhttp:3.6.0 Okhttp3下载器Okhttp3Downloader [java] view plain copy package com.tandong.picassodemo.utils; import android.content.Context; import android.net.Uri; import android.os.StatFs; 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; /** * Created by office on 2017/4/10. */ public final class OkHttp3Downloader implements Downloader { 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 defaultCacheDir(Context context) { File cache new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE); if (!cache.exists()) { //noinspection ResultOfMethodCallIgnored cache.mkdirs(); } return cache; } private static long calculateDiskCacheSize(File dir) { long size MIN_DISK_CACHE_SIZE; try { StatFs statFs new StatFs(dir.getAbsolutePath()); long available ((long) statFs.getBlockCount()) * statFs.getBlockSize(); // 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); } /** * Creates a {link Cache} that would have otherwise been created by calling * {link #OkHttp3Downloader(Context)}. This allows you to build your own {link OkHttpClient} * while still getting the default disk cache. */ public static Cache createDefaultCache(Context context) { File dir defaultCacheDir(context); return new Cache(dir, calculateDiskCacheSize(dir)); } private static OkHttpClient createOkHttpClient(File cacheDir, long maxSize) { return new OkHttpClient.Builder() .cache(new Cache(cacheDir, maxSize)) .build(); } private final Call.Factory client; private final Cache cache; /** * Create new downloader that uses OkHttp. This will install an image cache into your application * cache directory. */ public OkHttp3Downloader(Context context) { this(defaultCacheDir(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(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(defaultCacheDir(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(File cacheDir, long maxSize) { this(createOkHttpClient(cacheDir, maxSize)); } public OkHttp3Downloader(OkHttpClient client) { this.client client; this.cache client.cache(); } public OkHttp3Downloader(Call.Factory client) { this.client client; this.cache null; } Override public Response load(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 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 (cache ! null) { try { cache.close(); } catch (IOException ignored) { } } } } 官方地址
https://github.com/JakeWharton/picasso2-okhttp3-downloader/blob/master/src/main/java/com/jakewharton/picasso/OkHttp3Downloader.java 自定义ResponseBody实现对数据下载进度的监听处理 [java] view plain copy package com.tandong.picassodemo; import android.util.Log; import com.tandong.picassodemo.utils.ProgressListener; import java.io.IOException; import okhttp3.MediaType; import okhttp3.ResponseBody; import okio.Buffer; import okio.BufferedSource; import okio.ForwardingSource; import okio.Okio; import okio.Source; /** * Created by Administrator on 2017/4/14. */ public class ProgressResponseBody extends ResponseBody { private ResponseBody responseBody; private BufferedSource bufferedSource; private ProgressListener progressListener; public ProgressResponseBody(ProgressListener progressListener,ResponseBody responseBody) { this.responseBody responseBody; this.progressListenerprogressListener; } Override public MediaType contentType() { return responseBody.contentType(); } Override public long contentLength() { return responseBody.contentLength(); } Override public BufferedSource source() { if (bufferedSource null) { bufferedSource Okio.buffer(source(responseBody.source())); } return bufferedSource; } private Source source(Source source) { ForwardingSource forwardingSource new ForwardingSource(source) { long totalBytesRead 0L; Override public long read(Buffer sink, long byteCount) throws IOException { long bytesRead super.read(sink, byteCount); totalBytesRead bytesRead ! -1 ? bytesRead : 0; progressListener.update((int) ((100 * totalBytesRead) / responseBody.contentLength())); Log.i(info, 进度 (int) ((100 * totalBytesRead) / responseBody.contentLength())); return bytesRead; } Override public void close() throws IOException { super.close(); } }; return forwardingSource; } } 进度接口 [java] view plain copy package com.tandong.picassodemo.utils; import android.support.annotation.IntRange; /** * Created by Administrator on 2017/4/14. */ public interface ProgressListener { public void update(IntRange(from 0, to 100) int progress); } 自定义带进度的ImageView [java] view plain copy package com.tandong.picassodemo.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.support.annotation.IntRange; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.widget.ImageView; /** * Created by office on 2017/4/10. */ public class PicassoImageView extends ImageView { private final int MAX_PROGRESS 100; private Paint mArcPaint; private RectF mBound; private Paint mCirclePaint; private int mProgress 0; public PicassoImageView(Context context) { this(context, null, 0); } public PicassoImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PicassoImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mArcPaint new Paint(Paint.ANTI_ALIAS_FLAG); mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE); mArcPaint.setStrokeWidth(dpToPixel(0.1f, getContext())); mArcPaint.setColor(Color.argb(120, 0xff, 0xff, 0xff)); mCirclePaint new Paint(Paint.ANTI_ALIAS_FLAG); mCirclePaint.setStyle(Paint.Style.STROKE); mCirclePaint.setStrokeWidth(dpToPixel(2, getContext())); mCirclePaint.setColor(Color.argb(120, 0xff, 0xff, 0xff)); mBound new RectF(); } public void setProgress(IntRange(from 0, to MAX_PROGRESS) int mProgress) { this.mProgress mProgress; ViewCompat.postInvalidateOnAnimation(this); } Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); int min Math.min(w, h); int max w h - min; int r min / 5; //set up a square in the imageView //设置了在图片中间的一个小圆 mBound.set(max / 2 - r, min / 2 - r, max / 2 r, min / 2 r); } Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //画进度条, Paint是画笔详见源码 if (mProgress ! MAX_PROGRESS mProgress ! 0) { float mAngle mProgress * 360f / MAX_PROGRESS; //画扇型 canvas.drawArc(mBound, 270, mAngle, true, mArcPaint); //画圆 canvas.drawCircle(mBound.centerX(), mBound.centerY(), mBound.height() / 2, mCirclePaint); } } private static float scale; public static int dpToPixel(float dp, Context context) { if (scale 0) { scale context.getResources().getDisplayMetrics().density; } return (int) (dp * scale); } } Picasso源码原理解析
先看流程图 Picasso的源码很小类也很少相对简单些。
我们主要关注以下标红的几个类。 不是太想写太多的文字那就看源码分析吧。
先看构造单例模式的Piasso实例 [java] view plain copy /** * with方式 */ Picasso.with(this).load(http://square.github.io/picasso/static/sample.png).centerCrop().into(imageView); /** * new with方式 */ Picasso picasso Picasso.with(this); /** * builder方式 */ picasso new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//默认3个线程 Picasso.setSingletonInstance(picasso); 这几种方式通过查看都是通过一个方式实现的构造那就是builder方式。 我们按照这句最基本的方式进行源码分析。 [java] view plain copy Picasso.with(this).load(http://square.github.io/picasso/static/sample.png).centerCrop().into(imageView); 点击with方法进去。 [java] view plain copy public static Picasso with(Context context) { if (singleton null) { synchronized (Picasso.class) { if (singleton null) { singleton new Builder(context).build(); } } } return singleton; } /** Create the {link Picasso} instance. */ public Picasso build() { Context context this.context; //是否自定义下载器无的话使用默认下载器 if (downloader null) { downloader Utils.createDefaultDownloader(context); } //是否自定义缓存方式无的话使用默认LruCache if (cache null) { cache new LruCache(context); } //是否自定义线程池无的话使用默认线程池 if (service null) { service new PicassoExecutorService(); } //是否自定义转换器无的话使用默认转换器 if (transformer null) { transformer RequestTransformer.IDENTITY; } //统计器统计缓存的命中率 Stats stats new Stats(cache); //创建Dispather分发调度器 Dispatcher dispatcher new Dispatcher(context, service, HANDLER, downloader, cache, stats); //创建Picasso实例 return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled); } } 可以看出都是通过builder方式实例化的Picasso类。这里实例化了Dispatcher和Picasso。
接下来点击load方法进去 [java] view plain copy /** * Start an image request using the specified path. This is a convenience method for calling * {link #load(Uri)}. * p * This path may be a remote URL, file resource (prefixed with {code file:}), content resource * (prefixed with {code content:}), or android resource (prefixed with {code * android.resource:}. * p * Passing {code null} as a {code path} will not trigger any request but will set a * placeholder, if one is specified. * * see #load(Uri) * see #load(File) * see #load(int) * throws IllegalArgumentException if {code path} is empty or blank string. */ public RequestCreator load(String path) { if (path null) { return new RequestCreator(this, null, 0); } if (path.trim().length() 0) { throw new IllegalArgumentException(Path must not be empty.); } return load(Uri.parse(path)); } /** * Start an image request using the specified URI. * p * Passing {code null} as a {code uri} will not trigger any request but will set a placeholder, * if one is specified. * * see #load(File) * see #load(String) * see #load(int) */ public RequestCreator load(Uri uri) { return new RequestCreator(this, uri, 0); } RequestCreator(Picasso picasso, Uri uri, int resourceId) { if (picasso.shutdown) { throw new IllegalStateException( Picasso instance already shut down. Cannot submit new requests.); } this.picasso picasso; this.data new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig); } 可以看出这里实例化了一个RequestCreator我们看下这个源码类干嘛的。 [java] view plain copy /** Fluent API for building an image download request. */ SuppressWarnings(UnusedDeclaration) // Public API. public class RequestCreator { private static final AtomicInteger nextId new AtomicInteger(); private final Picasso picasso; private final Request.Builder data; private boolean noFade; private boolean deferred; private boolean setPlaceholder true; private int placeholderResId; private int errorResId; private int memoryPolicy; private int networkPolicy; private Drawable placeholderDrawable; private Drawable errorDrawable; private Object tag; 看下基本的声明就是进行我们个性化配置存储的类。 我们接下来点击centerCrop方法进去 [java] view plain copy /** * Crops an image inside of the bounds specified by {link #resize(int, int)} rather than * distorting the aspect ratio. This cropping technique scales the image so that it fills the * requested bounds and then crops the extra. */ public RequestCreator centerCrop() { data.centerCrop(); return this; } 这个配置也是操作我们的RequestCreator。 最后点击into方法。 [java] view plain copy /** * Asynchronously fulfills the request into the specified {link ImageView}. * p * emNote:/em This method keeps a weak reference to the {link ImageView} instance and will * automatically support object recycling. */ public void into(ImageView target) { into(target, null); } ** * Asynchronously fulfills the request into the specified {link ImageView} and invokes the * target {link Callback} if its not {code null}. * p * emNote:/em The {link Callback} param is a strong reference and will prevent your * {link android.app.Activity} or {link android.app.Fragment} from being garbage collected. If * you use this method, it is bstrongly/b recommended you invoke an adjacent * {link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking. */ public void into(ImageView target, Callback callback) { long started System.nanoTime(); checkMain(); if (target null) { throw new IllegalArgumentException(Target must not be null.); } if (!data.hasImage()) {//清除取消存在请求 picasso.cancelRequest(target); if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } return; } if (deferred) { if (data.hasSize()) { throw new IllegalStateException(Fit cannot be used with resize.); } int width target.getWidth(); int height target.getHeight(); if (width 0 || height 0) { if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } picasso.defer(target, new DeferredRequestCreator(this, target, callback)); return; } data.resize(width, height); } Request request createRequest(started);//时间戳为Key创建Request String requestKey createKey(request); if (shouldReadFromMemoryCache(memoryPolicy)) {//内存缓存检查 Bitmap bitmap picasso.quickMemoryCacheCheck(requestKey); if (bitmap ! null) { picasso.cancelRequest(target); setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled); if (picasso.loggingEnabled) { log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), from MEMORY); } if (callback ! null) { callback.onSuccess(); } return; } } if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } //关键创建Action这里用的ImageViewAction Action action new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); //把请求封装成Action提交分发 picasso.enqueueAndSubmit(action); } [java] view plain copy void enqueueAndSubmit(Action action) { Object target action.getTarget(); if (target ! null targetToAction.get(target) ! action) { // This will also check we are on the main thread. cancelExistingRequest(target); targetToAction.put(target, action); } submit(action); } void submit(Action action) {//提交给Dispatcher分发 dispatcher.dispatchSubmit(action); } [java] view plain copy void dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action)); } private static class DispatcherHandler extends Handler { private final Dispatcher dispatcher; public DispatcherHandler(Looper looper, Dispatcher dispatcher) { super(looper); this.dispatcher dispatcher; } Override public void handleMessage(final Message msg) { switch (msg.what) { case REQUEST_SUBMIT: {//提交分发下载请求 Action action (Action) msg.obj; dispatcher.performSubmit(action); break; } case REQUEST_CANCEL: { Action action (Action) msg.obj; dispatcher.performCancel(action); break; } case TAG_PAUSE: { Object tag msg.obj; dispatcher.performPauseTag(tag); break; } case TAG_RESUME: { Object tag msg.obj; dispatcher.performResumeTag(tag); break; } case HUNTER_COMPLETE: { BitmapHunter hunter (BitmapHunter) msg.obj; dispatcher.performComplete(hunter); break; } case HUNTER_RETRY: { BitmapHunter hunter (BitmapHunter) msg.obj; dispatcher.performRetry(hunter); break; } case HUNTER_DECODE_FAILED: { BitmapHunter hunter (BitmapHunter) msg.obj; dispatcher.performError(hunter, false); break; } case HUNTER_DELAY_NEXT_BATCH: { dispatcher.performBatchComplete(); break; } case NETWORK_STATE_CHANGE: { NetworkInfo info (NetworkInfo) msg.obj; dispatcher.performNetworkStateChange(info); break; } case AIRPLANE_MODE_CHANGE: { dispatcher.performAirplaneModeChange(msg.arg1 AIRPLANE_MODE_ON); break; } default: Picasso.HANDLER.post(new Runnable() { Override public void run() { throw new AssertionError(Unknown handler message received: msg.what); } }); } } } [java] view plain copy void performSubmit(Action action) { performSubmit(action, true); } void performSubmit(Action action, boolean dismissFailed) { if (pausedTags.contains(action.getTag())) { pausedActions.put(action.getTarget(), action); if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(), because tag action.getTag() is paused); } return; } //BitmapHunter核心它是个线程执行图片下载编解码 BitmapHunter hunter hunterMap.get(action.getKey()); if (hunter ! null) { hunter.attach(action); return; } if (service.isShutdown()) { if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), because shut down); } return; } hunter forRequest(action.getPicasso(), this, cache, stats, action); hunter.future service.submit(hunter);//这个service是ExecutorService线程池提交执行下载线程 hunterMap.put(action.getKey(), hunter); if (dismissFailed) { failedActions.remove(action.getTarget()); } if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId()); } } 大概看下BitmapHunter源码 [java] view plain copy /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.NetworkInfo; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import static com.squareup.picasso.MemoryPolicy.shouldReadFromMemoryCache; import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY; import static com.squareup.picasso.Picasso.Priority; import static com.squareup.picasso.Picasso.Priority.LOW; import static com.squareup.picasso.Utils.OWNER_HUNTER; import static com.squareup.picasso.Utils.VERB_DECODED; import static com.squareup.picasso.Utils.VERB_EXECUTING; import static com.squareup.picasso.Utils.VERB_JOINED; import static com.squareup.picasso.Utils.VERB_REMOVED; import static com.squareup.picasso.Utils.VERB_TRANSFORMED; import static com.squareup.picasso.Utils.getLogIdsForHunter; import static com.squareup.picasso.Utils.log; class BitmapHunter implements Runnable { /** * Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since * this will only ever happen in background threads we help avoid excessive memory thrashing as * well as potential OOMs. Shamelessly stolen from Volley. */ private static final Object DECODE_LOCK new Object(); private static final ThreadLocalStringBuilder NAME_BUILDER new ThreadLocalStringBuilder() { Override protected StringBuilder initialValue() { return new StringBuilder(Utils.THREAD_PREFIX); } }; private static final AtomicInteger SEQUENCE_GENERATOR new AtomicInteger(); private static final RequestHandler ERRORING_HANDLER new RequestHandler() { Override public boolean canHandleRequest(Request data) { return true; } Override public Result load(Request request, int networkPolicy) throws IOException { throw new IllegalStateException(Unrecognized type of request: request); } }; final int sequence; final Picasso picasso; final Dispatcher dispatcher; final Cache cache; final Stats stats; final String key; final Request data; final int memoryPolicy; int networkPolicy; final RequestHandler requestHandler; Action action; ListAction actions; Bitmap result; Future? future; Picasso.LoadedFrom loadedFrom; Exception exception; int exifRotation; // Determined during decoding of original resource. int retryCount; Priority priority; BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action, RequestHandler requestHandler) { this.sequence SEQUENCE_GENERATOR.incrementAndGet(); this.picasso picasso; this.dispatcher dispatcher; this.cache cache; this.stats stats; this.action action; this.key action.getKey(); this.data action.getRequest(); this.priority action.getPriority(); this.memoryPolicy action.getMemoryPolicy(); this.networkPolicy action.getNetworkPolicy(); this.requestHandler requestHandler; this.retryCount requestHandler.getRetryCount(); } 它是实现了Runable接口的线程再继续看Run方法。 [java] view plain copy Override public void run() { try { updateThreadName(data); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this)); } //这个result是Bitmap调用了hunt方法获取 result hunt(); if (result null) {//分发图片加载失败 dispatcher.dispatchFailed(this); } else {//分发图片加载完成 dispatcher.dispatchComplete(this); } } catch (Downloader.ResponseException e) { if (!e.localCacheOnly || e.responseCode ! 504) { exception e; } dispatcher.dispatchFailed(this); } catch (NetworkRequestHandler.ContentLengthException e) { exception e; dispatcher.dispatchRetry(this); } catch (IOException e) { exception e; dispatcher.dispatchRetry(this); } catch (OutOfMemoryError e) { StringWriter writer new StringWriter(); stats.createSnapshot().dump(new PrintWriter(writer)); exception new RuntimeException(writer.toString(), e); dispatcher.dispatchFailed(this); } catch (Exception e) { exception e; dispatcher.dispatchFailed(this); } finally { Thread.currentThread().setName(Utils.THREAD_IDLE_NAME); } } 再继续看hunt方法 [java] view plain copy Bitmap hunt() throws IOException { Bitmap bitmap null; //内存缓存检查检查了很多次内存缓存了 if (shouldReadFromMemoryCache(memoryPolicy)) { bitmap cache.get(key); if (bitmap ! null) { stats.dispatchCacheHit(); loadedFrom MEMORY; if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId(), from cache); } return bitmap; } } data.networkPolicy retryCount 0 ? NetworkPolicy.OFFLINE.index : networkPolicy; RequestHandler.Result result requestHandler.load(data, networkPolicy); if (result ! null) { loadedFrom result.getLoadedFrom(); exifRotation result.getExifOrientation(); bitmap result.getBitmap(); // If there was no Bitmap then we need to decode it from the stream. if (bitmap null) { InputStream is result.getStream(); try {//inputstream解码 bitmap decodeStream(is, data); } finally { Utils.closeQuietly(is); } } } if (bitmap ! null) { if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId()); } stats.dispatchBitmapDecoded(bitmap);//解码命中率统计 if (data.needsTransformation() || exifRotation ! 0) { synchronized (DECODE_LOCK) { if (data.needsMatrixTransform() || exifRotation ! 0) { bitmap transformResult(data, bitmap, exifRotation); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId()); } } if (data.hasCustomTransformations()) { bitmap applyCustomTransformations(data.transformations, bitmap); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), from custom transformations); } } } if (bitmap ! null) { stats.dispatchBitmapTransformed(bitmap); } } } return bitmap; } 看decodeStream方法里的逻辑 [java] view plain copy /** * Decode a byte stream into a Bitmap. This method will take into account additional information * about the supplied request in order to do the decoding efficiently (such as through leveraging * {code inSampleSize}). */ static Bitmap decodeStream(InputStream stream, Request request) throws IOException { MarkableInputStream markStream new MarkableInputStream(stream); stream markStream; long mark markStream.savePosition(65536); // TODO fix this crap. final BitmapFactory.Options options RequestHandler.createBitmapOptions(request); final boolean calculateSize RequestHandler.requiresInSampleSize(options); boolean isWebPFile Utils.isWebPFile(stream); markStream.reset(mark); // When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash. // Decode byte array instead if (isWebPFile) { byte[] bytes Utils.toByteArray(stream); if (calculateSize) { BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options, request); } return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); } else { if (calculateSize) { BitmapFactory.decodeStream(stream, null, options); RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options, request); markStream.reset(mark); } Bitmap bitmap BitmapFactory.decodeStream(stream, null, options); if (bitmap null) { // Treat null as an IO exception, we will eventually retry. throw new IOException(Failed to decode stream.); } return bitmap; } } 就是对inputSteam转为Bimap操作。 接下来看dispatcher派发加载成功消息的逻辑 [java] view plain copy void dispatchComplete(BitmapHunter hunter) { handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter)); } [java] view plain copy case HUNTER_COMPLETE: {//加载完成 BitmapHunter hunter (BitmapHunter) msg.obj; dispatcher.performComplete(hunter); break; } [java] view plain copy void performComplete(BitmapHunter hunter) { if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) { cache.set(hunter.getKey(), hunter.getResult()); } hunterMap.remove(hunter.getKey()); batch(hunter); if (hunter.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), for completion); } } private void batch(BitmapHunter hunter) { if (hunter.isCancelled()) { return; } batch.add(hunter); if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) { handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY); } } [java] view plain copy case HUNTER_DELAY_NEXT_BATCH: {//继续派发next_batch消息 dispatcher.performBatchComplete(); break; } [java] view plain copy void performBatchComplete() { ListBitmapHunter copy new ArrayListBitmapHunter(batch); batch.clear(); mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));//注意这里的mainThreadHandler是Picasso的Handler logBatch(copy); } 注意这里的mainThreadHander是Picasso里的Handler我们可以看下Dispatcher的实例化。 [java] view plain copy Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler, Downloader downloader, Cache cache, Stats stats) { this.dispatcherThread new DispatcherThread(); this.dispatcherThread.start(); Utils.flushStackLocalLeaks(dispatcherThread.getLooper()); this.context context; this.service service; this.hunterMap new LinkedHashMapString, BitmapHunter(); this.failedActions new WeakHashMapObject, Action(); this.pausedActions new WeakHashMapObject, Action(); this.pausedTags new HashSetObject(); this.handler new DispatcherHandler(dispatcherThread.getLooper(), this); this.downloader downloader; this.mainThreadHandler mainThreadHandler;//这个就是Picasso传进来的mainThreadHandler this.cache cache; this.stats stats; this.batch new ArrayListBitmapHunter(4); this.airplaneMode Utils.isAirplaneModeOn(this.context); this.scansNetworkChanges hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE); this.receiver new NetworkBroadcastReceiver(this); receiver.register(); } 那我们看下Picasso里的HUNTER_BATCH_COMPLETE消息处理 [java] view plain copy static final String TAG Picasso; static final Handler HANDLER new Handler(Looper.getMainLooper()) { Override public void handleMessage(Message msg) { switch (msg.what) { case HUNTER_BATCH_COMPLETE: { SuppressWarnings(unchecked) ListBitmapHunter batch (ListBitmapHunter) msg.obj; //noinspection ForLoopReplaceableByForEach for (int i 0, n batch.size(); i n; i) { BitmapHunter hunter batch.get(i); hunter.picasso.complete(hunter);//回调了complete方法 } break; } case REQUEST_GCED: { Action action (Action) msg.obj; if (action.getPicasso().loggingEnabled) { log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), target got garbage collected); } action.picasso.cancelExistingRequest(action.getTarget()); break; } case REQUEST_BATCH_RESUME: SuppressWarnings(unchecked) ListAction batch (ListAction) msg.obj; //noinspection ForLoopReplaceableByForEach for (int i 0, n batch.size(); i n; i) { Action action batch.get(i); action.picasso.resumeAction(action); } break; default: throw new AssertionError(Unknown handler message received: msg.what); } } }; 我们看下Picasso的complete方法 [java] view plain copy void complete(BitmapHunter hunter) { Action single hunter.getAction();//拿到了派发过来的Action ListAction joined hunter.getActions(); boolean hasMultiple joined ! null !joined.isEmpty(); boolean shouldDeliver single ! null || hasMultiple; if (!shouldDeliver) { return; } Uri uri hunter.getData().uri; Exception exception hunter.getException(); Bitmap result hunter.getResult();//拿到了Bitmap LoadedFrom from hunter.getLoadedFrom(); if (single ! null) { deliverAction(result, from, single);//执行deliverAction方法 } if (hasMultiple) { //noinspection ForLoopReplaceableByForEach for (int i 0, n joined.size(); i n; i) { Action join joined.get(i); deliverAction(result, from, join); } } if (listener ! null exception ! null) { listener.onImageLoadFailed(this, uri, exception); } } 我们看下deliverAction方法 [java] view plain copy private void deliverAction(Bitmap result, LoadedFrom from, Action action) { if (action.isCancelled()) { return; } if (!action.willReplay()) { targetToAction.remove(action.getTarget()); } if (result ! null) { if (from null) { throw new AssertionError(LoadedFrom cannot be null.); } action.complete(result, from);//回调了action的complete方法 if (loggingEnabled) { log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), from from); } } else { action.error(); if (loggingEnabled) { log(OWNER_MAIN, VERB_ERRORED, action.request.logId()); } } } 这里注意下我们之前用的是Action里的ImageViewAction类我们看下ImageViewAction源码和它的complete类里的处理逻辑 [java] view plain copy /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.widget.ImageView; class ImageViewAction extends ActionImageView { Callback callback; ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy, int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag, Callback callback, boolean noFade) { super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key, tag, noFade); this.callback callback; } //图片加载完成回调 Override public void complete(Bitmap result, Picasso.LoadedFrom from) { if (result null) { throw new AssertionError( String.format(Attempted to complete action with no result!\n%s, this)); } ImageView target this.target.get(); if (target null) { return; } Context context picasso.context; boolean indicatorsEnabled picasso.indicatorsEnabled; PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);//设置bitmap给PicassoDrawable if (callback ! null) { callback.onSuccess(); } } Override public void error() { ImageView target this.target.get(); if (target null) { return; } if (errorResId ! 0) { target.setImageResource(errorResId); } else if (errorDrawable ! null) { target.setImageDrawable(errorDrawable); } if (callback ! null) { callback.onError(); } } Override void cancel() { super.cancel(); if (callback ! null) { callback null; } } } 我们简单看下PicassoDrawable源码和它的setBitmap方法 [java] view plain copy /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.SystemClock; import android.widget.ImageView; import static android.graphics.Color.WHITE; import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY; final class PicassoDrawable extends BitmapDrawable {//继承自BitmapDrawable // Only accessed from main thread. private static final Paint DEBUG_PAINT new Paint(); private static final float FADE_DURATION 200f; //ms /** * Create or update the drawable on the target {link ImageView} to display the supplied bitmap * image. */ static void setBitmap(ImageView target, Context context, Bitmap bitmap, Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) { Drawable placeholder target.getDrawable(); if (placeholder instanceof AnimationDrawable) { ((AnimationDrawable) placeholder).stop(); } PicassoDrawable drawable new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging); target.setImageDrawable(drawable);//调用ImageView的setImageDrawable方法将转换后的drawable加载进去显示 } /** * Create or update the drawable on the target {link ImageView} to display the supplied * placeholder image. */ static void setPlaceholder(ImageView target, Drawable placeholderDrawable) { target.setImageDrawable(placeholderDrawable); if (target.getDrawable() instanceof AnimationDrawable) { ((AnimationDrawable) target.getDrawable()).start(); } } private final boolean debugging; private final float density; private final Picasso.LoadedFrom loadedFrom; Drawable placeholder; long startTimeMillis; boolean animating; int alpha 0xFF; PicassoDrawable(Context context, Bitmap bitmap, Drawable placeholder, Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) { super(context.getResources(), bitmap); this.debugging debugging; this.density context.getResources().getDisplayMetrics().density; this.loadedFrom loadedFrom; boolean fade loadedFrom ! MEMORY !noFade; if (fade) { this.placeholder placeholder; animating true; startTimeMillis SystemClock.uptimeMillis(); } } Override public void draw(Canvas canvas) { if (!animating) { super.draw(canvas); } else { float normalized (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION; if (normalized 1f) { animating false; placeholder null; super.draw(canvas); } else { if (placeholder ! null) { placeholder.draw(canvas); } int partialAlpha (int) (alpha * normalized); super.setAlpha(partialAlpha); super.draw(canvas); super.setAlpha(alpha); if (Build.VERSION.SDK_INT Build.VERSION_CODES.GINGERBREAD_MR1) { invalidateSelf(); } } } if (debugging) { drawDebugIndicator(canvas); } } Override public void setAlpha(int alpha) { this.alpha alpha; if (placeholder ! null) { placeholder.setAlpha(alpha); } super.setAlpha(alpha); } Override public void setColorFilter(ColorFilter cf) { if (placeholder ! null) { placeholder.setColorFilter(cf); } super.setColorFilter(cf); } Override protected void onBoundsChange(Rect bounds) { if (placeholder ! null) { placeholder.setBounds(bounds); } super.onBoundsChange(bounds); } private void drawDebugIndicator(Canvas canvas) { DEBUG_PAINT.setColor(WHITE); Path path getTrianglePath(new Point(0, 0), (int) (16 * density)); canvas.drawPath(path, DEBUG_PAINT); DEBUG_PAINT.setColor(loadedFrom.debugColor); path getTrianglePath(new Point(0, 0), (int) (15 * density)); canvas.drawPath(path, DEBUG_PAINT); } private static Path getTrianglePath(Point p1, int width) { Point p2 new Point(p1.x width, p1.y); Point p3 new Point(p1.x, p1.y width); Path path new Path(); path.moveTo(p1.x, p1.y); path.lineTo(p2.x, p2.y); path.lineTo(p3.x, p3.y); return path; } } 这样整个Picasso就实现了我们的图片请求加载显示。 如果你觉得本文太长可以单独查看Picasso源码原理分析一文
http://blog.csdn.net/jay100500/article/details/71055229 参考文献 [1]Picasso官方简易Wiki.Picasso[OL].2013.http://square.github.io/picasso/ 独创性声明 本人声明所呈交的学术研究文章是本人自己和在他人指导下进行的研究工作及取得的研究成果。除了文中特别加以标注和致谢的地方外文章中不包含他人已经发表或撰写过的研究成果也不包含为获得其他教育机构的学位或证书而使用过的材料。与我一同工作的同志对本研究所做的任何贡献均已在论文中作了明确的说明。 学术文章作者签名谭东 签字日期 2017年 4月 25日 学术文章版权使用授权书 本学术文章作者完全了解有关保留、使用学术文章的规定有权保留并向国家有关部门或机构送交文章的复印件和磁盘允许文章被查阅和借阅。本人授权后可以将学术文章的全部或部分内容编入有关数据库进行检索可以采用影印、缩印或扫描等复制手段保存、汇编学术文章。 遵循BY-SA 署名-相同方式共享 4.0协议