Picasso-源码解析(一)

作者: javalong | 来源:发表于2018-12-06 18:29 被阅读4次

    前言

    使用的是picasso最新版本
    github地址:https://github.com/square/picasso
    版本:2.71828

    简单例子

    image.png
    代码
    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            Picasso.get().setIndicatorsEnabled(true)
            Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest)
            Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest2)
        }
    

    代码很简单,令大家比较惊讶的应该是左上角的蓝三角,其实原图是没有的,由于我加入了Picasso.get().setIndicatorsEnabled(true),打开了指示标志。

    这里先直接说明下代表的意思,后面我们再慢慢深入。
    Picasso.java

    public enum LoadedFrom {
        //内存加载,绿色
        MEMORY(Color.GREEN),
        //磁盘加载,蓝色
        DISK(Color.BLUE),
        //网络加载,红色
        NETWORK(Color.RED);
    
        final int debugColor;
    
        LoadedFrom(int debugColor) {
          this.debugColor = debugColor;
        }
      }
    

    一般来说,绝大多数的图片框架都是三级缓存,Picasso也不例外。 Glide,Fresco我还未深入了解,但是Picasso这个标识还是很有用的。很容易让我们能够明白是哪种加载方式。

    先简单的说明下这是如何去实现的。

    PicassoDrawable.java

     @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);
            }
    
            // setAlpha will call invalidateSelf and drive the animation.
            int partialAlpha = (int) (alpha * normalized);
            super.setAlpha(partialAlpha);
            super.draw(canvas);
            super.setAlpha(alpha);
          }
        }
    
      //前面都是绘制原图的
        if (debugging) {
          //这里判断下,绘制下标识
          drawDebugIndicator(canvas);
        }
      }
    
     private void drawDebugIndicator(Canvas canvas) {
        DEBUG_PAINT.setColor(WHITE);
        Path path = getTrianglePath(0, 0, (int) (16 * density));
        canvas.drawPath(path, DEBUG_PAINT);
        //根据加载方式
        DEBUG_PAINT.setColor(loadedFrom.debugColor);
        path = getTrianglePath(0, 0, (int) (15 * density));
        canvas.drawPath(path, DEBUG_PAINT);
      }
    

    源码解析

    前面只是简单的介绍了一下Picasso的一个小功能,下面还是通过上面那个简单的加载图片代码,一步步跟入源码,来介绍下是如何实现图片加载的,如何做到三级缓存的。

    Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest)
    
    1. get
      Picasso.java
    public static Picasso get() {
        if (singleton == null) {
          synchronized (Picasso.class) {
            if (singleton == null) {
              if (PicassoProvider.context == null) {
                throw new IllegalStateException("context == null");
              }
              singleton = new Builder(PicassoProvider.context).build();
            }
          }
        }
        return singleton;
      }
    public Picasso build() {
          Context context = this.context;
    
          if (downloader == null) {
            downloader = new OkHttp3Downloader(context);
          }
          if (cache == null) {
            cache = new LruCache(context);
          }
          if (service == null) {
            service = new PicassoExecutorService();
          }
          if (transformer == null) {
            transformer = RequestTransformer.IDENTITY;
          }
    
          Stats stats = new Stats(cache);
    
          Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
    
          return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
              defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
        }
    

    非常简单的一个单例模式,和建造者模式。单例模式就不过多说了,这里主要介绍下建造者模式,一般来说对于参数比较多的构造方法,使用建造者模式,就可以直接使用链式的方式,来配置对象。

    这里直接使用Picasso.get其实是获取了默认的一个Picasso对象,然后帮你默认的配置了LruCache,PicassoExecutorService,RequestTransformer,OkHttp3Downloader,Stats,Dispatcher

    很显然,一般来说,肯定是会提供一个自定义的方式,不然就太low了。

    public static void setSingletonInstance(@NonNull Picasso picasso) {
        if (picasso == null) {
          throw new IllegalArgumentException("Picasso must not be null.");
        }
        synchronized (Picasso.class) {
          if (singleton != null) {
            throw new IllegalStateException("Singleton instance already exists.");
          }
          singleton = picasso;
        }
      }
    

    你可以使用Picasso.Builder先自己构建一个Picasso对象,然后再调用这个方法,接下来就可以使用Picasso.get()来获取自己的配置的单例了。

    1. load
      load方法有很多重载,这里还是以String为例子。
    public RequestCreator load(@Nullable 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));
      }
    public RequestCreator load(@Nullable Uri uri) {
        return new RequestCreator(this, uri, 0);
      }
    

    很显然,load方法只是为了获取一个RequestCreator对象。

    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里面最重要的,其实就是data也就是一个Request.Builder,从这里其实我们很明显的可以看出,RequestCreator,顾名思义,就是为了创建一个Request,最终的Request肯定是由data.build生成的。但是目前只是new了一个Request.Builder对象,并没有调用。这是因为后面我们还需要往Request.Builder塞入很多不同的参数。

    image.png

    由上图其实我们可以发现,我们常用的一些链式方法,如centerCrop等,其实就是调用了Request.Builder对象的方法,只是为了构建一个Request.

    1. into
      这里才是真正发起请求的地方。
    public void into(ImageView target) {
        into(target, null);
      }
    public void into(ImageView target, Callback callback) {
        long started = System.nanoTime();
        //判断下是否为主线程
        checkMain();
    
        if (target == null) {
          throw new IllegalArgumentException("Target must not be null.");
        }
        //如果uri为空或者resId为0,则直接取消请求,设置为placeholder图片
        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);
        }
    
        //简单理解,就是调用了data.build(),生成一个Request
        Request request = createRequest(started);
        //这里通过request生成一个String,用来后面key-value保存图片在LruCache中
        String requestKey = createKey(request);
        
        if (shouldReadFromMemoryCache(memoryPolicy)) {
          //如果前面请求过了,会缓存到内存,这边再请求,还是会生成了相同的key,直接从cache中获取到了Bitmap
          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;
          }
        }
        //没有从内存中获取到缓存,先设置placeholder图片
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        //创建一个action
        Action action =
            new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                errorDrawable, requestKey, tag, callback, noFade);
         //提交一个action
        picasso.enqueueAndSubmit(action);
      }
    

    这里其实非常简单的分析了下。

    这里面有2步单独拿出来说。

    1. deferred的作用
     public RequestCreator fit() {
        deferred = true;
        return this;
      }
    
      /** Internal use only. Used by {@link DeferredRequestCreator}. */
      RequestCreator unfit() {
        deferred = false;
        return this;
      }
    
    ....
    if (deferred) {
          if (data.hasSize()) {
            throw new IllegalStateException("Fit cannot be used with resize.");
          }
          int width = target.getWidth();
          int height = target.getHeight();
          //如果说imageview本身已经可以获取到宽高了,都不是0,那么就直接resize一下图片,如果说有一个是0,说明这个Imageview可能还没有布局完成,还没有自己的宽高,那么就在原来的`RequestCreator`外面再包了一层`DeferredRequestCreator `
          if (width == 0 || height == 0) {
            if (setPlaceholder) {
              setPlaceholder(target, getPlaceholderDrawable());
            }
            picasso.defer(target, new DeferredRequestCreator(this, target, callback));
            return;
          }
          data.resize(width, height);
        }
    

    用过了fit方法的人应该知道,调用后可以适配ImageView的尺寸,这里就是实现方式

    下面我们来看看DeferredRequestCreator是如何实现的

    DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {
        this.creator = creator;
        this.target = new WeakReference<>(target);
        this.callback = callback;
        //实现很简单,就是给ImageView设置下监听
        target.addOnAttachStateChangeListener(this);
        if (target.getWindowToken() != null) {
          onViewAttachedToWindow(target);
        }
      }
    
      @Override public void onViewAttachedToWindow(View view) {
        view.getViewTreeObserver().addOnPreDrawListener(this);
      }
    
    
    //这里才是最关键的部分
     @Override public boolean onPreDraw() {
        ImageView target = this.target.get();
        if (target == null) {
          return true;
        }
    
        ViewTreeObserver vto = target.getViewTreeObserver();
        if (!vto.isAlive()) {
          return true;
        }
    
        int width = target.getWidth();
        int height = target.getHeight();
    
        if (width <= 0 || height <= 0) {
          return true;
        }
    
        target.removeOnAttachStateChangeListener(this);
        vto.removeOnPreDrawListener(this);
        this.target.clear();
        //获取到了ImageView的宽高后,调用resize重新设置了下宽高。
        this.creator.unfit().resize(width, height).into(target, callback);
        return true;
      }
    
    1. 真正去加载图片的地方
    public void into(ImageView target, Callback callback) {
        ...
        Action action =
            new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                errorDrawable, requestKey, tag, callback, noFade);
    
        picasso.enqueueAndSubmit(action);
      }
    
    
    /**下面是每一步的方法***/
    
    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.dispatchSubmit(action);
      }
    
    void dispatchSubmit(Action action) {
        handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
      }
    
     @Override public void handleMessage(final Message msg) {
          switch (msg.what) {
            case REQUEST_SUBMIT: {
              Action action = (Action) msg.obj;
              dispatcher.performSubmit(action);
              break;
            }
        ...
    
     void performSubmit(Action action) {
        performSubmit(action, true);
      }
    
    
    

    前面其实讲到了这里,我们再往后继续。从enqueueAndSubmit一步步往下,虽然调用了很多方法,但是最终,其实就是调用Dispatcher中的performSubmit方法。下面我们来具体分析下这个方法。

    void performSubmit(Action action, boolean dismissFailed) {
       ...
       
        BitmapHunter hunter = hunterMap.get(action.getKey());
        if (hunter != null) {
          hunter.attach(action);
          return;
        }
    
        ...
       //一开始hunterMap肯定不包含action的key,所以会创建一个BitmapHunter
        hunter = forRequest(action.getPicasso(), this, cache, stats, action);
       //其实我们会发现BitmapHunter是一个Runnable,service是ExecutorService,可以理解为一个线程池,这里就直接执行一个Runnable
        hunter.future = service.submit(hunter);
        hunterMap.put(action.getKey(), hunter);
      ...
      }
    
    
    //通过传入的参数生成一个BitmapHunter
    static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
          Action action) {
        Request request = action.getRequest();
        List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
    
        for (int i = 0, count = requestHandlers.size(); i < count; i++) {
          RequestHandler requestHandler = requestHandlers.get(i);
            //最重要的地方在这里,遍历所有的requestHandler,看哪个requestHandler能够处理request,后面再详细介绍
          if (requestHandler.canHandleRequest(request)) {
            return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
          }
        }
    
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
      }
    

    为了让后面我们可以更好的理解,我们先回过头来,看一下requestHandlers是什么东西,为什么要先找出能够处理当前RequestRequestHandler.

    一直往前找发现是在Picasso的构造方法里面初始化的

    Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
          RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
          Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
      ...
        List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
        //resource图片处理,比如R.drawable这种
        allRequestHandlers.add(new ResourceRequestHandler(context));
        if (extraRequestHandlers != null) {
            //自定义处理
          allRequestHandlers.addAll(extraRequestHandlers);
        }
      //联系人图片处理
        allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
        //媒体资源处理
        allRequestHandlers.add(new MediaStoreRequestHandler(context));
      //流资源处理
        allRequestHandlers.add(new ContentStreamRequestHandler(context));
      //asset资源处理
        allRequestHandlers.add(new AssetRequestHandler(context));
      //文件资源处理
        allRequestHandlers.add(new FileRequestHandler(context));
      //网络资源处理
        allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
      ...
      }
    

    都是通过load方法之后的参数来判断的。我们这里以NetworkRequestHandler为例

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

    如果说uri是http或者https就可以由NetworkRequestHandler来处理。

    那么我们继续回到刚才那个地方

    void performSubmit(Action action, boolean dismissFailed) {
    ...
        hunter = forRequest(action.getPicasso(), this, cache, stats, action);
       //其实我们会发现BitmapHunter是一个Runnable,service是ExecutorService,可以理解为一个线程池,这里就直接执行一个Runnable
        hunter.future = service.submit(hunter);
        hunterMap.put(action.getKey(), hunter);
      ...
      }
    

    获取到可以处理RequestHandler之后创建了一个BitmapHunter,然后调用service.submit最终其实是调用Runnablerun方法,我们继续跟入。

    @Override public void run() {
        try {
          ...
          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;
          }
        }
      
        networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    
        //前面其实我们已经分析过,requestHandler其实是NetworkRequestHandler,等下单独提出load方法来讲
        RequestHandler.Result result = requestHandler.load(data, networkPolicy);
        if (result != null) {
          loadedFrom = result.getLoadedFrom();
          //获取下exif格式信息,一般情况用不到,这里不深入
          exifOrientation = result.getExifOrientation();
          //获取到真正的bitmap
          bitmap = result.getBitmap();
          if (bitmap == null) {
            Source source = result.getSource();
            try {
              bitmap = decodeStream(source, data);
            } finally {
              try {
                source.close();
              } catch (IOException ignored) {
              }
            }
          }
        }
    
        //下面一大串其实是对原来的图片进行一些变换,这里先不深入
        if (bitmap != null) {
         ...
          if (data.needsTransformation() || exifOrientation != 0) {
            synchronized (DECODE_LOCK) {
              if (data.needsMatrixTransform() || exifOrientation != 0) {
                bitmap = transformResult(data, bitmap, exifOrientation);
                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;
    
    

    下面我们还是具体再看看NetworkRequestHandler

    @Override public Result load(Request request, int networkPolicy) throws IOException {
        okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
        Response response = downloader.load(downloaderRequest);
        ResponseBody body = response.body();
    
        if (!response.isSuccessful()) {
          body.close();
          throw new ResponseException(response.code(), request.networkPolicy);
        }
        //从这里可以看出磁盘缓存
        Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
        if (loadedFrom == DISK && body.contentLength() == 0) {
          body.close();
          throw new ContentLengthException("Received response with 0 content-length header.");
        }
        if (loadedFrom == NETWORK && body.contentLength() > 0) {
          stats.dispatchDownloadFinished(body.contentLength());
        }
        return new Result(body.source(), loadedFrom);
      }
    

    其实这里最关键的部分就是response.cacheResponse()这一句代码。因为之前我一直以为Picasso使用的是DiskLruCache来进行磁盘缓存。但是一直找不到实现的地方。一直找到这里才恍然大悟,Picasso的磁盘缓存是利用http协议中的cache-control去实现的。
    然后使用的其实是Okhttp3实现了http协议,其中磁盘缓存确实也是用DiskLruCache来实现的。

    总结

    后面还会继续深入。

    相关文章

      网友评论

        本文标题:Picasso-源码解析(一)

        本文链接:https://www.haomeiwen.com/subject/ymagcqtx.html