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