添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

个人博客传送门

一、涉及类目

GlideDrawableImageViewTarget.java
GifDrawable.java
GifFrameLoader.java
GifDecoder.java

二、原理概述

老规矩先介绍原理的框架,免得看源代码迷路

  • GlideDrawableImageViewTarget 会调用加载的 GifDrawable 来启动动画
  • GifDrawable 会在 draw() 中绘制当前帧, 并委托 GifFrameLoader 去加载下一帧
  • GifFrameLoader 依赖 GifDecoder 加载完成下一帧通知 GifDrawable 刷新视图

GifDrawable 其实是重写的 Drawable, 通过其 invalidateSelf() 通知界面重绘自己, 且在 draw() 方法中完成重绘, 还需要管理 loop, 用以控制结束循环
GifFrameLoader 负责控制加载每一帧的时间间隔, 还负责管理加载位置
GifDecoder 负责加载 gif 的帧

三、源码细节

先把下面这两步的代码看了

GlideDrawableImageViewTarget 调用加载的 GifDrawable 来启动动画
GifDrawable 会在 draw() 中绘制当前帧, 并委托 GifFrameLoader 去加载下一帧

// GlideDrawableImageViewTarget
    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
        if (!resource.isAnimated()) {
            float viewRatio = view.getWidth() / (float) view.getHeight();
            float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();
            if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN
                    && Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
                resource = new SquaringDrawable(resource, view.getWidth());
        super.onResourceReady(resource, animation);
        this.resource = resource;
        // 这里调用了 GifDrawable 的 start 方法
        resource.setLoopCount(maxLoopCount);
        resource.start();
// GlideDrawable
    public void start() {
        // 状态置换跳过不看
        isStarted = true;
        resetLoopCount();
        if (isVisible) {
            // 真正的使能代码
            startRunning();
    private void startRunning() {
        // gif 只有 1 帧, 开始即结束
        if (decoder.getFrameCount() == 1) {
            // 通知界面重绘自己, 结束了
            invalidateSelf();
        // 不只 1 帧
        else if (!isRunning) {
            isRunning = true;
            // frameLoader 开始工作啦
            frameLoader.start();
            // 通知界面重绘自己, 就是把当前帧给先画出来
            invalidateSelf();

至此, 我们触发了 frameLoader.start() , 并且界面上目前也因为 invalidateSelf() 而绘制上了第一帧
再来看看第三步: 循环加载帧, 并渲染到界面上

GifFrameLoader 依赖 GifDecoder 加载完成下一帧通知 GifDrawable 刷新视图

// GifFrameLoader
    public void start() {
        if (isRunning) {
            return;
        isRunning = true;
        isCleared = false;
        // 上面都是些状态信息, 跳过不看, 这个函数看名字就知道跑去加载下一帧了
        loadNextFrame();
    private void loadNextFrame() {
        if (!isRunning || isLoadPending) {
            return;
        isLoadPending = true;
        // 这行是移动 gifDecoder 的解析位置, 跳到下一帧的位置
        gifDecoder.advance();
        // 获取下一帧的延时时间(gif 每帧之间都有个时间间隔)
        long targetTime = SystemClock.uptimeMillis() + gifDecoder.getNextDelay();
        DelayTarget next = new DelayTarget(handler, gifDecoder.getCurrentFrameIndex(), targetTime);
        // 开始异步加载, 即不在主线程执行加载程序
        requestBuilder
                .signature(new FrameSignature())
                .into(next);

直接触发 loadNextFrame() 去加载下一帧, 真正的代码则是

  • gifDecoder.advance() 可以粗略理解成跳到下一帧头部位置
  • gifDecoder.getNextDelay() 获得下一帧的间隔时间
  • requestBuilder.into() 异步解析下一帧, 解析完成会回调 DelayTarget

下面看看 DelayTarget 里面的回调

// DelayTarget    
    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
            this.resource = resource;
            // 解析完成了, resource就存储着下一帧的图
            Message msg = handler.obtainMessage(FrameLoaderCallback.MSG_DELAY, this);
            // 这里 MSG_DELAY 明显是处理帧的时间间隔, 现在要异步切回主线程处理刷新问题了
            handler.sendMessageAtTime(msg, targetTime);
    private class FrameLoaderCallback implements Handler.Callback {
        public static final int MSG_DELAY = 1;
        public static final int MSG_CLEAR = 2;
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == MSG_DELAY) {
                GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj;
                // 继续追踪
                onFrameReady(target);
                return true;
            } else if (msg.what == MSG_CLEAR) {
                GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj;
                Glide.clear(target);
            return false;
// GifFrameLoader
    void onFrameReady(DelayTarget delayTarget) {
        if (isCleared) {
            handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, delayTarget).sendToTarget();
            return;
        DelayTarget previous = current;
        current = delayTarget;
        // callback 是一个 GlideDrawable, 告诉它我帮你把下一帧加载出来了, 下面来看看 GlideDrawable 是如何做的
        callback.onFrameReady(delayTarget.index);
        if (previous != null) {
            handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, previous).sendToTarget();
        isLoadPending = false;
        loadNextFrame();
  • DelayTarget.onResourceReady() 加载下一帧完成了;
  • handler.sendMessageAtTime 切回主线程, 顺便还把 帧的时间间隔问题解决了
  • callback.onFrameReady() 通知 callback 也就是 GlideDrawable 加载好了

下面来看看 GlideDrawable 在被通知加载好了之后做了些啥

// GlideDrawable
    public void onFrameReady(int frameIndex) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && getCallback() == null) {
            stop();
            reset();
            return;
        // 刷新自己, 触发 draw() 方法
        invalidateSelf();
        // 如果满一个来回, 则loop 循环数量加一
        if (frameIndex == decoder.getFrameCount() - 1) {
            loopCount++;
        // 循环次数够了,跳出循环
        if (maxLoopCount != LOOP_FOREVER && loopCount >= maxLoopCount) {
            stop();
    public void draw(Canvas canvas) {
        if (isRecycled) {
            return;
        if (applyGravity) {
            Gravity.apply(GifState.GRAVITY, getIntrinsicWidth(), getIntrinsicHeight(), getBounds(), destRect);
            applyGravity = false;
        // 从 frameLoader 拿出当前帧(也就是之前委托它加载的下一帧)
        Bitmap currentFrame = frameLoader.getCurrentFrame();
        Bitmap toDraw = currentFrame != null ? currentFrame : state.firstFrame;
        // 直接往画布上绘制
        canvas.drawBitmap(toDraw, null, destRect, paint);
  • 下一帧加载好了之后, GlideDrawable 触发自己的 draw() 方法开始绘制
  • frameLoader.getCurrentFrame() 取出下一帧
  • canvas.drawBitmap() 直接往界面上绘制

至此, glide gif的加载实现已讲解完毕! 感谢观看

个人博客传送门一、涉及类目GlideDrawableImageViewTarget.javaGifDrawable.javaGifFrameLoader.javaGifDecoder.java二、原理概述老规矩先介绍原理的框架,免得看源代码迷路GlideDrawableImageViewTarget 会调用加载的 GifDrawable 来启动动画GifDrawable 会在 draw() 中绘制当前帧, 并委托 GifFrameLoader 去加载下一帧GifFrameLoader Glide是一个面向Android的快速高效的开源媒体管理和图像加载框架,它将媒体解码、内存和磁盘缓存以及资源池封装到一个简单易用的接口中; Glide支持获取、解码和显示视频静像、图像和动画GIFGlide包括一个灵活的API,允许开发人员插入几乎任何网络堆栈。默认情况下,Glide使用自定义的基于HttpUrlConnection的堆栈,但也包括插件到Google的Volley项目或Square的OkHttp库中的实用程序库。 Glide的主要重点是尽可能平滑和快速地滚动
Android图片框架Glide-3.7.0(最新,很强大),超好用的图片框架(包含jar和源码Glide 是一个高效、开源、 Android设备上的媒体管理框架,它遵循BSD、MIT以及Apache 2.0协议发布。Glide具有获取、解码和展示视频剧照、图片、动画等功能,它还有灵活的API,这些API使开发者能够将Glide应用在几乎任何网络协议栈里。创建Glide的主要目的有两个,一个是实现平滑的图片列表滚动效果,另一个是支持远程图片的获取、大小调整和展示。近日,Glide 3.0发布,现已提供 jar包下载 ,同时还支持使用Gradle以及Maven进行构建。该版本包括很多值得关注的新功能,如支持Gif 动画和视频剧照解码、智能的暂停和重新开始请求、支持缩略图等,具体新增功能如下如下: GIF 动画的解码 :通过调用Glide.with(context).load(“图片路径“)方法,GIF动画图片可以自动显示为动画效果。如果想有更多的控制,还可以使用Glide.with(context).load(“图片路径“).asBitmap()方法加载静态图片,使用Glide.with(context).load(“图片路径“).asGif()方法加载动画图片 本地视频剧照的解码: 通过调用Glide.with(context).load(“图片路径“)方法,Glide能够支持Android设备中的所有视频剧照的加载和展示 缩略图的支持: 为了减少在同一个view组件里同时加载多张图片的时间,可以调用Glide.with(context).load(“图片路径“).thumbnail(“缩略比例“).into(“view组件“)方法加载一个缩略图,还可以控制thumbnail()中的参数的大小,以控制显示不同比例大小的缩略图 Activity 生命周期的集成: 当Activity暂停和重启时,Glide能够做到智能的暂停和重新开始请求,并且当Android设备的连接状态变化时,所有失败的请求能够自动重新请求 转码的支持: Glide的toBytes() 和transcode() 两个方法可以用来获取、解码和变换背景图片,并且transcode() 方法还能够改变图片的样式 动画的支持: 新增支持图片的淡入淡出动画效果(调用crossFade()方法)和查看动画的属性的功能 OkHttp 和Volley 的支持: 默认选择HttpUrlConnection作为网络协议栈,还可以选择OkHttp和Volley作为网络协议栈 其他功能: 如在图片加载过程中,使用Drawables对象作为占位符、图片请求的优化、图片的宽度和高度可重新设定、缩略图和原图的缓存等功能
1. 首先,您需要下载Glide-3.7.0.jar文件并将其保存在您的项目文件夹中。 2. 在Android Studio中,右键单击您的项目文件夹,然后选择“Open Module Settings”。 3. 在“Modules”选项卡中,选择您的应用程序模块,然后单击“Dependencies”选项卡。 4. 单击“+”按钮,然后选择“File dependency”。 5. 在弹出的对话框中,选择您刚刚下载的Glide-3.7.0.jar文件。 6. 单击“OK”按钮,然后单击“Apply”按钮。 7. 现在,您已经成功添加了Glide-3.7.0.jar框架,可以在您的项目中使用它了。