美文网首页
Picasso 图片加载库源码分析6-图片加载流程

Picasso 图片加载库源码分析6-图片加载流程

作者: jkwen | 来源:发表于2021-07-05 18:56 被阅读0次

Picasso 图片加载总体流程

Picasso 图片加载流程.jpg

我将整个请求加载过程分为 3 步。

第一步:创建请求。涉及到 RequestCreator, ImageViewAction, Dispatcher 等。

第二步:线程池执行请求。涉及到 PicassoExecutorService, PicassoFutureTask, BitmapHunter, RequestHandler, OkHttp3Downloader, Dispatcher 等。

第三步:拿到请求结果,对结果的处理和展示。BitmapHunter, Dispatcher, Picasso, PicassoDrawable, ImageViewAction。

第一步,创建请求

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

从 into 方法调用说起,创建了一个 ImageViewAction 类型的对象,关键入参有 target, request, requestKey 等,这个 action 将作为关键数据结构被传递到后面流程。

void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
        cancelExistingRequest(target);
        targetToAction.put(target, action);
    }
    submit(action);
}

通过 Picasso 对象提交这个 action。根据 target (也就是 ImageView 对象)先检查下有没有别的 Action 动作,有的话要做一次清退,然后提交这个新的 action。

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

提交 Action 的动作实际交给了 Dispatcher 对象,这点和 OkHttp 类似。

接下去请注意,这里要切换线程了。

void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler, Downloader downloader, Cache cache, Stats stats) {
    //省略部分代码......
    this.dispatcherThread = new DispatcherThread();
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
}

Dispatcher 类里有个静态类 DispatcherThread 继承自 HandlerThread,还有一个静态类 DispatcherHandler 继承自 Handler,再看看 Dispatcher 构造方法。嗯~ 原来是这样。

当 Dispatcher 提交 Action 时,利用这个 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;
        }
    }
}

Action 对象始终在传递,接下去真的要提交了。

void performSubmit(Action action, boolean dismissFailed) {
    //省略部分代码......
    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
        hunter.attach(action);
        return;
    }
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);
}

创建关键对象 BitmapHunter 对象,并将 hunter 交给 service 进行提交。这个 service 实际就是 PicassoExecutorService,还记得前面说过的这个线程池吗?hunter 是个 Runnable 类型,所以可以作为入参。另外还要注意,前面传递过来的 action,被用来创建 hunter 对象了,所以接下去 hunter 变成了请求数据对象的载体。

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);
        if (requestHandler.canHandleRequest(request)) {
            return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
        }
    }
    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

看下 BitmapHunter 对象的创建,会根据是否有 RequestHandler 能处理来进行创建。根据前面的分析,假设我们想要展示的是一张网络图片,那么这里满足条件的将是 NetworkRequestHandler 。

接下去又要注意了,我们又要切线程了,并且即将进入第二步。

第二步,线程池执行请求

我们说线程池执行请求,实际上是通过线程池分配线程来完成 Runnable 的任务,也就是在线程里执行 BitmapHunter 的 run 方法。

@Override public void run() {
    result = hunt();
    if (result == null) {
        dispatcher.dispatchFailed(this);
    } else {
        dispatcher.dispatchComplete(this);
    }
}

通过 hunt 方法获取到 Bitmap 类型的结果值 result,再通过 Dispatcher 对象分发完成还是失败。

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    if (shouldReadFromMemoryCache(memoryPolicy)) {
        bitmap = cache.get(key);
        if (bitmap != null) {
            stats.dispatchCacheHit();
            loadedFrom = MEMORY;
            return bitmap;
        }
    }
    
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
        bitmap = result.getBitmap();
        if (bitmap == null) {
            Source source = result.getSource();
            bitmap = decodeStream(source, data);
        }
    }
    
    if (bitmap != null) {
        stats.dispatchBitmapDecoded(bitmap);
        if (data.needsTransformation() || exifOrientation != 0) {
            //省略
        }
    }
    return bitmap;
}

根据内存缓存策略还是优先会从缓存里获取,如果取到就不再继续,直接返回结果。如果没有,这时就要通过 requestHandler 对象来处理了。假设这里是 NetworkRequestHandler 类型,那么实际就是一个网络请求,并将请求结果包装成 Result 类型返回。

包装成的 Result 对象里其实并没有 Bitmap 类型的值,需要将网络数据做一次解码,即 decodeStream 操作,这样 bitmap 就有值了。

如果此时图片需要做变换,那就下去就会对 bitmap 做形变之类的操作,这不是我们的重点,就省略了。

对于返回的 Bitmap 类型结果,完成和失败的处理类似,我们以完成为例,进行第三步的分析。

第三步,拿到请求结果,对结果的处理和展示

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

又回到了 Dispatcher 进行消息的发送,显然这里又要切线程了,接下去的工作将交给 DispatcherThread 线程处理。

@Override public void handleMessage(final Message msg) {
    switch (msg.what) {
        case HUNTER_COMPLETE: {
            BitmapHunter hunter = (BitmapHunter) msg.obj;
            dispatcher.performComplete(hunter);
            break;
        }
    }
}

传过来的数据对象是 hunter,这样来说可以最大限度的引用到一些相关的东西。

void performComplete(BitmapHunter hunter) {
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
        cache.set(hunter.getKey(), hunter.getResult());
    }
    hunterMap.remove(hunter.getKey());
    batch(hunter);
}

如果需要缓存,这里就会把图片缓存到内存里,缓存的机制就是前面说的 LruCache。接着从 hunterMap 里移除,还记得前面 performSubmit 的时候会把这个 hunter 给加进去。最后调用 batch 方法,我的理解是,可能图片请求有好多个,毕竟用了线程池,同时可以有 3 个请求,这样一来有可能他们几乎同时完成,那么在展示上就让他们作为一批进行处理。

private void batch(BitmapHunter hunter) {
    if (hunter.isCancelled()) {
        return;
    }
    if (hunter.result != null) {
        hunter.result.prepareToDraw();
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
        handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
}

batch 是个 BitmapHunter 类型的 List 集合,hunter 被添加之后,如果没有 HUNTER_DELAY_NEXT_BATCH 这个延时消息,就发送一个,这里的延时是 200ms,也就是说间隔 200ms 处理一次图片展示。就跟公交车一样,定时发车,有可能车上只有 1 个乘客,有可能很多个,但这里在没有 hunter 时是不会发车的,而公交车有可能空车也发。

这里采用发消息的形式,我觉得主要是利用延时的功能,而并非线程切换。

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

这里才是线程切换,将要展示的 hunters 通过消息传递给主线程,因为想要做 UI 相关的操作,必须在主线程。从名字上可以看出这个 Handler 是个主线程的 Handler,或者从它的赋值来源也可以知道,就是 Picasso 类里的 HANDLER 变量。

@Override public void handleMessage(Message msg) {
    switch (msg.what) {
        case HUNTER_BATCH_COMPLETE: {
            List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
            for (int i = 0, n = batch.size(); i < n; i++) {
                BitmapHunter hunter = batch.get(i);
                hunter.picasso.complete(hunter);
            }
            break;
        }
    }
}

还是调用了 Picasso 对象的 complete 方法,这里可能会有疑问,为什么要通过 hunter 来引用 picasso 对象,原因是这个 HANDLER 是用 static final 修饰的,因此没办法持有外部类引用,自然就不能直接调用 complete 方法。

void complete(BitmapHunter hunter) {
    //省略部分代码......
    Action single = hunter.getAction();
    Bitmap result = hunter.getResult();
    LoadedFrom from = hunter.getLoadedFrom();
    if (single != null) {
        deliverAction(result, from, single, exception);
    }
    if (listener != null && exception != null) {
        listener.onImageLoadFailed(this, uri, exception);
    }
}

我们就看单个 action 的,hunter 还能获取多个 action,这个先不管。获取到 single 这个 Action 后就会去分发它。

private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
    if (result != null) {
        action.complete(result, from);
    } else {
        action.error(e);
    }
}

前面说到这个 action 是 ImageViewAction 类型,那么要处理的就是 complete 或者 error 操作,前面大致看过 error 方法,这里再看下 complete 方法里的代码。

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

PicassoDrawable 继承自 BitmapDrawable,这个没研究过,不太懂,应该就是可以将 Bitmap 对象作为参数生成 Drawable 类型对象吧。最后调用 ImageView 的 setImageDrawable 方法完成图片展示。

相关文章

网友评论

      本文标题:Picasso 图片加载库源码分析6-图片加载流程

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