[关闭]
@linux1s1s 2017-01-22T16:31:13.000000Z 字数 3028 阅读 2488

Android SurfaceView

AndroidView 2015-05


ContentView

  1. public class MainActivity extends ActionBarActivity
  2. {
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState)
  5. {
  6. super.onCreate(savedInstanceState);
  7. setContentView(new SurfaceViewExtend(this));
  8. }
  9. }

整体重绘

  1. import android.content.Context;
  2. import android.content.res.Resources;
  3. import android.graphics.Bitmap;
  4. import android.graphics.BitmapFactory;
  5. import android.graphics.Canvas;
  6. import android.graphics.Matrix;
  7. import android.graphics.Paint;
  8. import android.view.SurfaceHolder;
  9. import android.view.SurfaceView;
  10. public class SurfaceViewExtend extends SurfaceView implements SurfaceHolder.Callback
  11. {
  12. private Bitmap mBackground;
  13. private Bitmap mDirector;
  14. private final SurfaceHolder mHolder;
  15. private Worker mRunnable;
  16. public SurfaceViewExtend(Context context)
  17. {
  18. super(context);
  19. final Resources resources = getResources();
  20. mBackground = BitmapFactory.decodeResource(resources, R.mipmap.ic_bg);
  21. mDirector = BitmapFactory.decodeResource(resources, R.mipmap.ic_directer);
  22. mHolder = this.getHolder();
  23. mHolder.addCallback(this);
  24. }
  25. @Override
  26. public void surfaceCreated(SurfaceHolder holder)
  27. {
  28. mRunnable = new Worker();
  29. new Thread(mRunnable).start();
  30. }
  31. @Override
  32. public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
  33. {
  34. }
  35. @Override
  36. public void surfaceDestroyed(SurfaceHolder holder)
  37. {
  38. mRunnable.stopRunning();
  39. }
  40. private class Worker implements Runnable
  41. {
  42. private boolean running = true;
  43. @Override
  44. public void run()
  45. {
  46. Canvas canvas = null;
  47. int rotate = 0;
  48. while (running)
  49. {
  50. try
  51. {
  52. canvas = mHolder.lockCanvas();
  53. Paint paint = new Paint();
  54. if (canvas == null) return;
  55. canvas.drawBitmap(mBackground, 0, 0, paint);
  56. Matrix matrix = new Matrix();
  57. matrix.postRotate((rotate += 48) % 360,
  58. mDirector.getWidth() / 2, mDirector.getHeight() / 2);
  59. matrix.postTranslate(200, 200);
  60. canvas.drawBitmap(mDirector, matrix, paint);
  61. Thread.sleep(33);
  62. }
  63. catch (InterruptedException e)
  64. {
  65. e.printStackTrace();
  66. }
  67. finally
  68. {
  69. if (canvas != null)
  70. mHolder.unlockCanvasAndPost(canvas);
  71. }
  72. }
  73. }
  74. public void stopRunning()
  75. {
  76. running = false;
  77. }
  78. }
  79. }

上面的代码我们基本上搞清楚了几件事情:

脏矩形刷新

接下来我们将采取脏矩形刷新的方法来优化性能,所谓脏矩形刷新,意为仅刷新有新变化的部分所在的矩形区域,而其他没用的部分就不去刷新,以此来减少资源浪费。

我们可以通过在获取Canvas画布时,为其指派一个参数来声明我们需要画布哪个局部,这样就可以只获得这个部分的控制权:

上面的代码几乎不要改动,只需要更改一部分代码:
L61行,我们在获取画布的时候指定一个矩形,然后每次都是刷新这么一点区域:

  1. canvas = holder.get().lockCanvas(new Rect(200, 200, 300, 300));

比较之前的代码,效果改善许多,但是当我们再一次扩大刷新区域的话,效果几乎微乎其微了,如何进一步优化?

覆盖刷新

试想一下,我们每次刷新时最大的消耗在哪?

没错,在背景图绘制上,这个绘制区域非常大,会消耗我们很多资源,但实际上背景图在此例中是从不变化的,也就是说我们浪费了很多资源在无用的地方。

那么可不可以只绘制一次背景,以后每次都只绘制会动的问号图形呢?

完全可以,尝试修改一下代码,再前面加一个帧计数器,然后我们仅在第一帧的时候绘制背景:

  1. ...
  2. int framCount = 0;
  3. while (running)
  4. {
  5. try
  6. {
  7. canvas = holder.get().lockCanvas(new Rect(200, 200, 300, 300));
  8. Paint paint = new Paint();
  9. if (canvas == null) return;
  10. if (framCount ++ < 1)
  11. {
  12. canvas.drawBitmap(background.get(), 0, 0, paint);
  13. }
  14. ...
  15. }
  16. }

当你运行以后,会发生会奇怪的现象,背景图片没有了?
背景会在背景图和黑色背景之间来回闪。

这个问题其实是源于SurfaceView的双缓冲机制,我理解就是它会缓冲前两帧的图像交替传递给后面的帧用作覆盖,这样由于我们仅在第一帧绘制了背景,第二帧就是无背景状态了,且通过双缓冲机制一直保持下来,解决办法就是改为在前两帧都进行背景绘制:
所以我们这样修改:

  1. if (framCount ++ < 2)
  2. {
  3. canvas.drawBitmap(background.get(), 0, 0, paint);
  4. }

这样就可以了,效率提高了不少。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注