Android开发学习之Picasso源码简析

介绍

Picasso(毕加索)是时下网上比较流行的开源图片加载库,性能不错,使用也方便,调用时只需要一行代码就可以:
Picasso.with(context).load(imageUrl).into(imageView);

现在,我就三个方法,看一下源码怎么实现的

with()

源码如下

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

构造了一个Bulder的单例,点进去Builder的构造方法和build()方法看看

Builder构造方法
public Builder(Context context) {
      if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
      }
      this.context = context.getApplicationContext();
}
可见毕加索为了防止内存泄漏,用了应用的上下文

build()方法
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);

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

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

构造了一堆组件,他们的作用根据名字就能猜到,留意一下里面的HANDLER,它是主线程的HANDLER,用来更新UI,定义如下
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
    @Override public void handleMessage(Message msg) {
      ...//处理图片下载结果,一会儿再说
    }
 };

再就是那个dispatcher,它负责消息的中转,具体使用也一会儿再说。

这样,我们就通过with()方法构造了一个毕加索对象

load()

load()方法代码如下
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));
}

假设我们的path不为空,就进入了load(Uri uri)方法
public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
}

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


而后又是Builder的构造方法
Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
      this.uri = uri;
      this.resourceId = resourceId;
      this.config = bitmapConfig;
}

原来就是一些属性的赋值,那我们接着看into()方法。不过此时,我们已经获取了一个RequestCreator对象,into()方法是他的方法

into()

into()方法是在RequestCreator类中的,这个方法代码很长,我先贴出来
public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    checkMain(); // 必须是主线程调用

    .. // uri和requestId合法性检查,一般可忽略

    .. // RequestCreator.Builder的resize,一般用不到

    Request request = createRequest(started);
    String requestKey = createKey(request);

    .. // 检查缓存映射,找到bitmap放入target中,并返回成功,

    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable()); // 设置占位图
    }

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

    picasso.enqueueAndSubmit(action);
}


enqueueAndSubmit()

里面最主要的是最后那几行,实例化一个Action对象(其构造方法也是一堆属性的赋值),并调用picasso.enqueueAndSubmit()方法,我们点进这个enqueueAndSubmit()方法:
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);
}

首先尝试从targetToAction这个映射中获取先前的action,如果能获取,就调用cancelExistingRequest()方法,这个方法用来取消前面的请求
而后把新的action保存到映射中
最后调用submit()方法
void submit(Action action) {
    dispatcher.dispatchSubmit(action);
}

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

给handler发送提交消息,这时dispatcher的调度作用就显示出来了
我们先看handler的定义,这是Dispatcher内部的一个handler,定义也是在构造方法中
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);

dispatcherThread是DispatcherThread类的对象,它其实就是一个HandlerThread,只不过给线程起了个新名字罢了
我们看handler怎么处理REQUEST_SUBMIT信息
@Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }
        .. //其他case
}

调用的是dispatcher的performSubmit()方法
void performSubmit(Action action, boolean dismissFailed) {
    .. // 暂停、请求重复、service关闭判断并处理

    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);

    .. // 更新failedActions,打日志
}

主要的就是中间那三行,第一行构造一个BitmapHunter对象,构造方法也是一堆属性的赋值;第二行submit这个hunter,第三行把这个hunter保存到映射中,防止重复请求
我们主要就看第二行那个submit的实现,这个在PicassoExecutorService类中
public Future<?> submit(Runnable task) {
    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
    execute(ftask);
    return ftask;
}

第一行的PicassoFutureTask类就是一个FutureTask类的子类,只不过多了一个BitmapHunter属性,构造方法也是为这个BitmapHunter赋值
第二行的execute(ftask),就是把这个ftask交给线程池调度,这个不在本文的范围,而线程池调度主要就是调用Runnable的run()方法,而BitmapHunter正是实现了Runnable接口,所以下面,我们将重心,放在BitmapHunter的run()方法中
@Override public void run() {
    try {
      .. // 更新线程名+打日志

      result = hunt();

      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
    }
    .. // 异常处理
}


调用hunt()方法获取结果,而后让dispatcher分发结果

hunt()

我们先看hunt()方法
Bitmap hunt() throws IOException {
    Bitmap bitmap = null;

    .. // 缓存处理

    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      .. // 其他字段

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

    .. // 日志、转换、状态更新

    return bitmap;
  }



主要分三步:1、requestHandler.load()获取结果 
      2、根据result获取Bitmap。这里就是获取了mBitmap属性
      3、如果我们传入的是图片网址,这里获取的位图就是空,需要调用decodeStream()解码输入流

我们这儿只看第一步

requestHandler的load()

这个requestHandler属性由构造方法而来,BitmapHunter的构造方法只在它自己的静态方法forRequest()中用到
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    // Index-based loop to avoid allocating an iterator.
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

就是遍历毕加索对象里的requestHandler,找到一个能处理的(canHandleRequest()),返回

canHandleRequest()方法是抽象方法,不同的RequestHandler子类有不同的实现,于是我们主要就在毕加索里找,他在哪里给requestHandlers赋值或添加内容,发现是在构造方法中
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<RequestHandler>(builtInHandlers + extraCount);

    // ResourceRequestHandler needs to be the first in the list to avoid
    // forcing other RequestHandlers to perform null checks on request.uri
    // to cover the (request.resourceId != 0) case.
    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));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

    ...
}

假设我们是传入的是网址,我们就会用NetworkRequestHandler,方才提到的canHandleRequest()方法,就是判断Request参数里的uri,其协议是否是http或https,是的话返回true,这里我就不粘代码了

最主要的是load()方法,我们点进去
@Override public Result load(Request request, int networkPolicy) throws IOException {
    Response response = downloader.load(request.uri, request.networkPolicy);
    .. // 判空

    Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;

    Bitmap bitmap = response.getBitmap();
    .. // 如果用默认方式获取的话,这里的位图是空

    InputStream is = response.getInputStream();
    .. // 判空+结果状态更新
    return new Result(is, loadedFrom);
}

注意,如果我们传入的是图片网址的话,这里我们只能获取图片的输入流。不过我们要先看看downloader.load()方法是怎么获取Response的

downloader的load()
downloader的默认最终来源是在Picasso.Builder的build()方法,代码在文章开始已经展示过了,这里我们只看downloader的赋值相关内容
public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = Utils.createDefaultDownloader(context);
      }
      .. // 其他组件

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

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

调用了createDefaultDownloader()
static Downloader createDefaultDownloader(Context context) {
    try {
      Class.forName("com.squareup.okhttp.OkHttpClient");
      return OkHttpLoaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    return new UrlConnectionDownloader(context);
}

可见,先尝试构造okHttpClient做为downloader,如果没这个类,就用UrlConnectionDownloader做为downloader。
假设我们就没导入okHttp依赖,我们用的是UrlConnectionDownloader,它的load()方法代码如下:
@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);

    .. // 处理networkPolicy不是0的情况,而默认是0

    int responseCode = connection.getResponseCode();
    if (responseCode >= 300) {
      .. // 断连接,抛异常
    }

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

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

其实就是用了我们常用的URLConnection,代码很简单。我再把调用的openConnection()方法代码贴出来,就更清晰了
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;
}

好,返回到NetRequestHandler.load()方法中,这是我们已经获取了response对象,而后获取response中的输入流,赋值到Result对象中,就从NetRequestHandler.load()中返回到BitmapHunter.hunt()方法中,进行输入流解码,顺利的话,就返回到run()方法中了。

handler与dispatcher处理结果

run()方法中,获取的result对象不为空,就调用dispatcher.dispatchComplete()方法
void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}

给handler发送完成消息,handler中相应处理如下:
case HUNTER_COMPLETE: {
    BitmapHunter hunter = (BitmapHunter) msg.obj;
    dispatcher.performComplete(hunter);
    break;
}

调用了dispatcher.performComplete()方法
void performComplete(BitmapHunter hunter) {
    .. // hunter写入缓存,同时移除请求
    batch(hunter);
    .. // 日志
}

调用了batch()方法
private void batch(BitmapHunter hunter) {
    .. // 异常处理
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
}

把hunter参数添加进batch——这个BitmapHunter的list,而后隔200毫秒发送一个next_batch消息给handler(如果消息队列中没有未发送的next_batch消息的话),也就是说每200毫秒处理一批BitmapHunter。

而handler对next_batch的处理如下:
case HUNTER_DELAY_NEXT_BATCH: {
    dispatcher.performBatchComplete();
    break;
}

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

主要是给主线程发送了batch_complete消息,并把bitmapHunter列表传了过去
主线程HANDLER对这个消息的处理如下:
case HUNTER_BATCH_COMPLETE: {
    @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, n = batch.size(); i < n; i++) {
       BitmapHunter hunter = batch.get(i);
       hunter.picasso.complete(hunter);
    }
    break;
}

遍历BitmapHunter列表,调用每个的complete()方法

complete()

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;

    .. // 异常判断+处理

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

    .. // 异常处理
}

不管是处理单个还是多个,都调用了deliverAction()方法
private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
    .. // 异常判断+处理
    if (result != null) {
      .. // 对from的判空
      action.complete(result, from);
      .. // 日志
    } else {
      // .. 异常处理
    }
}

主要就是action.complete()方法,这儿我们还是只分析ImageViewAction中的实现
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    .. // 异常判断+处理

    ImageView target = this.target.get();
    .. // 判空

    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

    .. // 回调
}


调用了PicassoDrawable.setBitmap()来设置位图,代码如下:
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
      Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
    .. // 占位图处理
    PicassoDrawable drawable =
        new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
    target.setImageDrawable(drawable);
}

其实就是把bitmap包装成PicassoDrawable,这个PicassoDrawable就是一个BitmapDrawable,只不过多了一些渲染,但默认的话其实都没有
最后就是把drawable设置为imageView了。

结语

到此,Picasso的源码我们就简单分析过了,其中两个handler(一个实现图片的批处理,一个设置UI)实现的图片批处理、线程池的运用、dispatcher的调度作用等等内容,都值得我们细细回味。

猜你喜欢

转载自blog.csdn.net/qq_37475168/article/details/80062776