1. 听说游戏开发都用Surfaceview,上网搜了下,说是SurfaceView在更新视图时,采用了双缓存机制,可以提高更新效率,加强用户体验。下面两段粘贴自别人的博客,说的还挺清楚的。
Note: On each pass you retrieve the Canvas from the SurfaceHolder, the previous state of the Canvas will be retained. In order to properly animate your graphics, you must re-paint the entire surface. For example, you can clear the previous state of the Canvas by filling in a color with drawColor() or setting a background image with drawBitmap(). Otherwise, you will see traces of the drawings you previously performed.
在运用时可以理解为:SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。例如,如果你已经先后两次绘制了视图A和B,那么你再调用lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你讲重绘的C视图上传,那么C将取代B作为新的frontCanvas显示在SurfaceView上,原来的B则转换为backCanvas。
2. 在网上依样画葫芦写了短代码,结果send to background的时候会crash。Debug后发现原因是没检查SurfaceHolder获得的Canvas是不是为空。在surfaceDestroyed之后,Canvas肯定没有了,所以要检查。
public class DemoSurfaceView extends SurfaceView implements Callback{ LoopThread thread; public DemoSurfaceView(Context context) { super(context); init(); } private void init(){ SurfaceHolder holder = getHolder(); thread = new LoopThread(holder, getContext()); holder.addCallback(this); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { thread.isRunning = true; thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i("Hailin", "surfaceDestroyed"); thread.isRunning = false; try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } class LoopThread extends Thread{ SurfaceHolder surfaceHolder; Context context; boolean isRunning; float radius = 10f; Paint paint; public LoopThread(SurfaceHolder surfaceHolder,Context context){ this.surfaceHolder = surfaceHolder; this.context = context; isRunning = false; paint = new Paint(); paint.setColor(Color.YELLOW); paint.setStyle(Paint.Style.STROKE); } @Override public void run() { Canvas c = null; while(isRunning){ try{ synchronized (surfaceHolder) { if(surfaceHolder == null) { Log.i("Hailin", "surfaceHolder == null"); } c = surfaceHolder.lockCanvas(null); if(c == null) { Log.i("Hailin", "c == null"); return; } doDraw(c); Thread.sleep(50); surfaceHolder.unlockCanvasAndPost(c); } } catch (InterruptedException e) { e.printStackTrace(); } finally { } } } public void doDraw(Canvas c){ c.drawColor(Color.BLACK); c.translate(200, 200); c.drawCircle(0,0, radius++, paint); if(radius > 100){ radius = 10f; } } } }
3. 照着《疯狂Android讲义》敲了断代码,发现在点击屏幕后在surfaceCreated里面画的背景图老是丢失。如果直接用Surfaceview.setBackground来设置背景,点击屏幕画的图会被背景图非黑色部分给遮住(背景图本来是白色背景,但是画上去都变黑色背景了)。最后只能在onTouch里面重新画一遍背景图。
public class SVTest extends Activity { private SurfaceHolder holder; private Paint paint; private SurfaceView surface; private Canvas canvas; private boolean isTouched; private Bitmap mBitmap; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.svtest); paint = new Paint(); surface = (SurfaceView) this.findViewById(R.id.svTest); final Bitmap back = BitmapFactory.decodeResource(SVTest.this.getResources(), R.drawable.ic_launcher); DisplayMetrics dm = getResources().getDisplayMetrics(); int mScreenWidth = dm.widthPixels; int mScreenHeight = dm.heightPixels; mBitmap = Bitmap.createScaledBitmap(back, mScreenWidth, mScreenHeight, true); // actually it set a Drawable with pure color as background // surface.setBackgroundColor(Color.WHITE); // set background Drawable // surface.setBackgroundResource(R.drawable.ic_launcher); holder = surface.getHolder(); holder.addCallback(new Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub Log.i("Hailin", "surfaceCreated"); // set background canvas = holder.lockCanvas(); canvas.drawBitmap(mBitmap, 0, 0, null); holder.unlockCanvasAndPost(canvas); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub Log.i("Hailin", "surfaceChanged"); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub Log.i("Hailin", "surfaceDestroyed"); } }); surface.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction() == MotionEvent.ACTION_DOWN) { if(!isTouched) { // set background again canvas = holder.lockCanvas(); canvas.drawBitmap(mBitmap, 0, 0, null); holder.unlockCanvasAndPost(canvas); isTouched = true; } int cx = (int) event.getX(); int cy = (int) event.getY(); Canvas canvas = holder.lockCanvas(new Rect(cx - 50, cy - 50, cx + 50, cy + 50)); canvas.save(); canvas.rotate(30, cx, cy); paint.setColor(Color.RED); canvas.drawRect(cx - 50, cy - 50, cx, cy, paint); canvas.restore(); paint.setColor(Color.GREEN); canvas.drawRect(cx, cy, cx + 50, cy + 50, paint); holder.unlockCanvasAndPost(canvas); } return false; } }); } @Override public void onResume() { super.onResume(); Log.i("Hailin", "onResume"); } @Override public void onPause() { super.onPause(); Log.i("Hailin", "onPause"); } @Override public void onDestroy() { super.onDestroy(); Log.i("Hailin", "onDestroy"); } }