阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Picasso 基本使用和源码完全解析

324次阅读
没有评论

共计 23209 个字符,预计需要花费 59 分钟才能阅读完成。

说到 Picasso,相信 Android 开发人员绝不陌生,它是 Square 公司开发的一款图片加载神器。使用过它的 coder 绝对是爱不释手:对它本身而言,轻量安全,有效加载图片并防止 OOM;对我们开发者来说,简单方便,一行代码搞定图片加载。因此它备受 Android 开发人员的钟爱。

关于它的更多好处优点,相信不用我介绍你也非常的清楚,在这里我们废话不多说,直接的进入主题来讲解它的使用和从源码的角度解析它的加载原理。

Picasso 基本使用

一 工程引入

在我们的项目 build.gradle 中添加对 Picasso 框架的依赖:

compile 'com.squareup.picasso:picasso:2.5.2'

在这里我使用的是 2.5.2 的最新版本。

二 添加权限

因为加载图片需要访问网络,由此我们在 Manifest 中添加访问网络的权限:

<uses-permission android:name="android.permission.INTERNET"/>

三 创建加载图片布局文件

在我们的 MainActivity 的布局文件中添加一个 Button 和一个 ImageView:

Picasso 基本使用和源码完全解析

四 MainActivity 中点击加载图片

在这里我们点击 Button 让它加载一张百度 Logo 的图片并显示在 ImageView 控件中。

Picasso 基本使用和源码完全解析

五 加载图片

点击 Button 加载图片,来看结果:

Picasso 基本使用和源码完全解析

ok,已加载出来了,但是我们到底做了什么呢?

基本加载

其实我们所做的事情非常的简单,那就是在 Button 的点击事件中添加一行代码:

Picasso.with(MainActivity.this).load(url).into(headerImage);

没错就是一行代码搞定图片加载。至于它是怎么实现的,我们会在后面的源码分析中详细的解析它,现在来看看它的其他使用。

占位图

不光加载图片是非常简单的事情,而且还可以在加载过程中使用占位图,可以很友好的告诉用户它正在加载,而不是加载中显示空白的界面。

Picasso.with(MainActivity.this)
.load(url)
.placeholder(R.mipmap.ic_launcher)
.into(headerImage);

使用 placeholder 为尚未加载到图片的 ImageView 设置占位图,这是一种友好的显示方式。

Picasso 基本使用和源码完全解析

异常图

不光可以设置占位图,而且还可以在图片加载不出来,或是找不到要加载的图片后,可以为 ImageView 设置异常图:

Picasso.with(MainActivity.this)
       .load(url)
       .placeholder(R.mipmap.ic_launcher)
       .error(R.drawable.error)
       .into(headerImage);

使用 error 可以为加载异常的 ImageView 设置异常图,修改我们的图片 URL,使它无法获取到正确的图片地址,然后来看结果:

Picasso 基本使用和源码完全解析

转换器

不仅如此,我们还可以对加载到的图片进行重新调整,比如改变图片的大小,显示形状等,可以使用 transform 方法,例如:

首先我们先自定义一个 Transformation:

private class customTransformer implements Transformation{

        @Override
        public Bitmap transform(Bitmap source) {
            // 在这里可以对 Bitmap 进行操作,比如改变大小,形状等

            return source;
        }
        @Override
        public String key() {return null;}
    }

然后在 transform 方法中进行处理:

Picasso.with(MainActivity.this)
        .load(url)
        .placeholder(R.mipmap.ic_launcher)
        .transform(new customTransformer())
        .error(R.drawable.error)
        .into(headerImage);

这样的话就可以对图片进行一定意义上的控制和选择,使它更加符合我们的需求。

当然 Picasso 中还有很多其他的应用,比如可以设置加载大小,使用 resizeDimen 方法,填充方式使用 centerCrop,fit 等等,大家如果有需要的话可以自己尝试使用。这里就不要一一的介绍了。

Picasso 源码解析

ok,上面介绍了 Picasso 的部分基础使用,非常的简单,一行代码搞定你的所需,那么下面我们从源码的角度来解析下它到底是怎么实现我们的图片加载和使用的。

以下面最简洁的加载为示例:

Picasso.with(MainActivity.this).load(url).into(headerImage);
with

首先 Picasso 会调用静态 with 方法,那么我们来看看 with 方法是怎么实现的:

Picasso 基本使用和源码完全解析

由上面的源码我们可以看到,在 with 方法中主要做了一件事,那就是返回一个 Picasso 实例,当然这个实例也不是那么简单的创建的,为了防止 Picasso 的多次创建,这里使用了双重加锁的单例模式来创建的,主要目的是为了保证线程的安全性。但是它又不是直接的使用单例模式创建的,在创建实例的过程中使用了 Builder 模式,它可以使 Picasso 在创建时初始化很多对象,以便后期使用,那么我们就来看看这个 Builder 是怎么操作的:

Picasso 基本使用和源码完全解析

在 Builder 的构造方法中就只是获取到当前应用级别的上下文,也就说明了 Picasso 是针对应用级别的使用,不会是随着 Activity 或是 Fragment 的生命周期而产生变化,只有当当前的应用退出或是销毁时 Picasso 才会停止它的行为。

那么接下来看下 build 方法中做了哪些操作呢:

Picasso 基本使用和源码完全解析

这里代码也是很简单,主要是初始化了 downloader,cache,service,dispatcher 等几个实例变量,而这几个变量值也是已设置的,如源码:

public Builder downloader(Downloader downloader) {if (downloader == null) {throw new IllegalArgumentException("Downloader must not be null.");
      }
      if (this.downloader != null) {throw new IllegalStateException("Downloader already set.");
      }
      this.downloader = downloader;
      return this;
    }

    public Builder executor(ExecutorService executorService) {if (executorService == null) {throw new IllegalArgumentException("Executor service must not be null.");
      }
      if (this.service != null) {throw new IllegalStateException("Executor service already set.");
      }
      this.service = executorService;
      return this;
    }

    public Builder memoryCache(Cache memoryCache) {if (memoryCache == null) {throw new IllegalArgumentException("Memory cache must not be null.");
      }
      if (this.cache != null) {throw new IllegalStateException("Memory cache already set.");
      }
      this.cache = memoryCache;
      return this;
    }

    ...

这些设置就像我们平常使用 AlertDialog 一样,货到到 Builder 之后分别进行设置就行。

ok,我们现在来看看初始化中几个非常重要的变量:
downloader 下载器
首先看下 downloader,builder 中首先判断 downloader 是否为空值,当为空值时就为它初始化默认值,如:

if (downloader == null) {downloader = Utils.createDefaultDownloader(context);
}

来看下 Utils 中是怎么实现 downloader 的初始化的:

Picasso 基本使用和源码完全解析

createDefaultDownloader 方法中首先使用 Java 反射机制来查找项目中是否使用了 OKHttp 网络加载框架,如果使用了则会使用 okhttp 作为图片的加载方式,如果没有使用,则会使用内置的封装加载器 UrlConnectionDownloader。

注:由于 okhttp3 的包名已更换,所以在这里都是使用内置的封装下载器,这个是一个小 bug 等待完善。当修复之后 Picasso+okhttp3 则是最理想的加载方式。
service 线程池
同样的使用,首先判断是否为空,如果为空则初始化默认对象:

if (service == null) {service = new PicassoExecutorService();
}

我们在来看看 PicassoExecutorService 的源码:

Picasso 基本使用和源码完全解析

PicassoExecutorService 直接继承与 ThreadPoolExecutor 线程池,在构造方法中初始化了主线程大小,最大线程等,如:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;

当然,在 Picasso 线程池中主线程和最大线程数是可变的,根据用户使用的网络类型来设置线程数量,后面会详细说明它的应用。
dispatcher 事务分发器
dispatcher 在 Picasso 中扮演着十分重要的角色,可以说它是整个图片加载过程中的中转站,主线程和子线程来回的切换主要都是依赖它的存在。下面我们将来仔细的研究下它的设计,理解好它的存在对整个 Picasso 框架的理解也基本明朗。

首先,来看看它的登场亮相:

Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

它的登场就是创建一个 Dispatcher 对象实例,当然,它传递了在 Builder 中所初始化的对象实例,比如 downloader 下载器,用于图片的下载,当然下载是不能再主线程进行的,所以这里也传递了 service 线程池,而下载好的资源也是不能直接在子线程中进行更新 UI 的,所以同时也把主线程中的 HANDLER 传递进去,同时应用级别的上下文 context,和缓存 cache,状态变化 stats 等也传递进去进行相对应的业务操作。

经过上面的分析,我们可以很清楚的看出,就这么简单的一个创建实例已经很明确的表达出了 Dispatcher 存在的意义,而且我们也明确了它大概的职责。

那么我们接着看看 Dispatcher 的构造方法中具体的做了哪些操作:

Picasso 基本使用和源码完全解析

在 Dispatcher 的构造方法中我把它分为了 5 个部分,下面来详细的解析下:

①:dispatcherThread,它是一个 HandlerThread 线程,如:

static class DispatcherThread extends HandlerThread {DispatcherThread() {super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
    }
  }

它的创建主要是开启一个子线程供 Dispatcher 调用,目的就是为了在子线程中去执行耗时的图片下载操作。

②:对象实例的接受 ,主要是接受在 Picasso 中初始化的对象实例,这个没有什么好说的。

③:创建用于保存数据、对象的集合 ,也就是为了保存对象或状态用的。

④:DispatcherHandler,这是一个 Handler,并且是作用在 dispatcherThread 线程中的 Handler,它用于把在 dispatcherThread 子线程的操作转到到 Dispatcher 中去 ,它的构造方法中接受了 Dispatcher 对象:

Picasso 基本使用和源码完全解析

我们在来看看他的 handleMessage 方法是怎么处理消息的:

@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);
            }
          });
      }
    }

而从它的处理消息的 handleMessage 中我们可以看出,所有的消息并没有被直接进行处理而是转移到了 dispatcher 中,在 dispatcher 中进行相应的处理。

⑤:监听网络变化操作 ,它是用于监听用户手机网络变化而存在的。我们主要来看看 NetworkBroadcastReceiver 这个类:

Picasso 基本使用和源码完全解析

在构造参数中也接收到 Dispatcher 对象,并有注册广播和销毁广播的方法,当然它也没有直接的处理,也是传递到 Dispatcher 中进行消化的。

我们在来看看当用户的网络发生变化时,它会做哪些操作:

Picasso 基本使用和源码完全解析

我们可以看到主要分为两个,一个是航班模式,一个是正常的网络状态变化,航班模式我们先不用理会,主要看下网络变化的操作:

void dispatchNetworkStateChange(NetworkInfo info) {handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info));
}

这里的 handler 就是 DispatcherHandler,那么我们看看它又是怎么做的:

case NETWORK_STATE_CHANGE: {NetworkInfo info = (NetworkInfo) msg.obj;
          dispatcher.performNetworkStateChange(info);
          break;
        }

调用 dispatcher 的 performNetworkStateChange 方法来处理:

Picasso 基本使用和源码完全解析

当网络变化时,它会传递给我们的 PicassoExecutorService 线程池,在 adjustThreadCount 方法中判断用户是使用的那类型网络,如 wifi,4G 等,然后为线程池设置相应的线程数。来看:

void adjustThreadCount(NetworkInfo info) {if (info == null || !info.isConnectedOrConnecting()) {setThreadCount(DEFAULT_THREAD_COUNT);
      return;
    }
    switch (info.getType()) {
      case ConnectivityManager.TYPE_WIFI:
      case ConnectivityManager.TYPE_WIMAX:
      case ConnectivityManager.TYPE_ETHERNET:
        setThreadCount(4);
        break;
      case ConnectivityManager.TYPE_MOBILE:
        switch (info.getSubtype()) {
          case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
          case TelephonyManager.NETWORK_TYPE_HSPAP:
          case TelephonyManager.NETWORK_TYPE_EHRPD:
            setThreadCount(3);
            break;
          case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
          case TelephonyManager.NETWORK_TYPE_CDMA:
          case TelephonyManager.NETWORK_TYPE_EVDO_0:
          case TelephonyManager.NETWORK_TYPE_EVDO_A:
          case TelephonyManager.NETWORK_TYPE_EVDO_B:
            setThreadCount(2);
            break;
          case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
          case TelephonyManager.NETWORK_TYPE_EDGE:
            setThreadCount(1);
            break;
          default:
            setThreadCount(DEFAULT_THREAD_COUNT);
        }
        break;
      default:
        setThreadCount(DEFAULT_THREAD_COUNT);
    }
  }

  private void setThreadCount(int threadCount) {setCorePoolSize(threadCount);
    setMaximumPoolSize(threadCount);
  }

就如上面所说,根据用户使用不同的网络类型分别设置线程的数量,比如当用户使用的是 wifi,线程数量将会设置为 4 个,4G 的话设为 3 个等,这样根据用户的具体情况来设计线程数量是非常人性化的,也是值得我们效仿的。

ok,到此我们重要的 Dispatcher 对象的构造方法已完全的解析完成了,从上面的解析中我们很清楚的看到了 Dispatcher 作为中转站存在的意义,几乎所有的线程转换操作都是由 Dispatcher 来操控的,当然可能还有小伙伴们并不清楚它是怎么运作的,怎么进入子线程的,那是因为我们的讲解还没有进行到进入子线程的步骤而已,下面将会进一步的讲解。

总结下 Dispatcher 中所包含的重要对象实例:

①:PicassoExecutorService 线程池

②:downloader 下载器

③:针对 DispatcherThread 线程的 DispatcherHandler 处理器

④:NetworkBroadcastReceiver 网络监听器

⑤:mainThreadHandler 主线程的 Handler

⑥:保存数据的集合:hunterMap,pausedActions,batch 等

理解好了上面这些对象存在的意义将对下面的理解有着巨大的好处,请没有完全理解的在仔细的阅读一遍(非常重要)。

ok,知道了 Dispatcher 包含哪些重要的对象实例之后,让我们再回到 Picasso 的 Builder 中,在 build 方法中最终返回的是一个 Picasso 的对象实例:

return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,defaultBitmapConfig, indicatorsEnabled, loggingEnabled);

在 Picasso 的构造方法中值得我们只要注意的是针对 requestHandlers 的应用:

Picasso 基本使用和源码完全解析

这里把 Picasso 能都应用的 RequestHandler 都添加到集合中,然后根据具体的需求运用相对应的 Handler 进行业务的处理。

ok,到此 Picasso 中 with 方法中所做的事情已经完完全全的展示在您的面前了,相信您对这一步应用理解的很透彻了。

那么来总结下,Picasso 现在拥有了那些重要的对象实例:

①:dispatcher,重要性不言而喻,上面已充分的展示了它的重要性,作为中转站,Picasso 是必须要拥有了的。最终目的是让我们的主线程进入子线程中去进行耗时的下载操作。

②:requestHandlers,它的存在是为了选择一种请求处理方式,比如说,下载网络图片需要使用 NetworkRequestHandler 这个请求器。

③:HANDLER,这是主线程的 Handler,用来处理返回结果的,比如说图片下载成功后要更新 UI 就是通过它来完成的,这会在后面图片下载完成后更新 UI 时详细讲解

④:一些配置对象实例等,如:缓存 cache,图片加载默认配置 defaultBitmapConfig,状态存储 stats 等等。

load

with 方法中主要是做了一个基础的配置工作,比如 Picasso 的配置,Dispatcher 的配置,这些都是非常重要的前提工作,只有做好了这些配置我们使用起来才能显得毫不费劲。

下面我们就来看看它的应用吧。在 load 方法中需要我们传递一个参数,这个参数可以是 Url,可以是一个 path 路径,也可以是一个文件,一个资源布局等:

①:url
public RequestCreator load(Uri uri) {return new RequestCreator(this, uri, 0);
}

②:path
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));
}

③:file
public RequestCreator load(File file) {if (file == null) {return new RequestCreator(this, null, 0);
    }
    return load(Uri.fromFile(file));
}

④:resourceId
public RequestCreator load(int resourceId) {if (resourceId == 0) {throw new IllegalArgumentException("Resource ID must not be zero.");
    }
    return new RequestCreator(this, null, resourceId);
}

不管传递的是一个什么参数,它都是返回一个请求构造器 RequestCreator,我们来看看它的构造方法做了哪些事情:

Picasso 基本使用和源码完全解析

主要是获取到 Picasso 对象,并 Builder 模式构建一个 Request 中的 Builder 对象:

Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
      this.uri = uri;
      this.resourceId = resourceId;
      this.config = bitmapConfig;
    }

这个 Builder 对象主要接受了一些参数信息,包括 url,资源布局,和默认的图片配置。

由于 load 方法返回的是一个 RequestCreator 对象,所以我们可以使用方法链的形式进行调用其他的方法,比如给请求添加占位图,异常图,转换器等等,具体的可以看源码。

ok,总体来说这 load 方法中主要是创建了一个 RequestCreator 对象,并且 RequestCreator 中构造了一个 Request.Builder 对象。

那么来看看 RequestCreator 拥有哪些重要的对象实例:

①:picasso 对象

②:Request.Builder 对象实例 data,它里面包括我们请求的 URL 地址,资源文件以及图片默认配置。

into

在 load 方法中主要创建了一个 RequestCreator 对象,并获取到了要加载的 url/path/file/resourceId 资源地址路径,那么接下来要做的就是在 into 方法中来加载图片了。先来看看 into 方法中做了哪些事情:

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);
    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 action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }

如上源码可以知道 into 还是做了很多的事情,下面一一解析:

①:checkMain(): 首先检查主否在主线程运行,如果不是在主线程就会抛出一个应该在主线程运行的异常:throw new IllegalStateException(“Method call should happen from the main thread.”); 这说明到这一步还是在主线程运行的。

②:data.hasImage(): 这里的 data 是在之前初始化的 Request.Builder 对象,它里面包含 url 地址,resourceId 和默认配置,这里是判断 uri 或 resourceId 是否为空为 0,如果是的话就取消 imageview 的请求:picasso.cancelRequest(target);

③:deferred: 延迟,如果需要延迟的话就会得到执行,然后会去获取 data 中图片的大小,如果没有的话,就得到 target 的宽高来重新设置要加载的图片的尺寸:data.resize(width, height);

④:createRequest: 在 data.build() 方法中创建 Request 对象,该对象将会包含 uri 或 resourceId 以及默认图片 config。然后在得到的 Request 对象后进行转换,该转换主要是与我们在 Picasso 构建时是否自定义了 RequestTransformer 有关。

⑤:createKey: 它主要的是返回已 String 类型 key,主要目的是建立 ImageView 和 key 的关联,可以通过 key 来获取到 Imageview 的状态,比如说是否已缓存

⑥:shouldReadFromMemoryCache: 看方法名也能知道,它的作用是是否从内存缓存中读取数据,如果是的话,就从缓存中读取数据,假如获取到数据则会取消当前 ImageView 的请求并直接给它设置 Bitmap,而且如果有回调的话将会回调成功的方法。

⑦:ImageViewAction: 构建一个 ImageViewAction 对象,值得注意的是在构建对象时,会把 ImageView 添加到 RequestWeakReference 进行存储,以便于使用时查找,RequestWeakReference 是一个 WeakReference 类型的弱引用。同时 ImageViewAction 也是在完成图片加载时真正更新 UI 的关键类,这在后面会进行详细讲解。

⑧:enqueueAndSubmit: 调用 picasso 的 enqueueAndSubmit 方法进行提交任务。

Picasso 基本使用和源码完全解析

在来看 submit 方法的操作:

void submit(Action action) {dispatcher.dispatchSubmit(action);
}

在这里再次使用到了 dispatcher 任务分发器,把我们的任务 action 提交到 Dispatcher 中进行处理。然后在来看下 dispatchSubmit 方法:

void dispatchSubmit(Action action) {handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
  }

由 Dispatcher 的构造方法我们可以知道此时的 handler 是 DispatcherHandler,那么我们看下它是怎么处理我们的任务行为的:

case REQUEST_SUBMIT: {Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;

在 handleMessage 中直接获取到我们的任务行为 Action,然后调用 performSubmit 方法:

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 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);
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
  }

performSubmit 方法是真正意义上的任务提交的具体地方,我们来解读下它的源码:

①:首先根据 action 的标志来查询是否已存在于暂停列表中,如果存在将会把 action 存放到 pausedActions 的集合列表中,以便等待唤醒请求。

②:然后通过 action 的 key 从 hunterMap 集合中查询是否已存在该 hunterMap 的请求项,如果存在将会调用 hunter 的 attach 方法,进行合并请求,避免一个 ImageView 进行多次的重复请求:

Picasso 基本使用和源码完全解析

把 action 存放到 ArrayList 当中,如果有相同的 action 根据 ArrayList 的不重复性将会保存一个 action,并且更新新的属性值 priority。

③:当线程池 service 没有关闭的时候,通过 forRequest 方法获取一个 Runnable 类型的 BitmapHunter 线程,来看下 forRequest 的源码:

Picasso 基本使用和源码完全解析

分别从 action 和 picasso 获取到 Request 请求和所有的 requestHandlers 请求处理器,然后遍历所有的请求器获取每一个请求处理器,调用 canHandleRequest 尝试看是否该处理器能够处理,来看下 canHandleRequest 方法:

public abstract boolean canHandleRequest(Request data);

它是一个抽象方法,需要到子类去查找,而实现它的子类都是根据以下的约束条件来判断是否可以处理该请求的:

String scheme = data.uri.getScheme();

也就是说通过 url 地址的 Scheme 约束条件进行判断的,而以我们现在的 action 中的 Request 获取到 url,是以 http/https 开头的,那么来看下 NetworkRequestHandler 中的 canHandleRequest 源码:

private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";

@Override 
public boolean canHandleRequest(Request data) {String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }

可以看出正好的匹配,由此得出结论,将要处理我们请求数据的将是 NetworkRequestHandler 处理器。

匹配好了请求处理器,将会返回一个 BitmapHunter 的线程,获取到线程之后会在线程池进行开启线程,并把该线程存放到 hunterMap 线程集合中以便多次请求可以合并相同的线程:

hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);

ok,现在可以到我们的线程池 PicassoExecutorService 中看看它到底是怎么执行的,submit 源码如下:

@Override
  public Future<?> submit(Runnable task) {PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
    execute(ftask);
    return ftask;
  }

用把我们的线程 task 封装到 PicassoFutureTask 中,PicassoFutureTask 是一个更便于我们控住处理的线程,然后调用 execute 开启线程,之后会把我们的线程转移到 addWorker 方法中,在 addWorker 中开启线程 start:

boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {mainLock.unlock();
                }
                if (workerAdded) {t.start();
                    workerStarted = true;
                }

上述源码是执行在 ThreadPoolExecutor 线程池中的,可以看下源码。

当线程开启后,在哪执行呢?

我们知道我们的封装的线程最初始的是 BitmapHunter,那么我们就到它里面来看看是怎么执行的:

Picasso 基本使用和源码完全解析

在 BitmapHunter 的 run 方法中,先修改线程名称,然后执行 hunt 方法,把执行结果存放到 result 中,那我们先看看 hunt 方法是怎么执行的:

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 {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;
  }

解析下源码:

①:还是首先获取到 action 行为对应 key,通过 key 从缓存 cache 中查找 bitmap 是否存在,如果存在修改 stats 状态并直接的把 bitmap 返回。

②:如果缓存中不存在则是要去网络加载 requestHandler.load(); 我们通过上面的分析知道能处理当前 requesr 的 requestHandler 是 NetworkRequestHandler,那么我们去 NetworkRequestHandler 的 load 方法中查看:

Picasso 基本使用和源码完全解析

这里很清晰的可以看到是直接调用 downloader 的 load 方法,而我们的 downloader 在 Picasso 构建 Builder 的时候也很清晰的说明是 UrlConnectionDownloader,那么在去 UrlConnectionDownloader 的 load 方法看看:

protected HttpURLConnection openConnection(Uri path) throws IOException {HttpURLConnection connection = (HttpURLConnection) new URL(path.toString()).openConnection();
    connection.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS);
    connection.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS);
    return connection;
  }

  @Override public Response load(Uri uri, int networkPolicy) throws IOException {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {installCacheIfNeeded(context);
    }

    HttpURLConnection connection = openConnection(uri);
    connection.setUseCaches(true);

    if (networkPolicy != 0) {
      String headerValue;

      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {headerValue = FORCE_CACHE;} else {StringBuilder builder = CACHE_HEADER_BUILDER.get();
        builder.setLength(0);

        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {builder.append("no-cache");
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {if (builder.length() > 0) {builder.append(',');
          }
          builder.append("no-store");
        }

        headerValue = builder.toString();}

      connection.setRequestProperty("Cache-Control", headerValue);
    }

    int responseCode = connection.getResponseCode();
    if (responseCode >= 300) {connection.disconnect();
      throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
          networkPolicy, responseCode);
    }

    long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
    boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));

    return new Response(connection.getInputStream(), fromCache, contentLength);
  }

这里的代码相信我们都很熟悉,就是构建一个 HttpURLConnection 去进行网络请求,当然还有就是根据 networkPolicy 进行一些网络缓存的策略。最后把结果存放到 Response 对象中。

然后 NetworkRequestHandler 的 load 方法又会从 Response 对象中获取数据,并把它存放到 Result 对象中。然后返回给 BitmapHunter 中 hunt 方法的 RequestHandler.Result result 中,从 result 中获取输入流,解析输入流转化为 Bitmap 并返回。

到此我们已从网络中下载了数据,并转化为 bitmap 了,然后在返回我们的 BitmapHunter 中的 run 方法中,在 run 方法中我们获取到了 bitmap,然后调用 dispatcher 进行事物分发,成功获取则调用 dispatchComplete,否则调用 dispatchFailed。

下面我们看看 dispatcher 中的 dispatchComplete 方法:

void dispatchComplete(BitmapHunter hunter) {handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
  }

同样的道理,这里的 handler 还是 DispatcherHandler,那么来看看它的处理:

case HUNTER_COMPLETE: {BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }

转到 dispatcher 的 performComplete 方法中:

Picasso 基本使用和源码完全解析

这里首先把我们的结果保存在 cache 缓存中,然后从 hunterMap 集合中移除 BitmapHunter 对应的 key,原因是请求已完成。然后调用 batch(hunter); 方法:

Picasso 基本使用和源码完全解析

首先判断 BitmapHunter 是否已取消,然后把 BitmapHunter 存放在一个 List< BitmapHunter > batch 集合中,最后通过 DispatcherHandler 发送一个空的延迟消息,目的是为了延迟下下一个网络加载以便处理当前的 bitmap 工作,来看下它是怎么处理的:

case HUNTER_DELAY_NEXT_BATCH: {dispatcher.performBatchComplete();
          break;
        }

进入 performBatchComplete 中查看:

void performBatchComplete() {List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }

performBatchComplete 中首先获取存放 BitmapHunter 的集合,然后调用 mainThreadHandler 发送消息。

还记得 mainThreadHandler 是怎么吗?

在 Picasso 类中构建 Builder 的 build 方法中,创建一个 Dispatcher 对象,里面传进一个 HANDLER 的参数,请看:

Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

这个 HANDLER 就是 mainThreadHandler。

那么它发送的消息是怎么处理的呢?

Picasso 基本使用和源码完全解析

获取到 BitmapHunter 集合进行遍历,然后直接调用 Picasso 中的 complete 方法:

void complete(BitmapHunter hunter) {Action single = hunter.getAction();
    List<Action> 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();
    LoadedFrom from = hunter.getLoadedFrom();

    if (single != null) {deliverAction(result, from, single);
    }

    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);
    }
  }

在 complete 方法中,直接从 BitmapHunter 中获取原已封装的 Action,Uri,Bitmap 等等数据信息,然后调用 deliverAction 方法:

Picasso 基本使用和源码完全解析

这里进行一系列的判断,当 action 没有取消,并且 Bitmap 不为空时,将会调用 action.complete(result, from); 来完成操作。

那还记得我们在创建 action 时的操作吗?

Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

那么很清晰的就知道我们的 Action 其实就是 ImageViewAction 呢

那么我们来看下 ImageViewAction 的 complete 是怎么操作的呢?

Picasso 基本使用和源码完全解析

target 就是我们在创建 ImageViewAction 时传递的 ImageView,现在获取到它,然后调用 PicassoDrawable.setBitmap 方法来完成设置图片:

Picasso 基本使用和源码完全解析

当我们看到 target.setImageDrawable(drawable); 时,是不是终于松了一口气呢,终于看到了为 ImageView 设置图片的信息了。

好了,到了这一步整个 Picasso 加载图片的源码执行流程就已完全的解析完毕了,相信您可以非常清楚的了解了整个框架的加载过程,同时我也相信可能很少人能做到我这么详细的完完全全的分析整个执行过程,而且整个过程中并没有给大家留下什么盲点,是真真正正的源码大解析。

阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配

腾讯云新客低至 82 元 / 年,老客户 99 元 / 年

代金券:在阿里云专用满减优惠券

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-07-24发表,共计23209字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19350
评论数
4
阅读量
7966562
文章搜索
热门文章
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见 zabbix!轻量级自建服务器监控神器在 Linux 的完整部署指南 在日常运维中,服务器监控是绕不开的...
飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛 NAS 中安装 Navidrome 音乐文件中文标签乱码问题解决、安装 FntermX 终端 问题背景 ...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
多服务器管理神器 Nexterm 横空出世!NAS/Win/Linux 通吃,SSH/VNC/RDP 一站式搞定

多服务器管理神器 Nexterm 横空出世!NAS/Win/Linux 通吃,SSH/VNC/RDP 一站式搞定

多服务器管理神器 Nexterm 横空出世!NAS/Win/Linux 通吃,SSH/VNC/RDP 一站式搞...
在Windows系统中通过VMware安装苹果macOS15

在Windows系统中通过VMware安装苹果macOS15

在 Windows 系统中通过 VMware 安装苹果 macOS15 许多开发者和爱好者希望在 Window...
支付宝、淘宝、闲鱼又双叕崩了,Cloudflare也瘫了连监控都挂,根因藏在哪?

支付宝、淘宝、闲鱼又双叕崩了,Cloudflare也瘫了连监控都挂,根因藏在哪?

支付宝、淘宝、闲鱼又双叕崩了,Cloudflare 也瘫了连监控都挂,根因藏在哪? 最近两天的互联网堪称“故障...
2025年11月28日-Cloudflare史诗级事故:一次配置失误,引爆全球宕机

2025年11月28日-Cloudflare史诗级事故:一次配置失误,引爆全球宕机

2025 年 11 月 28 日 -Cloudflare 史诗级事故: 一次配置失误,引爆全球宕机 前言 继今...
仅2MB大小!开源硬件监控工具:Win11 无缝适配,CPU、GPU、网速全维度掌控

仅2MB大小!开源硬件监控工具:Win11 无缝适配,CPU、GPU、网速全维度掌控

还在忍受动辄数百兆的“全家桶”监控软件?后台偷占资源、界面杂乱冗余,想查个 CPU 温度都要层层点选? 今天给...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
还在找免费服务器?无广告免费主机,新手也能轻松上手!

还在找免费服务器?无广告免费主机,新手也能轻松上手!

还在找免费服务器?无广告免费主机,新手也能轻松上手! 前言 对于个人开发者、建站新手或是想搭建测试站点的从业者...
恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击

恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击

恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击 PHP-FPM(FastCGl Process M...
开发者福利:免费 .frii.site 子域名,一分钟申请即用

开发者福利:免费 .frii.site 子域名,一分钟申请即用

  开发者福利:免费 .frii.site 子域名,一分钟申请即用 前言 在学习 Web 开发、部署...
小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比

小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比

小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比 星哥玩云,带你从小白到上云高手。今天咱们就来聊聊——什...
颠覆 AI 开发效率!开源工具一站式管控 30+大模型ApiKey,秘钥付费+负载均衡全搞定

颠覆 AI 开发效率!开源工具一站式管控 30+大模型ApiKey,秘钥付费+负载均衡全搞定

  颠覆 AI 开发效率!开源工具一站式管控 30+ 大模型 ApiKey,秘钥付费 + 负载均衡全...