Glide框架加载gif动态图优化

Glide图片库,相必大家用的不能再熟了,但是不知道小伙伴们有没有遇到gif加载卡顿,或者gif它变成了静态图,它不动了,这个是没法忍受的,glide全程都是java写的所以在加载gif的时候占用的内存就比较大,应用层渲染解析gif源文件相对于底层又比较慢,最终导致卡着不动了,这个是无法忍受的,所以怎么对gif这一块进行优化呢,既然进程的堆内存不能用太多,那么是否可以将gif的内存占用放在直接内存,也就是用c或c++去申请直接内存去保存gif产生的每一帧动态图,让后用c或c++底层语言去渲染gif文件。

用c和c++写一个,这显然在重复造轮子,那么直接拿比较好的底层的gif开源框架android-gif-drawable感兴趣的可以研究一下这个框架的原理,底层库有了,那接下来就是把这个库和Glide做到无缝链接就可以了

既然用来解析gif,那么就需要实现解码这一部分,Glide扩展性很强我们直接继承实现就好,如下:

package com.panxiapp.app.gifdecode;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.bumptech.glide.Glide;
import com.bumptech.glide.Registry;
import com.bumptech.glide.load.ImageHeaderParser;
import com.bumptech.glide.load.ImageHeaderParserUtils;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
import com.bumptech.glide.load.model.StreamEncoder;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 将InputStream转为FileBridge类型,便于GifDrawable处理.
 *
 * @author yangtianrui
 * @date 2019/1/5
 */

public class StreamFileDecoder implements ResourceDecoder<InputStream, FileBridge> {

    private final ArrayPool mArrayPool;
    private final Registry mRegistry;
    private final StreamEncoder mStreamEncoder;
    private final File mDir;

    private List<FileBridge> mRecyclers;

    public StreamFileDecoder(Glide glide, File dir) {
        mRegistry = glide.getRegistry();
        mArrayPool = glide.getArrayPool();
        mStreamEncoder = new StreamEncoder(mArrayPool);
        mDir = dir;
    }

    /**
     * 判断是不是用该解析器解析
     * @param source
     * @param options
     * @return
     * @throws IOException
     */
    @Override
    public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException {
        final List<ImageHeaderParser> parsers = mRegistry.getImageHeaderParsers();
        //Log.d("<ytr>", "isGifFile ? " + isGifFile + " hasOptions ? " + hasOptions(options));
        return ImageHeaderParserUtils.getType(parsers, source, mArrayPool) == ImageHeaderParser.ImageType.GIF;
    }


    @Nullable
    @Override
    public Resource<FileBridge> decode(@NonNull InputStream source, int width, int height, @NonNull Options options) throws IOException {
        final FileBridge fileBridge = getFileBridge();
        final boolean result = mStreamEncoder.encode(source, fileBridge.getFile(), options);
        Log.d("<ytr>", "decode: " + result);
        if (result) {
            return new FileBridgeResource(fileBridge);
        }
        return null;
    }

    private FileBridge getFileBridge() throws IOException {
        inflateFileBridgeIfNeeded();
        FileBridge fileBridge = mRecyclers.isEmpty() ? null : mRecyclers.remove(0);
        if (fileBridge == null) {
            File file = new File(mDir, String.valueOf(System.currentTimeMillis()));
            fileBridge = new FileBridge(this, file);
        }
        // 检查文件是否存在
        File file = fileBridge.getFile();
        if (!file.exists()) {
            final boolean success = file.createNewFile();
            if (!success) {
                throw new IOException("can not create file bridge in " + mDir.getAbsolutePath());
            }
        }
        fileBridge.setRecycle(false);
        return fileBridge;
    }

    private void inflateFileBridgeIfNeeded() {
        if (mRecyclers != null) {
            return;
        }
        mRecyclers = new ArrayList<>();
        File[] files = mDir.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            FileBridge fileBridge = new FileBridge(this, file);
            mRecyclers.add(fileBridge);
        }
        Log.d("<ytr>", "inflateFileBridgeIfNeeded: " + mRecyclers);
    }


    public void recycleFileBridge(FileBridge fileBridge) {
        if (mRecyclers != null) {
            mRecyclers.add(fileBridge);
        }
    }
}

handles方法用来判断用不用这个解析器来解析,真正实现解析的是decode方法, mStreamEncoder.encode方法是把当前流写入到文件中,也就是把gif文件写到本地内存中,代码如下:

 public boolean encode(@NonNull InputStream data, @NonNull File file, @NonNull Options options) {
    byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    boolean success = false;
    OutputStream os = null;
    try {
      os = new FileOutputStream(file);
      int read;
      while ((read = data.read(buffer)) != -1) {
        os.write(buffer, 0, read);
      }
      os.close();
      success = true;
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to encode data onto the OutputStream", e);
      }
    } finally {
      if (os != null) {
        try {
          os.close();
        } catch (IOException e) {
          // Do nothing.
        }
      }
      byteArrayPool.put(buffer);
    }
    return success;
  }

FileBridge 类是一个数据封装类,例如这个文件和是否被回收等等

package com.panxiapp.app.gifdecode;

import java.io.File;

/**
 *
 * @author yangtianrui
 * @date 2019/1/5
 */

public class FileBridge {

    private File mFile;
    private boolean mIsRecycle;
    private StreamFileDecoder mDecoder;

    public FileBridge(StreamFileDecoder decoder, File file) {
        mFile = file;
        mDecoder = decoder;
    }

    public File getFile() {
        return mFile;
    }

    public void setFile(File file) {
        mFile = file;
    }

    public boolean isRecycle() {
        return mIsRecycle;
    }

    public void setRecycle(boolean recycle) {
        mIsRecycle = recycle;
        if (recycle) {
            mDecoder.recycleFileBridge(this);
        }
    }


    @Override
    public String toString() {
        return "FileBridge{" +
                "mFile=" + mFile.getName() +
                ", mIsRecycle=" + mIsRecycle +
                '}';
    }
}

写完解码器之后,还得写一个转化器,我们需要将解码的文件转化为GifDrawble,代码如下:

public class GifDrawableTranscoder implements ResourceTranscoder<FileBridge, GifDrawable> {


    @Nullable
    @Override
    public Resource<GifDrawable> transcode(@NonNull Resource<FileBridge> toTranscode, @NonNull Options options) {
        Log.d("<ytr>", "transcode: GifDrawableTranscoder");
        final File file = toTranscode.get().getFile();
        try {
            GifDrawable gif=  new GifDrawable(file);
            gif.setLoopCount(Character.MAX_VALUE);
            return new GifDrawableResource(gif);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

做完这个之后还需要写两个Resource,用于glide的图片处理

package com.panxiapp.app.gifdecode;

import androidx.annotation.NonNull;

import com.bumptech.glide.load.resource.SimpleResource;

public class FileBridgeResource extends SimpleResource<FileBridge> {


    public FileBridgeResource(@NonNull FileBridge data) {
        super(data);
    }

    @Override
    public void recycle() {
        super.recycle();
        get().setRecycle(true);
    }
}

package com.panxiapp.app.gifdecode;/*
 * 图片被移除内存缓存时调用,此时释放GifDrawable的资源
 *
 */

import androidx.annotation.NonNull;

import com.bumptech.glide.load.resource.SimpleResource;

import pl.droidsonroids.gif.GifDrawable;

public class GifDrawableResource extends SimpleResource<GifDrawable> {


    public GifDrawableResource(@NonNull GifDrawable data) {
        super(data);
    }

    @Override
    public void recycle() {
        final GifDrawable drawable = get();
        drawable.stop();
        drawable.recycle();
    }
}

因为最终Glide显示的图片都是通过Resource来最终显示在ImageView上的,最后再写一个BufferFileDecoder 用于将ByteBuffer转化为FileBridge

package com.panxiapp.app.gifdecode;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.util.ByteBufferUtil;

import java.io.IOException;
import java.nio.ByteBuffer;

public class BufferFileDecoder implements ResourceDecoder<ByteBuffer, FileBridge> {

    private final StreamFileDecoder mStreamFileDecoder;

    public BufferFileDecoder(StreamFileDecoder streamFileDecoder) {
        mStreamFileDecoder = streamFileDecoder;
    }

    @Override
    public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) throws IOException {
        Log.d("<ytr>", "handles: BufferFileDecoder");
        return hasOptions(options) && mStreamFileDecoder.handles(ByteBufferUtil.toStream(source), options);
    }

    @Nullable
    @Override
    public Resource<FileBridge> decode(@NonNull ByteBuffer source, int width, int height, @NonNull Options options) throws IOException {
        return mStreamFileDecoder.decode(ByteBufferUtil.toStream(source), width, height, options);
    }

    private static boolean hasOptions(Options options){
        final Boolean hasOptions = true;
        return hasOptions != null && hasOptions;
    }

}

这里总共搞了两个解析器,一个是从InputStream转化为FileBridge,一个是从ByteBuffer转化为FileBridge,为啥要加两个呢,最主要原因是内存中保存文件的数据是ByteBuffer情况下保存的,而从网络下载时,或从本地文件加载时时通过的形式加载,所以需要两个都有。

最后将这些解析器注入到Glide中,如下:

final String temp_dir = "glide_temp";
        StreamFileDecoder streamFileDecoder = new StreamFileDecoder(glide,
                glide.getContext().getDir(temp_dir, Context.MODE_PRIVATE));
        registry.prepend(InputStream.class, FileBridge.class, streamFileDecoder);
        registry.prepend(ByteBuffer.class, FileBridge.class, new BufferFileDecoder(streamFileDecoder));
        registry.register(FileBridge.class, GifDrawable.class, new GifDrawableTranscoder());

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值