SurfaceView的烦恼(二)-部分刷新与第一、二帧猜想

原文:http://blog.csdn.net/swingline/article/details/6044458

  上篇"SurfaceView的烦恼(一)-双缓存与清屏 "提供了一个解决办法:每次画的时候,先清屏然后再全部重新画。这里有两重意思:清屏就把上次的残留清除掉了,不会出现重叠现象;全部重新画,信息也就不会因为清屏而不全。这种办法用起来很有效,不管SurfaceView的双缓冲显示(flip)的底层原理,也不会出现下面要说的“第一、二帧的猜想”的问题。

       办法虽然有效,但对于一些每次只画一小部分区域,且这些画的区域不会重叠时,这办法的效率性就很差了;而这种情况是希望在不全清屏的情况下,在原来的基础上继续画。下面是测试代码,大部分代码与上篇的一样,除了MyTimerTask中的run()方法:

main.xml

[xhtml] view plain copy print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent"  
  6.     >  
  7. <com.tutor.surface.MySurfaceView    
  8.     android:layout_width="fill_parent"   
  9.     android:layout_height="fill_parent"  
  10.     />  
  11. </LinearLayout>  
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <com.tutor.surface.MySurfaceView android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>

MySurfaceView.java

[java] view plain copy print ?
  1. package com.tutor.surface;  
  2. import java.util.Timer;  
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.view.SurfaceHolder;  
  6. import android.view.SurfaceView;  
  7. public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {  
  8.     private Timer timer = null;  
  9.     private MyTimerTask task = null;  
  10.     public MySurfaceView(Context context, AttributeSet attrs) {  
  11.         super(context, attrs);  
  12.         SurfaceHolder holder = getHolder();  
  13.         holder.addCallback(this);  
  14.     }  
  15.     @Override  
  16.     public void surfaceChanged(SurfaceHolder holder, int format, int width,  
  17.             int height) {  
  18.         // TODO Auto-generated method stub  
  19.     }  
  20.     @Override  
  21.     public void surfaceCreated(SurfaceHolder holder) {  
  22.         // TODO Auto-generated method stub  
  23.         timer = new Timer();  
  24.         task = new MyTimerTask(holder);  
  25.                 //这里有句注释掉的代码,下面会看看有和没有这句的差别  
  26.         //task.clearDraw();   
  27.         timer.schedule(task, 5001000);  
  28.     }  
  29.     @Override  
  30.     public void surfaceDestroyed(SurfaceHolder holder) {  
  31.         // TODO Auto-generated method stub  
  32.         if(timer != null) {  
  33.             timer.cancel();  
  34.             timer = null;  
  35.         }  
  36.         task = null;  
  37.     }  
  38.       
  39. }  
package com.tutor.surface; import java.util.Timer; import android.content.Context; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { private Timer timer = null; private MyTimerTask task = null; public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); SurfaceHolder holder = getHolder(); holder.addCallback(this); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub timer = new Timer(); task = new MyTimerTask(holder); //这里有句注释掉的代码,下面会看看有和没有这句的差别 //task.clearDraw(); timer.schedule(task, 500, 1000); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub if(timer != null) { timer.cancel(); timer = null; } task = null; } }

MyTimerTask.java

[java] view plain copy print ?
  1. package com.tutor.surface;  
  2. import java.util.TimerTask;  
  3. import android.graphics.Canvas;  
  4. import android.graphics.Color;  
  5. import android.graphics.Paint;  
  6. import android.graphics.Rect;  
  7. import android.view.SurfaceHolder;  
  8. public class MyTimerTask extends TimerTask {  
  9.     private SurfaceHolder holder = null;  
  10.       
  11.     private int left = 50;  
  12.     private int top = 5;  
  13.     private int width = 20;  
  14.       
  15.     private Paint paint = null;  
  16.     boolean isRunning = true;  
  17.     private int count = 0;  
  18.       
  19.     public MyTimerTask(SurfaceHolder _holder) {  
  20.         holder = _holder;  
  21.         paint = new Paint();  
  22.         paint.setColor(Color.WHITE);  
  23.     }  
  24.       
  25.     @Override  
  26.     public void run() {  
  27.         // TODO Auto-generated method stub  
  28.         if(top > 400//测试程序,画到纵坐标超过400时不再画  
  29.             return;  
  30.           
  31.         switch(count%5) {  
  32.         case 0:  
  33.             paint.setColor(Color.BLUE);  
  34.             break;  
  35.         case 1:  
  36.             paint.setColor(Color.WHITE);  
  37.             break;  
  38.         case 2:  
  39.             paint.setColor(Color.YELLOW);  
  40.             break;  
  41.         case 3:  
  42.             paint.setColor(Color.RED);  
  43.             break;  
  44.         case 4:  
  45.             paint.setColor(Color.GREEN);  
  46.             break;  
  47.         }  
  48.           
  49.         Canvas canvas = null;  
  50.         try {  
  51.             Rect rectDirty = new Rect(left, top, left+width, top+width);  
  52.             Rect rectangle = new Rect(left, top, left+width, top+width);  
  53.               
  54.             canvas = holder.lockCanvas(rectDirty);  
  55.             canvas.drawRect(rectangle, paint);  
  56.             top += 25;  
  57.             if(top > 400) {  
  58.                 isRunning = false;  
  59.             }  
  60.         }catch (Exception e) {  
  61.             // TODO: handle exception  
  62.         }finally {  
  63.             if(canvas != null) {  
  64.                 holder.unlockCanvasAndPost(canvas);  
  65.             }  
  66.         }  
  67.         count++;  
  68.     }  
  69.       
  70.     public void clearDraw() {  
  71.         Canvas canvas = null;  
  72.         try {  
  73.             canvas = holder.lockCanvas(null);  
  74.             canvas.drawColor(Color.BLACK);  
  75.         }catch (Exception e) {  
  76.             // TODO: handle exception   
  77.         }finally {  
  78.             if(canvas != null) {  
  79.                 holder.unlockCanvasAndPost(canvas);  
  80.             }  
  81.         }  
  82.     }  
  83. }  
package com.tutor.surface; import java.util.TimerTask; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.view.SurfaceHolder; public class MyTimerTask extends TimerTask { private SurfaceHolder holder = null; private int left = 50; private int top = 5; private int width = 20; private Paint paint = null; boolean isRunning = true; private int count = 0; public MyTimerTask(SurfaceHolder _holder) { holder = _holder; paint = new Paint(); paint.setColor(Color.WHITE); } @Override public void run() { // TODO Auto-generated method stub if(top > 400) //测试程序,画到纵坐标超过400时不再画 return; switch(count%5) { case 0: paint.setColor(Color.BLUE); break; case 1: paint.setColor(Color.WHITE); break; case 2: paint.setColor(Color.YELLOW); break; case 3: paint.setColor(Color.RED); break; case 4: paint.setColor(Color.GREEN); break; } Canvas canvas = null; try { Rect rectDirty = new Rect(left, top, left+width, top+width); Rect rectangle = new Rect(left, top, left+width, top+width); canvas = holder.lockCanvas(rectDirty); canvas.drawRect(rectangle, paint); top += 25; if(top > 400) { isRunning = false; } }catch (Exception e) { // TODO: handle exception }finally { if(canvas != null) { holder.unlockCanvasAndPost(canvas); } } count++; } public void clearDraw() { Canvas canvas = null; try { canvas = holder.lockCanvas(null); canvas.drawColor(Color.BLACK); }catch (Exception e) { // TODO: handle exception }finally { if(canvas != null) { holder.unlockCanvasAndPost(canvas); } } } }

 

结果如下:

(1) 在MySurfaceView中,如果在Timer启动前不先调用清屏(即把task.clearDraw()注释掉),出现的结果是:

SurfaceView的烦恼(二)-部分刷新与第一、二帧猜想_第1张图片

(2) 如果加上那句先清屏,则结果如下:

SurfaceView的烦恼(二)-部分刷新与第一、二帧猜想_第2张图片

 

很明显,第一个结果在一开始的时候缺少了一个蓝块,代表着有部分想要画的信息被冲掉了(或覆盖掉了);第二个结果才是我们想要的结果。

(1) 从代码中可以看到,所画的矩形的位置每次都不一样,且每次画的位置都不会和以前画的区域重叠时,是可以使用递增的方式去画(即在保持原来的情况下再画新的),这样就不需要每次都全部重新画了,效率也就提高了。

(2) 代码中貌似没有什么第一帧、第二帧的现象。其实一开始尝试的时候,并不会想到在Timer启动之前先清屏;而是在MyTimerTask中的run()方法使用count==0、count==1来测试第一帧、第二帧的情况。由于SurfaceView的原理全部是使用jni调用底层库来实现的(使用了JAVA的代码没有几行),这代表着想要真正了解其原理,得深入到非JAVA代码中(目前我没有什么好办法,如果有的话麻烦告诉我一下)。因而,在此只能进行一下猜想。

 

第一、二帧猜想:

      虽然部分更新的时候,每次画canvas时都仅锁住一小部分区域,但我猜想第一帧、第二帧是全部换的,而不仅仅是锁住的那一小部分区域。这里说的第一帧是第一次调用MyTimerTask的run()方法进行画面板,这里根据SurfaceView的双缓冲原理中的交替显示,整个屏幕大小的back buffer换到前面来(并把第一个蓝方块画上),此时front buffer转回后台(此时front buffer是全屏黑的);第二次调用MyTimerTask的run()方法进行画面板时,在front buffer中画上白方块并显示(注意:由于front buffer之前并没有方块,第一个蓝方块是在back buffer中的),这时看到的现象是蓝方块被冲掉了,只剩白方块。特殊的是,只有这两帧是整个面板全部切换的,后面的front buffer、back buffer切换的空间变成了锁定面板区域,由于锁定的面板区域每次都是不一样的小区域,这时双缓冲的交替只有这一小部分区域,而不是整个屏幕;除了锁定的区域,其它区域的内容保留。

      如果在Timer启动前先清一下屏(实际上是为了调用holder.unlockCanvasAndPost(canvas)使得原来的back buffer先交换一下),然后再在此基础上画,就没有问题了。所以在run()方法中使用读数的方式,第一帧仅post,从第二帧开始画真正的内容也是一样的。

 

上面仅仅是猜想,只有真正深入到下面的代码中了解,才知道是否正确,如果有了解的朋友,不妨阐述一下。下面有两个佐证:

(1) 在MyTimerTask中锁定的区域rectDirty,如果直接传到下面的画方块代码中canvas.drawRect(rectDirty, paint);,会发现一个比较有趣的现象:首先会看到屏幕背景变成全蓝(不是全黑了),然后屏幕背景变成全白,此后屏幕背景颜色不再变化(一直是白的),方块除了第一个蓝块没有的话,其它的正常。这个现象是符合上面猜想的第一帧、第二帧现象,目前没有弄清楚的是,为什么背景整个颜色会被改变。把锁定的区域大小再new一个同样大小、起始坐标相同的Rect来画方块就没有问题。

(2) hellogv大牛写的一个例子:http://blog.csdn.net/hellogv/archive/2010/11/03/5985090.aspx ,由于之前发现这个例子是部分刷新的,所以重点学了一下。在我遇到第一、二帧那个问题时,回头再看这个例子,思考它为什么没有这个问题。其实里面恰好第一、二帧什么都没有画而是直接显示面板(即unlockCanvasAndPost(canvas) ),不知道这是特意这样做的,还是有其它原因。


你可能感兴趣的:(SurfaceView的烦恼(二)-部分刷新与第一、二帧猜想)