SurfaceView大图做帧动画就卡顿?不存在的

您所在的位置:网站首页 逐帧动画制作遇到的问题 SurfaceView大图做帧动画就卡顿?不存在的

SurfaceView大图做帧动画就卡顿?不存在的

2023-08-01 07:48| 来源: 网络整理| 查看: 265

壹·SurfaceView逐帧解析 & 帧复用

基类:定义绘制框架

基类:定义绘制框架 public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback {     ...     //绘制线程     private HandlerThread handlerThread;     private Handler handler;     @Override     public void surfaceCreated(SurfaceHolder holder) {         startDrawThread();     }     private void startDrawThread() {         handlerThread = new HandlerThread("SurfaceViewThread");         handlerThread.start();         handler = new Handler(handlerThread.getLooper());         handler.post(new DrawRunnable());     }     private class DrawRunnable implements Runnable {         @Override         public void run() {             try {                 canvas = getHolder().lockCanvas();                 //绘制一帧,包括解码+绘制帧                 onFrameDraw(canvas);             } catch (Exception e) {                 e.printStackTrace();             } finally {                 getHolder().unlockCanvasAndPost(canvas);                 onFrameDrawFinish();             }             //若onFrameDraw()执行超时,会导致下一帧的绘制被推后,预定的帧时间间隔不生效             handler.postDelayed(this, frameDuration);         }     }     protected abstract void onFrameDraw(Canvas canvas); } //帧动画绘制类:将绘制内容具体化为一张Bitmap public class FrameSurfaceView extends BaseSurfaceView {     ...     private BitmapFactory.Options options;     @Override     protected void onFrameDraw(Canvas canvas) {         clearCanvas(canvas);         if (!isStart()) {             return;         }         if (!isFinish()) {             //绘制一帧             drawOneFrame(canvas);         } else {             onFrameAnimationEnd();         }     }     private void drawOneFrame(Canvas canvas) {         //解析帧         frameBitmap = BitmapFactory.decodeResource(getResources(), bitmaps.get(bitmapIndex), options);         //复用帧         options.inBitmap = frameBitmap;         //绘制帧         canvas.drawBitmap(frameBitmap, srcRect, dstRect, paint);         bitmapIndex++;     }     ...

贰·对比图片解析速度

对于素材在 100k 以下的帧动画,上一篇的逐帧解析方案完全能够胜任。但如果素材是几百k,时间性能就不如预期。

掘友“小前锋”问:“你的方案有测试过大图吗?比如1024*768px”

在逐帧解析SurfaceView上试了下这个大小的帧动画,虽然播放过程很连续,但 600ms 的帧动画被放成了 1s。因为预定义的每帧播放时间被解码时间拉长了。

有没有比BitmapFactory.decodeResource()更快的解码方式?

于是乎对比了各种图片解码的速度,其中包括BitmapFactory.decodeStream()、BitmapFactory.decodeResource()、并分别将图片放到res/raw、res/drawable、及assets,还在 GitHub 上发现了RapidDecoder这个库(兴奋不已!)。

自定义了测量函数执行时间的工具类:

public class MethodUtil {     //测量并打印单次函数执行耗时     public static long time(Runnable runnable) {         long start = SystemClock.elapsedRealtime();         runnable.run();         long end = SystemClock.elapsedRealtime();         long span = end - start;         Log.v("ttaylor", "MethodUtil.time()" + " time span = " + span + " ms");         return span;     } } public class NumberUtil {     private static long total;     private static int times;     private static String tag;     //统计并打印多次执行时间的平均值     public static void average(String tag, Long l) {         if (!TextUtils.isEmpty(tag) && !tag.equals(NumberUtil.tag)) {             reset();             NumberUtil.tag = tag;         }         times++;         total += l;         int average = total / times ;         Log.v("ttaylor", "Average.average() " + NumberUtil.tag + " average = " + average);     }     private static void reset() {         total = 0;         times = 0;     } }

经多次测试取平均值,执行时间最长的是BitmapFactory.decodeResource(),最短的是用BitmapFactory.decodeStream()解析assets图片,后者只用了前者一半时间。

而RapidDecoder库的时间介于两者之间(失望至极~),不过它提供了一种边解码边绘制的技术号称比先解码再绘制要快,还没来得及试。

虽然将解码时间减半了,但解码一张 1MB 图片还是需要 60+ms,仍不能满足时间性能要求。

叁·独立解码线程

现在的矛盾是 图片解析速度 慢于 图片绘制速度,如果解码和绘制在同一个线程串行的进行,那解码势必会拖慢绘制效率。

可不可以将解码图片放在一个单独的线程中进行?

在上一篇FrameSurfaceView的基础上新增了独立解码线程:

public class FrameSurfaceView extends BaseSurfaceView {     ...     //独立解码线程     private HandlerThread decodeThread;     //解码算法写在这里面     private DecodeRunnable decodeRunnable;     //播放帧动画时启动解码线程     public void start() {         decodeThread = new HandlerThread(DECODE_THREAD_NAME);         decodeThread.start();         handler = new Handler(decodeThread.getLooper());         handler.post(decodeRunnable);     }     private class DecodeRunnable implements Runnable {         @Override         public void run() {             //在这里解码         }     } }

这样一来,基类中有独立的绘制线程,而子类中有独立的解码线程,解码速度不再影响绘制速度。

新的问题来了:图片被解码后存放在哪里?

肆·生产者 & 消费者

存放解码图片的容器,会被两个线程访问,绘制线程从中取图片(消费者),解码线程往里存图片(生产者),需考虑线程同步。第一个想到的就是LinkedBlockingQueue,于是乎在FrameSurfaceView中新增了大小为 1 的阻塞队列及存取操作:

public class FrameSurfaceView extends BaseSurfaceView {     ...     //解析队列:存放已经解析帧素材     private LinkedBlockingQueue decodedBitmaps = new LinkedBlockingQueue(1);     //记录已绘制的帧数     private int frameIndex ;     //存解码图片     private void putDecodedBitmap(int resId, BitmapFactory.Options options) {         Bitmap bitmap = decodeBitmap(resId, options);         try {             decodedBitmaps.put(bitmap);         } catch (InterruptedException e) {             e.printStackTrace();         }     }     //取解码图片     private Bitmap getDecodedBitmap() {         Bitmap bitmap = null;         try {             bitmap = decodedBitmaps.take();         } catch (InterruptedException e) {             e.printStackTrace();         }         return bitmap;     }     //解码图片     private Bitmap decodeBitmap(int resId, BitmapFactory.Options options) {         options.inScaled = false;         InputStream inputStream = getResources().openRawResource(resId);         return BitmapFactory.decodeStream(inputStream, null, options);     }     private void drawOneFrame(Canvas canvas) {         //在绘制线程中取解码图片并绘制         Bitmap bitmap = getDecodedBitmap();         if (bitmap != null) {             canvas.drawBitmap(bitmap, srcRect, dstRect, paint);         }         frameIndex++;     }     private class DecodeRunnable implements Runnable {         private int index;         private List bitmapIds;         private BitmapFactory.Options options;         public DecodeRunnable(int index, List bitmapIds, BitmapFactory.Options options) {             this.index = index;             this.bitmapIds = bitmapIds;             this.options = options;         }         @Override         public void run() {             //在解码线程中解码图片             putDecodedBitmap(bitmapIds.get(index), options);             index++;             if (index 


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3