Glide 加载webp动图实战(解决图片每帧间隔过长,动图单次播放,二次播放动图时首帧是动图最后一帧的问题)
零、前言
最近接了一个需求,要在某个地方加上动图的特效,最后方案确定下来有两种实现的方式
一、lottie动画来展示
二、类gif动图的方式来展示
考虑到时间的成本,优先使用类gif的方式来实现。
主要是原因第三方图片加载框架已经封装好了一套完整的图片展示的流程,包括图片下载,展示,回收,缓存等。我们直接进行调用即可,但是由于ui给出的动图是webp格式的,所以在使用过程中出现挺多棘手的问题,好在最后找到原因并解决。
一、具体的实现代码
添加依赖
//Glide库//implementation 'com.github.bumptech.glide:glide:4.7.1'//supportimplementation 'com.github.bumptech.glide:glide:4.12.0'//androidxannotationProcessor "com.github.bumptech.glide:compiler:4.12.0"//androidx
//Glide支持webp动图的库implementation "com.github.zjupure:webpdecoder:2.0.4.12.0"
xml里面就是一个普通的ImageView,我这里就不贴出来了
具体的实现java层代码
WebpDrawable mWebpDrawable = null;private void startWebpGifAni(ImageView iv,String url,int defaultIcon){
// if(mWebpDrawable!=null&&!mWebpDrawable.isRunning()){
// mWebpDrawable.startFromFirstFrame();
// mWebpDrawable.stop();
// }//webp动图Transformation<Bitmap> transformation = new CenterInside();Glide.with(this).load(url)//不是本地资源就改为url即可.optionalTransform(transformation).optionalTransform(WebpDrawable.class, new WebpDrawableTransformation(transformation)).addListener(new RequestListener<Drawable>() {@Overridepublic boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {iv.setImageResource(defaultIcon);return false;}@Overridepublic boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {if (resource instanceof WebpDrawable) {mWebpDrawable = (WebpDrawable) resource;try {//已知三方库的bug,webp的动图每一帧的时间间隔于实际的有所偏差,需要反射三方库去修改//https://github.com/zjupure/GlideWebpDecoder/issues/33Field gifStateField = mWebpDrawable.getClass().getDeclaredField("state");gifStateField.setAccessible(true);//开放权限Class gifStateClass = Class.forName("com.bumptech.glide.integration.webp.decoder.WebpDrawable$WebpState");Field gifFrameLoaderField = gifStateClass.getDeclaredField("frameLoader");gifFrameLoaderField.setAccessible(true);Class gifFrameLoaderClass = Class.forName("com.bumptech.glide.integration.webp.decoder.WebpFrameLoader");Field gifDecoderField = gifFrameLoaderClass.getDeclaredField("webpDecoder");gifDecoderField.setAccessible(true);WebpDecoder webpDecoder = (WebpDecoder) gifDecoderField.get(gifFrameLoaderField.get(gifStateField.get(resource)));Field durations = webpDecoder.getClass().getDeclaredField("mFrameDurations");durations.setAccessible(true);int[] args = (int[]) durations.get(webpDecoder);if (args.length > 0) {for (int i = 0; i < args.length; i++) {if (args[i] > 30) {//加载glide会比ios慢 这边把gif的间隔减少15sargs[i] = args[i] - 15;}}}durations.set(webpDecoder, args);} catch (Exception e) {e.printStackTrace();}//需要设置为循环1次才会有onAnimationEnd回调mWebpDrawable.setLoopCount(1);mWebpDrawable.registerAnimationCallback(new Animatable2Compat.AnimationCallback() {@Overridepublic void onAnimationStart(Drawable drawable) {super.onAnimationStart(drawable);}@Overridepublic void onAnimationEnd(Drawable drawable) {super.onAnimationEnd(drawable);
//第二次播放webp动图的时候 会显示改webp动图最后一帧的图片 然后才能正常显示if (mWebpDrawable != null && !mWebpDrawable.isRunning()) {mWebpDrawable.startFromFirstFrame();mWebpDrawable.stop();}mWebpDrawable.unregisterAnimationCallback(this);}});}return false;}})
// .skipMemoryCache(true).into(iv);}private void cancelGifOnResume(){//解决使用webp动图播放一次的时候 页面重新显示之后 webp动图还会播放一次的问题 //在onresume调用即可if(mWebpDrawable==null){return;}try {Field isRunning = mWebpDrawable.getClass().getDeclaredField("isRunning");isRunning.setAccessible(true);isRunning.setBoolean(mWebpDrawable,true);} catch (Exception e) {e.printStackTrace();}}
OK,这里以上就是所有的代码。
接下来是我怎么解决这些问题的思路,可能会有点啰嗦,感兴趣的话可以看下去,当然如果有更好的方法的话,也欢迎在评论区里面指出来。
二、碰到的问题&解决的思路
在一开始的方案确定的时候,我先试了工程中现有的代码,发现不能展示动图,baidu了一下之后发现 fresco 可以支持显示webp的动图,代码我这里也贴出来
private void startAni(SimpleDraweeView iv, String webp1){ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {@Overridepublic void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) {if (animatable != null && AnimatedDrawable2.class.isInstance(animatable)) {final AnimatedDrawable2 animatedDrawable2 = (AnimatedDrawable2) animatable;animatedDrawable2.start();final int totalCnt = animatedDrawable2.getFrameCount();animatedDrawable2.setAnimationListener(new BaseAnimationListener() {private int lastFrame; //防止无限循环 适时退出动画@Overridepublic void onAnimationFrame(AnimatedDrawable2 drawable, int frameNumber) {if (!(lastFrame == 0 && totalCnt <= 1) && lastFrame <= frameNumber) {lastFrame = frameNumber;} else {animatedDrawable2.stop();}}@Overridepublic void onAnimationStart(AnimatedDrawable2 drawable) {lastFrame = -1;}@Overridepublic void onAnimationStop(AnimatedDrawable2 drawable) {}});}};};DraweeController controller = Fresco.newDraweeControllerBuilder().setUri(Uri.parse(webp1)).setOldController(iv.getController()).setControllerListener(controllerListener).build();iv.setController(controller);}
但是 fresco的三方包过大,并且原有的工程并未引入,所以这个方案作废。。。
后续找到了Glide的拓展库webpdecoder,这个库可以支持webp的动图。
不过在使用过程中,还是发现了几个问题。
1.动图播放的速度有点慢
将代码集成上去之后,发现动图会有点慢,深入代码找原因
//WebpDrawable.javapublic void startFromFirstFrame() {Preconditions.checkArgument(!isRunning, "You cannot restart a currently running animation.");state.frameLoader.setNextStartFromFirstFrame();start();}public void start() {isStarted = true;resetLoopCount();if(isVisible) {startRunning();}}private void startRunning() {Preconditions.checkArgument(!isRecycled, "You cannot start a recycled Drawable. Ensure thatyou clear any references to the Drawable when clearing the corresponding request.");if(state.frameLoader.getFrameCount() == 1) {invalidateSelf();} else if(!isRunning) {isRunning = true;state.frameLoader.subscribe(this);invalidateSelf();}}
可以看到最后通过 state.frameLoader.subscribe(this); 这行代码来加载图片,而frameLoader是webpDrawable创建的时候就被提供的
WebpDrawable(WebpFrameLoader frameLoader, BitmapPool bitmapPool, Paint paint) {this(new WebpState(bitmapPool, frameLoader));this.paint = paint;}
我们接下去接着看
//WebpFrameLoader.java void subscribe(FrameCallback frameCallback) {if (isCleared) {throw new IllegalStateException("Cannot subscribe to a cleared frame loader");}if (callbacks.contains(frameCallback)) {throw new IllegalStateException("Cannot subscribe twice in a row");}boolean start = callbacks.isEmpty();callbacks.add(frameCallback);if (start) {start();}}private void start() {if (isRunning) {return;}isRunning = true;isCleared = false;loadNextFrame();}private void loadNextFrame() {if (!isRunning || isLoadPending) {return;}if (startFromFirstFrame) {Preconditions.checkArgument(pendingTarget == null, "Pending target must be null when starting from the first frame");webpDecoder.resetFrameIndex();startFromFirstFrame = false;}if (pendingTarget != null) {DelayTarget temp = pendingTarget;pendingTarget = null;onFrameReady(temp);return;}isLoadPending = true;// Get the delay before incrementing the pointer because the delay indicates the amount of time// we want to spend on the current frame.int delay = webpDecoder.getNextDelay();long targetTime = SystemClock.uptimeMillis() + delay;webpDecoder.advance();int frameIndex = webpDecoder.getCurrentFrameIndex();next = new DelayTarget(handler, frameIndex, targetTime);WebpFrameCacheStrategy cacheStrategy = webpDecoder.getCacheStrategy();RequestOptions options = RequestOptions.signatureOf(getFrameSignature(frameIndex)).skipMemoryCache(cacheStrategy.noCache());requestBuilder.apply(options).load(webpDecoder).into(next);}
可以看到这里是通过 loadNextFrame 这个方法读取每一帧的图片并展示的,具体展示的时间是由这两行代码控制
int delay = webpDecoder.getNextDelay(); long targetTime = SystemClock.uptimeMillis() + delay;
我们继续看webpDecoder的相关源码
//webpDecoder.javaprivate final int[] mFrameDurations;private final WebpFrameInfo[] mFrameInfos;@Overridepublic int getNextDelay() {if (mFrameDurations.length == 0 || mFramePointer < 0) {return 0;}return getDelay(mFramePointer);}@Overridepublic int getDelay(int n) {int delay = -1;if ((n >= 0) && (n < mFrameDurations.length)) {delay = mFrameDurations[n];}return delay;}
可以看到是由 mFrameDurations 这个参数控制,mFrameDurations这个参数是在 WebpImage 类中由 native 方法返回。那么我们只要获取到这个类的这个属性就能控制每一帧之间的间隔了。
试了一下,没有办法直接去设置,那么我们就用反射找到这个对象,然后再去修改这个参数
try {//已知三方库的bug,webp的动图每一帧的时间间隔于实际的有所偏差,需要反射三方库去修改//https://github.com/zjupure/GlideWebpDecoder/issues/33Field gifStateField = mWebpDrawable.getClass().getDeclaredField("state");gifStateField.setAccessible(true);//开放权限Class gifStateClass = Class.forName("com.bumptech.glide.integration.webp.decoder.WebpDrawable$WebpState");Field gifFrameLoaderField = gifStateClass.getDeclaredField("frameLoader");gifFrameLoaderField.setAccessible(true);Class gifFrameLoaderClass = Class.forName("com.bumptech.glide.integration.webp.decoder.WebpFrameLoader");Field gifDecoderField = gifFrameLoaderClass.getDeclaredField("webpDecoder");gifDecoderField.setAccessible(true);WebpDecoder webpDecoder = (WebpDecoder) gifDecoderField.get(gifFrameLoaderField.get(gifStateField.get(resource)));Field durations = webpDecoder.getClass().getDeclaredField("mFrameDurations");durations.setAccessible(true);int[] args = (int[]) durations.get(webpDecoder);if (args.length > 0) {for (int i = 0; i < args.length; i++) {if (args[i] > 30) {//加载glide会比ios慢 这边把gif的间隔减少15sargs[i] = args[i] - 15;}}}durations.set(webpDecoder, args);} catch (Exception e) {e.printStackTrace();}
我这里的话是将每一帧加快了15ms,这个值并不是固定,可以更具需求自己改,但要保证修改之后的数组长度不小于原数组长度。
2.单次播放的动图在页面切换(调用onResume)之后,会再播放一遍
这个问题是在切换界面的时候发现的,后面发现无论怎么设置都无法避免这个问题,后来才发现在页面onresume的时候,组件会调用一次 startFromFirstFrame 方法。估计可能是由于Glide监听了页面的生命周期,导致了每一次切换到了界面都会调用 startFromFirstFrame 这个方法。
如果不想要这个方法的话,我们就要将 isRunning 设为true 即可
//WebpDrawable.javapublic void startFromFirstFrame() {Preconditions.checkArgument(!isRunning, "You cannot restart a currently running animation.");state.frameLoader.setNextStartFromFirstFrame();start();}public void start() {isStarted = true;resetLoopCount();if(isVisible) {startRunning();}}private void startRunning() {Preconditions.checkArgument(!isRecycled, "You cannot start a recycled Drawable. Ensure thatyou clear any references to the Drawable when clearing the corresponding request.");if(state.frameLoader.getFrameCount() == 1) {invalidateSelf();} else if(!isRunning) {//只要将isRunning设置成true 动画将不会被触发isRunning = true;state.frameLoader.subscribe(this);invalidateSelf();}}
最后,我选择在页面的onresume中将isRunning设为true
@Overrideprotected void onResume() {super.onResume();cancelGifOnResume();...}private void cancelGifOnResume(){if(mWebpDrawable==null){return;}try {Field isRunning = mWebpDrawable.getClass().getDeclaredField("isRunning");isRunning.setAccessible(true);isRunning.setBoolean(mWebpDrawable,true);} catch (Exception e) {e.printStackTrace();}}
3.二次播放动图时首帧是动图最后一帧
这个是在最后试效果的时候发现的,在网上之后得到的方法只有一个
//禁止Glide缓存gif图片,否则会导致每次切换页面会先显示gif图片最后一帧,然后才开始播放动画RequestOptions options = new RequestOptions() .skipMemoryCache(true);
设置跳过缓存来避免这个问题,实测在这个过程中小的webp的动图效果还是可以的,但是只要动图稍微大一些,效果就不是特别的好了,会明显的先空白一下(处于加载过程中),然后才能正常显示,毕竟从内存中获取图片的速度是最快的。
然后去github,google搜索了一圈之后都没有结果。静下心来重新分析一下网上这个解决方法的思路。
skipMemoryCache(true) 的意思是不使用内存的缓存,并不是不使用disk缓存。那么为什么这么做呢?在Glide中缓存分为disk缓存,内存的缓存,在使用过程中优先匹配的内存缓存,然后是disk缓存,那么代表着在未使用 skipMemoryCache(true) 的过程中,优先匹配内存缓存,并且这一份内存缓存应该是同一份的。
所以我们第二次加载这个图片的时候,用的内存缓存的图片是上一次结束时最后一帧的图片。
那么怎么处理呢?我这边取了个巧,在单次动画结束的时候,有启动了一次,并马上停止,相当于重置内存缓存中的动图的到第一帧。
mWebpDrawable.registerAnimationCallback(new Animatable2Compat.AnimationCallback() {@Overridepublic void onAnimationStart(Drawable drawable) {super.onAnimationStart(drawable);}@Overridepublic void onAnimationEnd(Drawable drawable) {super.onAnimationEnd(drawable);if (mWebpDrawable != null && !mWebpDrawable.isRunning()) {//重置内存缓存动图为第一帧mWebpDrawable.startFromFirstFrame();mWebpDrawable.stop();}mWebpDrawable.unregisterAnimationCallback(this);}});
参考:
Glide加载webp动画及监听动画播放结束_Dway的博客-CSDN博客_glide加载webp
Android基于Glide(4.6.1)加载gif实践_mayundoyouknow的博客-CSDN博客_android glide加载gif
Glide 加载webp动图实战(解决图片每帧间隔过长,动图单次播放,二次播放动图时首帧是动图最后一帧的问题)相关推荐
- Glide 加载webp动图
前提 公司新需求要求视频封面加载动图,但是由于Gif上传的图片过大,所以为了节省开销,对图片进行了处理,最后给到客户端的是webp格式的图片链接. 1.验证webp格式的图片链接是否是动图,测试时,将 ...
- android glide加载webp动画设置播放动画次数和监听动画播放完成
需求 产品经理说:需要使用一系列的动画,我说没问题啊,用gif图不就好了吗?结果,设计师给了我一系列的webp动图.我能怎么办? 先是百度了一番,找到了一个播放webp动画的glide库,库地址是Gl ...
- Glide加载webp动画及监听动画播放结束
Glide加载图片非常方便,使用也非常广泛,但是并不能直接支持webp动图. 可能很多人会使用Fresco来加载webp,但是对于已经使用Glide的项目,总感觉再使用Fresco有点冗余,如果能用G ...
- Android开发之Glide加载U盘音乐的专辑图片的方法 | Glide加载content:图片 | Glide加载内容提供者 | Glide加载MP3专辑图片 | Glide加载音乐专辑图片
老套路先(献)上图 1.说下核心,Glide提供了直接加载内容提供者的方法用于显示图片 2.Glide加载的图片的格式如下 content://com.yhsh.app.media.server.us ...
- glide 加载webp_如何使您的网站通过WebP图像快速加载闪电
glide 加载webp by Carmen Chung 通过钟Car 如何使您的网站通过WebP图像快速加载闪电 (How to make your website load lightning f ...
- Bmob+Luban(鲁班)压缩图片实现相册选择图片压缩后上传到Bmob后台Glide加载图片显示到本地
源代码已上传CSDN:https://download.csdn.net/download/qq_16519957/11068345 因为本章需要跟前面的知识结合起来看所以就做了一个前面链接方便大家查 ...
- Glide加载大图长图
这里用的Glide是4.11.0的 第一种方法: Glide.with(activity).load(yourUrl).asBitmap().into(new SimpleTarget<Bitm ...
- Glide 加载圆形头像和gif 图
首先到github 上搜索glide 看看版本号 目前是 implementation 'com.github.bumptech.glide:glide:4.9.0'annotationProcess ...
- Glide加载常用类型图片(带白色边框的圆形图、圆角矩形图片、圆形图片)
一:依赖包: Glide_github implementation 'com.github.bumptech.glide:glide:4.8.0' annotationProcessor 'com. ...
- webp 动画 android,Android加载animated webp的控制和Glide加载GIF
因为动态的webp使用的也越来越多了,所以这里记录一种加载处理的办法.目前常用的Android图片加载库,只有fresco是可以直接加载animated webp的.那么如何处理呢?记录一下,不然坑都 ...
最新文章
- 10大高性能开发宝石,我要消灭一半程序员!
- hdu4911 简单树状数组
- [转] Web前端优化之 内容篇
- Spark RDD编程API
- mysql 检查_检查MySQL的健康状况
- 入门系列之在Ubuntu 16.04使用Buildbot建立持续集成系统
- 深度优先搜索----poj 1321棋盘问题
- nw.js 打包换桌面图标_我如何使用CometChat和NW.js构建桌面聊天应用程序(以及方法)
- Pytorch开发环境安装(Windows版本)
- jenkines的工作区目录位置查找
- Linux 内核源代码的结构
- 英特尔发布P900 Optane固态盘 而主打的市场并非数据中心
- 高级网络配置+Ipv6
- lammps教程:叠加力场hybrid/overlay设置方法介绍
- dfs——xy省赛选拔题——问题 E: 整数凑和问题
- SQL语句进阶学习一(where、通配符、正则表达式、计算字段、数据处理函数、分组数据)
- Linux下定时函数timerfd_xxx()的使用
- KingbaseES 中的xmin,xmax等系统字段说明
- android手机主板,主板芯片全解
- python人物抠图算法_Python实现AI自动抠图实例解析
热门文章
- stm32 simulink 快速计算 Timmer定时器需要的预分频PSC和自动重载ARR
- GitLab CI介绍——入门篇
- shader编程-三维场景下SDF建模,对模型进行扭曲、弯曲、裁剪、掏空操作(WebGL-Shader开发基础12)
- php halt,ThinkPHP halt和$this-error区别?
- python自学视频下载_Python下载哔哩哔哩学习视频
- 基于can总线的A2L文件解析(2)
- Linq中Skip和Take用法(TakeWhile和SkipWhile用法)
- 神经网络长什么样不知道?这有一份简单的 pytorch可视化技巧
- android system.img userdata.img文件解包打包及设置
- aria2 网页版服务器错误,aria2服务器错误