SurfaceView大图做帧动画就卡顿?不存在的 |
您所在的位置:网站首页 › 逐帧动画制作遇到的问题 › SurfaceView大图做帧动画就卡顿?不存在的 |
壹·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 |