Android 图片加载框架 Picasso 基本使用和源码完全解析详解手机开发

说到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:

1

四 MainActivity中点击加载图片

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

2

五 加载图片

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

3

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设置占位图,这是一种友好的显示方式。

4

异常图

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

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

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

5

转换器

不仅如此,我们还可以对加载到的图片进行重新调整,比如改变图片的大小,显示形状等,可以使用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方法是怎么实现的:

6

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

7

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

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

8

这里代码也是很简单,主要是初始化了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 的初始化的:

9

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

注:由于okhttp3的包名已更换,所以在这里都是使用内置的封装下载器,这个是一个小bug等待完善。当修复之后Picasso+okhttp3则是最理想的加载方式。

service 线程池

同样的使用,首先判断是否为空,如果为空则初始化默认对象:

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

我们在来看看PicassoExecutorService的源码:

10

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的构造方法中具体的做了哪些操作:

11

在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对象:

12

我们在来看看他的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这个类:

13

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

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

14

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

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方法来处理:

15

当网络变化时,它会传递给我们的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的应用:

16

这里把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,我们来看看它的构造方法做了哪些事情:

17

主要是获取到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方法进行提交任务。

18

在来看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进行多次的重复请求:

19

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

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

20

分别从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,那么我们就到它里面来看看是怎么执行的:

21

在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方法中查看:

22

这里很清晰的可以看到是直接调用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方法中:

23

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

24

首先判断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。

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

25

获取到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方法:

26

这里进行一系列的判断,当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是怎么操作的呢?

27

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

28

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

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

各位如果还有哪里不明白的,或是我这里讲的还不够透彻,亦或是讲错了的地方请留言指正,让我们共同进步,谢谢



原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/3186.html

(0)
上一篇 2021年7月16日
下一篇 2021年7月16日

相关推荐

发表回复

登录后才能评论