본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Android/안드로이드] Surface View 에 대해 알아보자.

by 돼지왕 왕돼지 2012. 2. 18.
반응형


안녕하세요 돼지왕 왕돼지입니다.
오늘은 Surface View 에 대해 간단히 알아보겠습니다.
이 글과 코드는 김상형씨의 안드로이드 프로그래밍 정복을 정리한 내용입니다.


Surface View 가 뭐고, 왜 사용하나요?

 
 일반 뷰는 Main Thread에서 캔버스에 그리기 수행합니다. 
 메인 스레드에서 그려야 하므로 속도가 빠르지 못하며( 다른 일들도 처리해야 하니.. ),
 그리기를 하는 동안에는 사용자의 입력 받을 수 없습니다.
 따라서 반응성이 좋지 못합니다. ( 게임류에는 쥐약입니다. 게임에서는 거의 대부분 SurfaceView 를 사용하죠.. )
 그렇다고, 그리는 작업을 스레드로도 분리할 수도 없습니다.
 안드로이드의 기본 정책으로, Main Thread 가 아닌 다른 스레드에서는 뷰나 캔버스를 직접 건드리지 못하기 때문이죠.
 


믿을 수 없다. Main 에서 그려보자.


class Ball{
  int x, y;
  int rad;
  int dx, dy;
  int color;
  int count;
 
  // 볼 만들기
 static Ball Create(int x, int y, int Rad){
     Random Rnd = new Random();
     Ball NewBall = new Ball();
  
     NewBall.x = x;
     NewBall.y = y;
     NewBall.rad = Rad;
     do{
         NewBall.dx = Rnd.nextInt(11) - 5;
         NewBall.dy = Rnd.nextInt(11) - 5;
      }while (NewBall.dx ==0 || NewBall.dy == 0);
  
     NewBall.count = 0;
     NewBall.color = Color.rgb(Rnd.nextInt(256), Rnd.nextInt(256), Rnd.nextInt(256));
     return NewBall;
 }
 
  // 볼 이동
 void Move(int Width, int Height){
     x += dx;
     y += dy;
  
     if (x <rad || x > Width - rad){
        dx *= -1;
        count++;
     }
  
     if (y < rad || y > Height - rad){
        dy *= -1;
        count++;
     }
  }
  
  // 볼 그리기
 void Draw(Canvas canvas){
     Paint pnt = new Paint();
     pnt.setAntiAlias(true);
  
     int r;
     int alpha;
  
     // 동심원을 그린다. (부드러운 느낌의 공이 나옴)
     for (r = rad, alpha = 1; r > 4; r--, alpha +=5){
         pnt.setColor(Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)));
         canvas.drawCircle(x, y, r, pnt);
     }
   }
}
 
class MyView extends View{
    Bitmap mBack;
    ArrayList<Ball> arBall = new ArrayList<Ball>();
    final static int DELAY = 50;
    final static int RAD = 24;
 
    public MyView (Context context){
       super(context);
       mBack = BitmapFactory.decodeResource(context.getResources(), R.drawable.family);
       mHandler.sendEmptyMessageDelayed(0, DELAY);
    }
 
    // Touch Event에 반응
    public boolean onTouchEvent(MotionEvent event){
        if (event.getAction() == MotionEvent.ACTION_DOWN){
           Ball NewBall = Ball.Create((int)event.getX(), (int)event.getY(), RAD);
           arBall.add(NewBall);
           invalidate();
           return true;
       }
       return false;
    }
 
   // 볼 그려주는 역할
   public void onDraw(Canvas canvas){
       canvas.drawBitmap(mBack, 0, 0, null);
       for (int idx = 0; idx < arBall.size(); idx++){
           arBall.get(idx).Draw(canvas);
       }
   }
 
   // 볼 이동시키고 제거해주는 역할 + 다시 드로우
   Handler mHandler = new Handler(){
        public void handleMessage(Message msg){
           Ball B;
           for (int idx = 0; idx < arBall.size(); idx++){
              B = arBall.get(idx);
              B.Move(getWidth(), getHeight());
              if (B.count > 4){
                  arBall.remove(idx);
                  idx--;
              }
          }
   
          invalidate();
         mHandler.sendEmptyMessageDelayed(0, DELAY);
      }
  };
}


어떤가요? 위와 같이 하면 CPU 점유율도 금방 높아지고, 말한대로 반응성도 안 좋아지죠??
금방 ANR 이 발생합니다.

 
 

SurfaceView 에 대해 그럼 먼저 자세히 알려주세요.

 
- SurfaceView는 canvas가 아닌 Surface를 갖고 있습니다. (가상 메모리 화면)
  -> 메인 스레드가 표면(Surfcae)의 변화를 감지해서 스레드에게 그리기 허용 여부를 알려 줘야 하며, 이는 SurfaceHolder.Callback 으로 합니다.
 
 
- void surfaceCreated (SurfaceHolder holder)
   : 표면이 처음 생성된 직후에 호출됩니다. 이때부터 표면에 그리기가 허용됩니다.
     표면에는 한 스레드만 그리기를 수행할 수 있습니다.

 
void surfaceDestroyed (SurfaceHolder holder)
   : 표면이 파괴되기 직전에 호출됩니다. 이게 리턴된 후에는 더 이상 그리면 안됩니다.

 
void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
  : 표면의 색상이나 포맷이 변경되었을 때 호출. 최소한 한번 호출됩니다.
    이 메서드로 전달된 인수를 통해 표면의 크기 초기화하곤 하죠.

 
class MyView extends SurfaceView implements SurfaceHolder.Callback
   : 표면 관리주체는 SurfaceHolder. 이 객체를 통해 표면 크기, 색상 등을 관리합니다.
    즉 View 는 보여주기만 할 뿐, Control 은 Holder 로 한다는 이야기지요. 
 
 
SurfaceHolder SurfaceView.getHolder()
   void SurfaceHolder.addCallback (SurfaceHolder.Callback callback)
    : 시스템이 표면의 변화가 발생할 때마다 콜백 메서드 호출
 
 
Canvas SurfaceHolder.lockCanvas ()
  : 표면을 잠그로 표면에 대한 캔버스 제공합니다. lock 된 상태에서 이 canvas에 그림을 그리고, unlock 이 되면 화면출력이 됩니다.

 
void SurfaceHolder.unlockCanvasAndPost (Canvas canvas)
  : 표면 비트맵에 그려진 그림을 화면으로 내보내 출력을 한다.
 
 
 

이번엔 SurfaceView 를 사용하여 그려보자.

 

class SurfView extends SurfaceView implements SurfaceHolder.Callback{
    Bitmap mBack;
    ArrayList<Ball> arBall = new ArrayList<Ball>();
    final static int DELAY = 50;
    final static int RAD = 24;
 
    SurfaceHolder mHolder;
    DrawThread mThread;
 
 public SurfView (Context context){
     super(context);
     mBack = BitmapFactory.decodeResource(context.getResources(), R.drawable.family);
  
     mHolder = getHolder();
     mHolder.addCallback(this);
 }
 
 public void surfaceCreated(SurfaceHolder holder){
     mThread = new DrawThread(mHolder);
     mThread.start();
 }
 
 public void surfaceDestroyed(SurfaceHolder holder){
     mThread.bExit = true;
     for (;;){
         try{
             mThread.join(); // Thread 종료 기다리기
             break;
         }
          catch (Exception e){;}
     }
 }
 
 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
      if (mThread != null){
          mThread.SizeChange(width, height);
      }
 }
 
 public boolean onTouchEvent(MotionEvent event){
    if (event.getAction() == MotionEvent.ACTION_DOWN){
       synchronized(mHolder){
             Ball NewBall = Ball.Create((int)event.getX(), (int)event.getY(), RAD);
             arBall.add(NewBall);
       }
       return true;
    }
    return false;
 }
 
 class DrawThread extends Thread{
    boolean bExit;
    int mWidth, mHeight;
    SurfaceHolder mHolder;
  
    DrawThread(SurfaceHolder Holder){
        mHolder = Holder;
        bExit = false;
    }
  
    public void SizeChange(int Width, int Height){
        mWidth = Width;
        mHeight= Height;
    }
  
    public void run(){
       Canvas canvas;
       Ball B;
   
       while (bExit == false){
           for (int idx = 0; idx < arBall.size(); idx++){
              B = arBall.get(idx);
              B.Move(mWidth, mHeight);
              if (B.count > 4){
                 arBall.remove(idx);
                 idx--;
              }
          }
 
        synchronized(mHolder){
           canvas = mHolder.lockCanvas();
           if (canvas == null) break;
           canvas.drawColor(Color.BLACK);
           canvas.drawBitmap(mBack, 0, 0, null);
     
           for (int idx = 0 ; idx < arBall.size(); idx++){
              arBall.get(idx).Draw(canvas);
              if (bExit) break;
           }
     
           mHolder.unlockCanvasAndPost(canvas);
        }
    
        try {Thread.sleep(MyView.DELAY);} catch(Exception e){;}
      }
   }
 }

 
 
로그인 없이 추천 가능합니다. 손가락 꾸욱~





반응형

댓글