A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 我是楠楠 于 2017-8-17 16:49 编辑

      前面写的5篇文章让我们对Picasso的用法有了很详细的了解,Android的框架层出不穷,每年都会开源出大量的优秀的框架,如果我们只是站在一个使用者的角度去不断的接触框架,而不去学习框架底层的实现原理,那样顶多也只能一直当新手,而不是老司机了,下面我们就从最基本的入手,从源码的角度去剖析Picasso的工作原理(该分析都是基于Picasso 2.5.2版本).Let’s Go.  

        官方给出的一个最简单的用法,一个链式调用就能实现一张图片的请求及展示:


  • <font color="rgb(85, 85, 85)">Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);</font>

复制代码

        只需这一行代码,Picasso就能从我们提供的URL地址中去请求图片,然后将图片展示到targetImageView中,我们就从最简单的开始,一层层的还原Picasso最底层的本质.


Picasso.with(context)
  • public static Picasso with(Context context) {
  •     if (singleton == null) {
  •       synchronized (Picasso.class) {
  •         if (singleton == null) {
  •           singleton = new Builder(context).build();
  •         }
  •       }
  •     }
  •     return singleton;
  •   }

复制代码

这段代码就是为了初始化Picasso,获取一个Picasso的实例,该类代码具有很大的共性,很多框架在获取实例时基本都是采用了 单例模式+建造者模式

  • 单例模式特点:
    • 采用后加载机制,保证实例只有在被使用的时候才被创建
    • 采用双重检验锁模式,保证了在多个线程同时请求的过程中,只创建唯一的单例对象
    • 同步对象为类本身,保证了该对象的同步范围在整个java虚拟机中,也就是全局的
  • 利用建造者模式创建一个默认的Picasso对象,一般来说使用建造者模式,是因为构建该对象本身时需要创建其他的类对象,也是保证了多个类的初始化,看代码new Builder(context).build()

  • <font color="rgb(85, 85, 85)">/** Create the {@link Picasso} instance. */
  •     public Picasso build() {
  •       Context context = this.context;
  •       if (downloader == null) {
  •         downloader = Utils.createDefaultDownloader(context);
  •       }
  •       if (cache == null) {
  •         cache = new LruCache(context);
  •       }
  •       if (service == null) {
  •         service = new PicassoExecutorService();
  •       }
  •       if (transformer == null) {
  •         transformer = RequestTransformer.IDENTITY;
  •       }
  •       Stats stats = new Stats(cache);
  •       picasso.Dispatcher dispatcher = new picasso.Dispatcher(context, service, HANDLER, downloader, cache, stats);
  •       return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
  •           defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
  •     }
  •   }</font>

复制代码

在该方法中主要对初始化了以下参数:

  • Downloader
    • 下载工具类,该Utils.createDefaultDownloader(context);如果OKHttp可用的话,会返回一个OkHttpDownloader,否则的话会返回一个UrlConnectionDownloader(内部使用HttpURLConnection实现)
  • Cache

    • 该类的默认实现为LruCache,也就是利用最近最少使用算法进行缓存,内部是一个LinkedHashMap,这里要注意的是,一般我们在初始化LinkedHashMap时,会限制initialCapacity容量值,但此处是在初始化LruCache时通过调用Utils.calculateMemoryCacheSize(context)来计算出最大的堆内存值然后划分出7分之一,也就是15%作为容量大小,所以在每次调用set()方法进行缓存时,都会调用trimToSize()不停的计算当前可使用的空间大小,超出范围就删除之前保存的数据;
    • Utils.calculateMemoryCacheSize(context)的代码块



  • <font color="rgb(85, 85, 85)">static int calculateMemoryCacheSize(Context context) {
  • ActivityManager am = getService(context, ACTIVITY_SERVICE);
  • //计算出最大的堆内存值,也就是可用大小值
  • boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
  • int memoryClass = am.getMemoryClass();
  • if (largeHeap && SDK_INT >= HONEYCOMB) {
  • memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
  • }
  • //分配各15%的大小
  • // Target ~15% of the available heap.
  • return 1024 * 1024 * memoryClass / 7;
  • }</font>

复制代码

  • ExecutorService

    • 默认实现类PicassoExecutorService,也就是ThreadPoolExecutor子类而已,默认线程数是3,但是PicassoExecutorService会在不同的网络状态下调整线程数,wifi下为4个线程,4G状态为3个线程,3G状态下为2个线程,2G状态下为1个线程.
  • RequestTransformer
  • Stats
    • 主要记录一些信息,比如下载的总大小值,缓存大小值,在之前的博客中有用到过,详见Picasso最全使用教程 四,用到了StatsSnapshot
  • Dispatcher

    • 负责分发和处理 Action,包括提交、暂停、继续、取消、网络状态变化、重试等等。这个类是我们着重要分析的类,我们先来看一下默认的操作都有哪些

      • 在构造方法中,将之前的ExecutorService,Downloader,Cache,Stats进行了赋值
      • 在构造方法第一行就创建了DispatcherThread类,启动了一个线程
      • 在构造方法中创建一个DispatcherHandler,并将DispatcherThread.getLooper()作为参数传入该handler的构造函数中,意味着该Handler处理的数据都是从DispatcherThread发出的
      • 初始化并添加了一个对网络监听的广播


      上面就是关于.with(context)所做的分析,我们继续向下分析.


     .load(String url)方法源码解析

     我们现在看.load(String url)方法都做了什么操作,其实.load(url)最后调用的依然是.load(Uri uri)


  • <font color="rgb(85, 85, 85)">public RequestCreator load(String path) {
  •    //对path进行判空,省略部分代码
  •     ....
  •     return load(Uri.parse(path));
  •   }
  • //其实调用的是这个方法
  • public RequestCreator load(Uri uri) {
  •     return new RequestCreator(this, uri, 0);
  •   }</font>

复制代码

       所以加载图片的本质就是我们通过Picasso调用了load(url)返回了一个new出来的RequestCreator示例,同时将Picasso ,uri地址, 还有resourceId也就是0,作为构造参数传入,下面我们就看看该构造函数都做了哪些操作;



  • <font color="rgb(85, 85, 85)"> 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);
  •   }</font>

复制代码

  • 先判断Picasso是否已经启动,如果没有启动,就直接抛出异常
  • 对picasso进行赋值
  • 这里又用到了建造者模式使用Request.Builder()将创建的Builder赋值给data,我们来看该部分的代码

    • 代码如下:

    • this.uri = uri;
    •   this.resourceId = resourceId;
    •   this.config = bitmapConfig;
    • }

    复制代码

    我们看到,这里只是做了赋值操作,并没有做其他事,Builder还有其他几个构造方法,也都只进行了赋值操作,并未做其他处理,等我们分析完之后,就会明白此处代码的用意. 贴出来两个,另一个Builder (Request request)就不在贴出,部分代码如下
  • public Builder(Uri uri) { setUri(uri); }
  • public Builder(int resourceId) { setResourceId(resourceId); }

复制代码


最重要的方法.into(target)方法解析

.into(target)方法是ReqeustCreator中的方法,所以我们去一窥究竟,其实.into(target)还是在内部调用的.into(target,callback),看代码



  • <font color="rgb(85, 85, 85)">public void into(ImageView target) {
  •     into(target, null);
  •   }</font>

复制代码

下面我们就着重看一下.into(target,callback)都做了哪些操作,代码较多,我们逐一进行分析



  • <font color="rgb(85, 85, 85)">public void into(ImageView target, Callback callback) {
  •     long started = System.nanoTime();
  •     //1 检测是否在主线程中执行,如果不是就抛异常
  •     checkMain();
  •     if (target == null) {
  •       throw new IllegalArgumentException("Target must not be null.");
  •     }
  •     //2 该判断是只有在uri为null,并且resourceId为0的情况下才会执行相应逻辑,取消请求显示默认图片,然后结束
  •     if (!data.hasImage()) {
  •       picasso.cancelRequest(target);
  •       if (setPlaceholder) {
  •         setPlaceholder(target, getPlaceholderDrawable());
  •       }
  •       return;
  •     }
  •     //3 是否延时加载,默认为false,只有当调用了 .fit() 才会执行该逻辑
  •     if (deferred) {
  •       //调用 .fit() 之前,不能调用 .resize(w,h) ,否则抛出异常
  •       if (data.hasSize()) {
  •         throw new IllegalStateException("Fit cannot be used with resize.");
  •       }
  •       int width = target.getWidth();
  •       int height = target.getHeight();
  •       //target是否已经完全绘制出来,如果没有,则执行相关操作
  •       if (width == 0 || height == 0) {
  •         //是否设置了默认图片,在进行延时请求前先展示默认图片
  •         if (setPlaceholder) {
  •           setPlaceholder(target, getPlaceholderDrawable());
  •         }
  •         // 加入延时队列,同时创建一个DeferredRequestCreator去测量target的宽高,结束任务
  •         picasso.defer(target, new DeferredRequestCreator(this, target, callback));
  •         return;
  •       }
  •       //target已经绘制完成,调用resize方法,并继续向下执行
  •       data.resize(width, height);
  •     }
  •     //4 创建一个request,并根据request生产一个requestKey
  •     Request request = createRequest(started);
  •     String requestKey = createKey(request);
  •     //5 判断可否从内存中读取资源,默认为可以
  •     if (shouldReadFromMemoryCache(memoryPolicy)) {
  •       //从内存中获取,如果能获取到就取消请求操作,并执行callback.onSuccess()的回调,结束任务
  •       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;
  •       }
  •     }
  •     //6 判断是否设置了默认图片,在进行请求前先展示默认图片
  •     if (setPlaceholder) {
  •       setPlaceholder(target, getPlaceholderDrawable());
  •     }
  •     //7 这时是的确需要联网请求了,生成对应的 ImageViewAction,并将 action 添加到 Picasso的请求队列中
  •     Action action =
  •         new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
  •             errorDrawable, requestKey, tag, callback, noFade);
  •     picasso.enqueueAndSubmit(action);
  •   }</font>

复制代码



部分功能说明已在代码中标出,但还是用一张示意图来进行解析比较简单易懂


图画的比较丑,但是看着还算比较清晰,我们在按照代码,图示,再用文字描述一遍,让更有深的认识;

  • 检测是否在主线程中执行,如果不是就抛异常,可能是要更新UI显示,所以要在主线程中执行代码逻辑
  • 该判定方法data.hasImage()容易让人误解为是否已经有图片获取好了,但其实是为了判断在Builder创建的时候,是否设置了uri或者resourceId,如果没有设置uri并且resourceId为0,说明该请求是一个错误请求,就可以加载默认图片并取消请求操作;
  • 是否延时加载,默认为false,只有当调用了 .fit() 才会执行该逻辑,如果有同学不太理解.fit()的作用,可翻看我之前写的博客,其实就是为了重新计算target的宽高值,而且不能在调用了.resize()之后再次调用.fit()
  • 创建一个request,并根据request生产一个requestKey
  • 判断可否从内存中读取资源,默认为true,如果有同学不知道怎么设置跳过内存读取这一步骤,可翻看我之前写的博客
  • 判断是否设置了默认图片,在进行请求前先展示默认图片
  • 这时是的确需要联网请求了,生成对应的 ImageViewAction,并将 action 添加到 Picasso的请求队列中

OK,到现在我们已经大致了解了Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);都做了哪些操作,至于第7步之后又做了哪些操作,我们将继续分析,还原她的本质,愿大家都有美好的一天…

1 个回复

倒序浏览
多谢分享
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马