2013년 7월 18일 목요일

[Android] (구 v1) GoogleMap API 사용전 설정해야 하는 부분

*중요:구글에서 제공하는 구글맵 등을 사용하려면 아래와같은 절차를 거처서 설정하시면 됩니다.

1. 구글 API 설치

-eclipse에서 구글 API 패키지를 설치하려면 이클립스의 [Window]->[Android SDK Manager] 메뉴를 클릭하면 위 아래와같은 창이 뜬다.

-여기에서 자기가 하고싶은 안드로이드 버전에 Google APIs 체크후 설치하면 된다.

2. 에뮬레이터 추가

제작된 프로젝트는 실행, 디버그, 프로파일링 및 테스트할 수 있어야 합니다. Android 에뮬레이터에서 지도 기반 애플리케이션을 실행하려면 Google API 애드온을 사용하도록 구성된 AVD(Android Virtual Device)를 설정해야 합니다. AVD를 설정하려면 Android AVD Manager를 사용합니다.

옵션 없이 android 명령을 사용하여 AVD Manager를 시작합니다. Eclipse/ADT에서 개발하는 경우에는 Window > Android SDK 및 AVD Manager에서 이 도구에 액세스 할 수도 있습니다.

- 새 AVD를 만들려면 '새로 만들기' 버튼을 클릭합니다.

- 대화상자가 표시되면 AVD의 이름을 지정하고 AVD에서 사용할 시스템 이미지 대상을 선택합니다. 'Google API(Google Inc.)' 타겟 중 하나를 선택하여 위에서 설명한 것처럼 API 수준이 애플리케이션의 매니페스트에서 선언된 android:minSdkVersion 특성과 일치하는 버전을 선택해야 합니다.

- 기타 옵션을 구성한 다음 'AVD 만들기'를 클릭합니다.

- AVD 만들기가 완료되면 AVD Manager UI에서 AVD를 실행하거나 에뮬레이터의 명령줄 인터페이스를 사용할 수 있습니다. Eclipse에서 개발하는 경우 AVD를 시작하도록 실행 구성을 설정하고 애플리케이션을 AVD에 설치할 수 있습니다.




3-1. 구글맵 API key 발급 받기(1)

3-1.1 디버그 서명 증명서의 MD5인증서 지문의 확인

- 디버그 서명 증명서 (debug.keystore 파일)는 Android SDK가 장동으로 생성
- 디버그 서명 증명서 (OS) 에 따라 생성되는 경로가 다르다.


3-1.2 Console 에서 OS 버전에 맞는 debug.keystore가 있는 경로로 이동한다.

3-1.3 명령어를 입력해준다

- 명령어: keytool -list -alias androiddebugkey -keystore debug.keystore -storepass android -keypass android -v
- JDK 7 부터는 기본값으로 SHA1 인증서 지문이 출력, 그래서 꼭 -v 옵션을 넣어야한다.
- JDK 6 에서는 MD5 인증서 지문이 기본값
- MD5 인증서 지문 저장


3-2. 구글맵 API key 발급 받기(2)

3-2.1 Google Map Service 에 접속해서 key 발급 요청

- 구글맵 API 키 얻는 URL: http://code.google.com/intl/ko/android/maps-api-signup.html
- Android Maps API key 사용에 관한 조건에 동의하고 위단계에서 얻은 MD5 인증서 지문을 입력란에 입력한다.

3-2.2 Android Maps API key 확인

[Android] GPS를 이용해 현재 위치 지도에서 보여주기(심화)

1. 사전 준비과정 및 지식

1.1 맵뷰와 맵액티비티

- 애플리케이션에 지도를 넣기 위해서는 화면 영역을 할당 받을 수 있도록 맵뷰(MapView)사용한다.
- 맵뷰를 사용할 때는 맵액티비티(MapActivity)를 같이 사용한다.

1.2 지도 API 키

- 구글맵을 사용할 때는 지도 API키를 발급받아 등록해야 구글서버에서 지도 데이터를 받을 수 있다.
- 지도키 받는 절차 링크 : http://ilililililililililili.blogspot.kr/2013/07/android-googlemap-api.html

1.3 구글 라이브러리의 사용

- 구글맵은 Google API가 포함된 플랫폼으로 에뮬레이터를 실행해야 한다.

1.4 매니페스트 수정

-지도를 추가하게 되면 크게 두 가지 권한(인터넷 접속 권한, GPS 위치정보 확인 권한)이 필요하다.
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-구글 라이브러리를 사용할때는 application 태그 안에 아래와같은 태그를 넣어주어야한다
    <uses-library android:name="com.google.android.maps"/>

1.5 레이아웃에 맵뷰 추가하기

-xml 코드
    <com.google.android.maps.MapView
        android:id="@+id/mapview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_above="@+id/scrollview"
        android:layout_below="@+id/button01"
        android:clickable="true"
        android:apikey="xxx" />
2. 예제
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;

public class MyLocationMapActivity extends MapActivity {

 TextView text01;
 LocationManager manager;
 MapView mapview;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  
  text01 = (TextView) findViewById(R.id.text01);
  mapview = (MapView) findViewById(R.id.mapview);
  
  // 줌인 줌아웃 버튼이 나오게 하는 설정
  mapview.setBuiltInZoomControls(true);
  
  Button button01 = (Button) findViewById(R.id.button01);
  button01.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    // LocationManager 객체 초기화 , LocationListener 리스너 설정
    getMyLocation();
   }
  });
  

 }

 // LocationManager 객체 초기화 , LocationListener 리스너 설정
 private void getMyLocation() {
  if (manager == null) {
   manager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
  }
  // provider 기지국||GPS 를 통해서 받을건지 알려주는 Stirng 변수
  // minTime 최소한 얼마만의 시간이 흐른후 위치정보를 받을건지 시간간격을 설정 설정하는 변수
  // minDistance 얼마만의 거리가 떨어지면 위치정보를 받을건지 설정하는 변수
  // manager.requestLocationUpdates(provider, minTime, minDistance, listener);

  // 10초
  long minTime = 10000;
  
  // 거리는 0으로 설정 
  // 그래서 시간과 거리 변수만 보면 움직이지않고 10초뒤에 다시 위치정보를 받는다
  float minDistance = 0;

  MyLocationListener listener = new MyLocationListener();

  manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime, minDistance, listener);

  appendText("내 위치를 요청 했습니다.");
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.my_location, menu);
  return true;
 }

 private void appendText(String msg) {
  text01.append(msg + "\n");
 }

 class MyLocationListener implements LocationListener {

  // 위치정보는 아래 메서드를 통해서 전달된다.
  @Override
  public void onLocationChanged(Location location) {
   appendText("onLocationChanged()가 호출되었습니다");

   double latitude = location.getLatitude();
   double longitude = location.getLongitude();

   appendText("현재 위치:" + latitude + "," + longitude);
   
   showMyLocation(latitude,longitude);
  }

  @Override
  public void onProviderDisabled(String provider) {

  }

  @Override
  public void onProviderEnabled(String provider) {

  }

  @Override
  public void onStatusChanged(String provider, int status, Bundle extras) {

  }

 }
 
 private void showMyLocation(double latitude,double longitude){
  int intlatitude = new Double(latitude).intValue();
  int intlongitude = new Double(longitude).intValue();
  
  // GeoPoint - 경도와 위도를 한점으로 표시하는 객체
  GeoPoint myPoint = new GeoPoint(intlatitude, intlongitude);
  
  // 맵뷰의 속성등을 컨트롤하는 클래스(맵뷰 어뎁터) 
  MapController controller = mapview.getController();
  
  // 애니메이션 효과가 나면서 점으로 이동
  controller.animateTo(myPoint);
  
  // 줌에는 단계가 있는데 정수값으로 표기한다
  controller.setZoom(17);
  
 }

 @Override
 protected boolean isRouteDisplayed() {
  // TODO Auto-generated method stub
  return false;
 }
}

[Android] GPS를 이용해 나의 위치 확인하기(기본)

1. 사전지식

1.1 위치기반서비스

- 모바일 네트워크를 통해 휴대 단말에서 접근할 수 있는 위치 정보 활용 시스템 입니다.
- LBS는 스마트폰 단말에서 자신의 위치를 찾을 수 있게 되면서 활성화 되었다.

1.2 위치기반서비스 활용 분야

- 엔터테인먼트, 위급상황, 의료, 산업, 개인 생활 등등

1.3 대표적인 사용 예

- 가장 가까운 은행, 식당, 주유소, 호텔, 골프장, 병원, 경찰서 찾기
- 출발지부터 목적지까지 가는 길 찾기
- 친구나 가족들의 위치 또는 행사 장소등을 알려주는 소셜 네트워킹 서비스, 친구찾기

1.4 위치를 찾는 대표적인 방법 두가지

- GPS 위성을 이용한 위치 찾기
- 기지국의 타워 ID를 이용한 위치 찾기

1.5 기지국을 이용한 위치 찾기 (cell Tower Triangulation)

- 가까이 위치하고 있는 기지국과의 상대적인 거리를 이용하는 방법
- 휴대폰과 기지국 간의 거리는 기지국으로 부터 휴대폰까지 신호를 보내고 받는 데 따른 지연 시간(lag time)으로 계산

2. 주요 객체 소개

2.1 LocationManager 객체

- 시스템의 위치기반 서비스 접근 지원 하는 객체
- 주기적으로 바뀌는 단말의 위치정보 수신

2.2 LocationListener 객체

-위치정보를 전달 받기 위한 리스너

2.3 Location 객체

-위치는 위도(latitude)와 경도(longitude)로 표시되며 시간은 UTCtimestamp 로 표시되고 그외에 선택적으로 고도(altitude),속도(speed)그리고 bearing정보 표시


3. 기능 구현 순서

3.1 위치관리자 객체 참조 하기

- 코드 37줄

3.2 위치 리스너 구현하기

- 코드 69-98줄

3.3 위치정보 업데이트 요청하기 (동시에 리스너 등록)

- 코드 53줄

3.4 매니페스트에 권한 추가하기
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>  // GPS
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>  //기지국

4.예제
import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MyLocationActivity extends Activity {

 TextView text01;
 LocationManager manager;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  text01 = (TextView) findViewById(R.id.text01);
  Button button01 = (Button) findViewById(R.id.button01);
  button01.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    // LocationManager 객체 초기화 , LocationListener 리스너 설정
    getMyLocation();
   }
  });
 }

 // LocationManager 객체 초기화 , LocationListener 리스너 설정
 private void getMyLocation() {
  if (manager == null) {
   manager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
  }
  // provider 기지국||GPS 를 통해서 받을건지 알려주는 Stirng 변수
  // minTime 최소한 얼마만의 시간이 흐른후 위치정보를 받을건지 시간간격을 설정 설정하는 변수
  // minDistance 얼마만의 거리가 떨어지면 위치정보를 받을건지 설정하는 변수
  // manager.requestLocationUpdates(provider, minTime, minDistance, listener);

  // 10초
  long minTime = 10000;
  
  // 거리는 0으로 설정 
  // 그래서 시간과 거리 변수만 보면 움직이지않고 10초뒤에 다시 위치정보를 받는다
  float minDistance = 0;

  MyLocationListener listener = new MyLocationListener();

  manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime, minDistance, listener);

  appendText("내 위치를 요청 했습니다.");
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.my_location, menu);
  return true;
 }

 private void appendText(String msg) {
  text01.append(msg + "\n");
 }

 class MyLocationListener implements LocationListener {

  // 위치정보는 아래 메서드를 통해서 전달된다.
  @Override
  public void onLocationChanged(Location location) {
   appendText("onLocationChanged()가 호출되었습니다");

   double latitude = location.getLatitude();
   double longitude = location.getLongitude();

   appendText("현재 위치:" + latitude + "," + longitude);
  }

  @Override
  public void onProviderDisabled(String provider) {

  }

  @Override
  public void onProviderEnabled(String provider) {

  }

  @Override
  public void onStatusChanged(String provider, int status, Bundle extras) {

  }

 }
}

2013년 7월 16일 화요일

[Android] 기본적인 Paint 사용법


1.예제
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.view.View;

//뷰를 상속받아 새로운 뷰를 만든다
public class CustomViewStyles extends View {
 
 // paint객체 - 그래픽 그리기를 위해 필요한 색깔 , 폰트 등을 저장하는곳
 private Paint paint;

 public CustomViewStyles(Context context) {
  super(context);

  paint = new Paint();
 }

 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  // 첫번째 사각형
  paint.setStyle(Style.FILL);
  paint.setColor(Color.RED);
  canvas.drawRect(10, 10, 100, 100, paint);
   
  paint.setStyle(Style.STROKE);
  paint.setStrokeWidth(2.0F);
  paint.setColor(Color.GREEN);
  canvas.drawRect(10, 10, 100, 100, paint);

  // 두번째 사각형
  paint.setStyle(Style.FILL);
  paint.setARGB(128, 0, 0, 255);
  canvas.drawRect(120, 10, 210, 100, paint);
  
  DashPathEffect dashEffect = new DashPathEffect(new float[]{5,5}, 1);
  paint.setStyle(Style.STROKE);
  paint.setStrokeWidth(3.0F);
  paint.setPathEffect(dashEffect);
  paint.setColor(Color.GREEN);
  canvas.drawRect(120, 10, 210, 100, paint);
  
  paint = new Paint();
  
  // 첫번째 원
  paint.setColor(Color.MAGENTA);
  canvas.drawCircle(50, 160, 40, paint);
  
  // 두번째 원
  // paint.setAntiAlias(true); 좀더 부드럽게 보이게 해준다
  paint.setAntiAlias(true);
  canvas.drawCircle(160, 160, 40, paint);
  
  // 첫번째
  paint.setStyle(Style.STROKE);
  paint.setStrokeWidth(1);
  paint.setColor(Color.MAGENTA);
  paint.setTextSize(30);
  canvas.drawText("Text (Stroke)", 20, 260, paint);

  // 첫번째 텍스트
  paint.setStyle(Style.FILL);
  paint.setTextSize(30);
  canvas.drawText("Text (채우기)", 20, 320, paint);
  
 }
 
}

2.결과

[Android] Paint , Path , Drawable , LinearGradient 사용예제 와 뷰의 폭과 높이 확인 방법 과 색상 리소스 정의 방법

1.예제
android version = jelly bean
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

public class CustomViewDrawables extends View {

 private ShapeDrawable upperDrawable;
 private ShapeDrawable lowerDrawable;

 public CustomViewDrawables(Context context) {
  super(context);

  // 윈도우 매니저를 이용해 뷰의 폭과 높이 확인
  // 뷰가 채워지는 화면의 크기를 알아오기 위해 시스템 서비스 객체인 WindowManager를 참조함
  WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
     Display display = manager.getDefaultDisplay();
     Point sizePoint = new Point();
     display.getSize(sizePoint);
     int width = sizePoint.x;
     int height = sizePoint.y;

  // 리소스에 정의된 색상값을 변수에 설정
  // 색상 정보는 xml 파일로 정의되며,[/res/values/]폴더 밑에 colors.xml이라는 이름으로 저장되어있다.
  // 안드로이드에서 색상을 만들어 사용하는 방법은 크게 자바 코드에서 사용하는 경우와 xml 리소스를 이용하는 경우로 나눌 수 있다.
  // 자바 코드에서는 Color 클래스에 정의된 상수 또는 Color.argb()메소드를 사용한다.
  Resources curRes = getResources();
  int blackColor = curRes.getColor(R.color.color01);
  int grayColor = curRes.getColor(R.color.color02);
  int darkGrayColor = curRes.getColor(R.color.color03);

  // upperDrawable 객체 생성
  upperDrawable = new ShapeDrawable();
  RectShape rectangle = new RectShape();
  rectangle.resize(width, height*2/3);
  upperDrawable.setShape(rectangle);
  upperDrawable.setBounds(0, 0, width, height*2/3);

  // 그라데이션 효과를 가지는 gradient객체 생성과 upperDrawable의 paint객체에 gradient객체를 shader로 설정
  LinearGradient gradient = new LinearGradient(0, 0, 0, height*2/3, grayColor, blackColor, TileMode.CLAMP);
  Paint paint = upperDrawable.getPaint();
  paint.setShader(gradient);

  // lowerDrawable 객체 생성
  lowerDrawable = new ShapeDrawable();
  RectShape rectangle2 = new RectShape();
  rectangle2.resize(width, height*1/3);
  lowerDrawable.setShape(rectangle2);
  lowerDrawable.setBounds(0, height*2/3, width, height);

  // 그라데이션 효과를 가지는 gradient 객체 생성과 lowerDrawable의 paint객체에 gradient객체를 shader로 설정
  LinearGradient gradient2 = new LinearGradient(0, 0, 0, height*1/3, blackColor, darkGrayColor, TileMode.CLAMP);
  Paint paint2 = lowerDrawable.getPaint();
  paint2.setShader(gradient2);

 }

 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  // 뷰 밑바탕이 되는 upperDrawable , upperDrawable 그리기
  upperDrawable.draw(canvas);
  upperDrawable.draw(canvas);

  // pathPaint 설정
  Paint pathPaint = new Paint();
  pathPaint.setAntiAlias(true);
  pathPaint.setColor(Color.YELLOW);
  pathPaint.setStyle(Style.STROKE);
  pathPaint.setStrokeWidth(16.0F);
  pathPaint.setStrokeCap(Cap.BUTT);
  pathPaint.setStrokeJoin(Join.MITER);

  // Path 설정
  Path path = new Path();
  path.moveTo(20, 20);
  path.lineTo(120, 20);
  path.lineTo(160, 90);
  path.lineTo(180, 80);
  path.lineTo(200, 120);

  // path 그리기
  canvas.drawPath(path, pathPaint);

  // pathPaint 설정
  pathPaint.setColor(Color.WHITE);
  pathPaint.setStrokeCap(Cap.ROUND);
  pathPaint.setStrokeJoin(Join.ROUND);

  // path 그리기
  path.offset(30, 120);
  canvas.drawPath(path, pathPaint);

  // pathPaint 설정
  pathPaint.setColor(Color.CYAN);
  pathPaint.setStrokeCap(Cap.SQUARE);
  pathPaint.setStrokeJoin(Join.BEVEL);

  // path 그리기
  path.offset(30, 120);
  canvas.drawPath(path, pathPaint);

 }

}

[Android] 뷰 위에 그래픽을 그리기

1. 뷰 위에 그래픽을 그리는 순서(정형화된 방법)

1.1 새로운 클래스를 만들고 뷰를 상속받는다.

1.2 페인트 객체를 초기화하고 필요한 속성을 설정한다.

1.3 뷰클래스에 정의된 onDraw() 오버라이딩해서 메서드 내에 원하는 그림을 그리는 메소드를 호출한다.

1.4 onTouch() 메서드 내에 터치 이벤트를 처리하는 코드를 넣는다.

1.5 새로 만든 뷰를 메인 액티비티에 추가한다.

2. 예제
android version = jelly bean
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

// 뷰를 상속받아 새로운 뷰를 만든다
public class CustomView extends View {

 // paint객체 - 그래픽 그리기를 위해 필요한 색깔 , 폰트 등을 저장하는곳
 Paint paint;
 float curX;
 float curY;

 public CustomView(Context context) {
  super(context);
  init();
 }

 public CustomView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 // 뷰의 초기화 작업
 private void init() {

                // 페인트 객체 생성 및 색깔 설정
  paint = new Paint();
  paint.setColor(Color.RED);
 }

 // 뷰에 그림을 그릴때 자동으로 호출되는 메서드
 // 이뷰가 화면상에 보여지기 바로전에 호출된다.
 @Override
 protected void onDraw(Canvas canvas) {

  // 뷰에 사격형을 그립니다.
  // canvas - 뷰의 표면에 직접 그릴 수 있도록 만들어 주는 객체로 그래픽 그리기를 위한 메소드가 정의되어 있다.
  canvas.drawRect(curX, curY, curX + 100.0f, curY + 100.0f,paint);
  
  super.onDraw(canvas);
 }

 // 뷰안에서 터치 이벤트가 발생할때 자동으로 호출되는 메서드
 // return 값은 true 로 만들어줘야한다.
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  int action = event.getAction();

  switch (action) {
  // 누른 상태
  case MotionEvent.ACTION_DOWN:
   break;
   
  // 누르고 욺직인 상태
  case MotionEvent.ACTION_MOVE:
   
   curX = event.getX();
   curY = event.getY();

   // 뷰의 그래픽을 다시 그리게 한다 .
   // OnDraw() 메서드를 호출하게 한다.
   invalidate();
   
   break;
   
  // 누르고 땔때 상태
  case MotionEvent.ACTION_UP:
   break;
  default:
  }

  return true;
 }

}

[Android] 사진찍기와 카메라 미리보기

*중요

*.1 Manifest에 주가해야되는 권한
- <uses-permission android:name="android.permission.CAMERA"/>  //카메라 사용
- <uses-permission android:name="android.permission.FLASHLIGHT"/>  //카메라 플레쉬 사용

1. 카메라 미리보기 주요과정

1.1 카메라 미리보는 SurfaceView를 사용함 따라서, SurfaceView 사용 패턴을 그대로 사용한다.(SurfaceHolder를 이용한 SurfaceView 컨트롤)

1.2 카메라에게 서피스뷰를 알려주는 메서드
camera.setPreviewDisplay(mHolder);
1.3 미리보기에 시작 메서드
camera.startPreview();
2. 카메라 캡처 주요과정

- 이부분은 주관적인 부분으로 정확하지않을수 있습니다.

2.1 카메라 셔터를 눌렀을때 일어나는 이벤트를 정의한 인터페이스를 재정의한다.
 Camera.ShutterCallback suttercallback = new Camera.ShutterCallback() {
  public void onShutter() {...}
 };
2.2 카메라 캡처후 사진데이터가 Raw 혹은 JPEG 형식으로 메모리에 적제되면 그것을 어떻게 처리할것인지 처리하는 인터페이스를 제정의한다.
 Camera.PictureCallback picturecallback = new Camera.PictureCallback() {
  public void onPictureTaken(byte[] data, Camera camera) {...}
 };
2.3 카메라 캡처 이벤트가 발생한곳(버튼이거나 뷰 등등)에 메서드를 넣어준다. 그리고 2.1 / 2.2 에서 생성한 객체들을 매개변수에 넣어준다
camera.takePicture(suttercallback , null , picturecallback);
3. SurfaceView 재정의 주요과정(전형적인 패턴)

-가끔 디테일한 설정이 필요할때는 SurfaceView를 재정의 해서 사용한다.

3.1 클래스 재정의 할때 SurfaceView 와 SurfaceView 의 상태컨트롤을 위한 SurfaceHolder.Callback를 상속받아서 정의한다
class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback
3.2 재정의된 SurfaceView를 컨트롤할 내부 변수 SurfaceHolder 객체를 선언하고 생성자나 다른 함수를 안에서 SurfaceHolder 객체를 생성한다.
private SurfaceHolder mHolder;
mHolder = getHolder();
3.3 카메라가 SurfaceView를 독점하기 위해선 SurfaceHolder의 타입은 항상SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS로 설정한다.
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
3.4 SurfaceView 의상태(메모리 유무 ,사이즈변화 등등)는 SurfaceHolder.Callback 인터페이스 에 정의된 메서드로 처리한다.(기존 다른 리스너 방식과 유사)
-mHolder.addCallback(this); //콜백 인터페이스 홀더에 설정
-public void surfaceCreated(SurfaceHolder holder)  // SurfaceView가 생성됬을때 호출된다
-public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)  //SurfaceView 크기가 변화될때 호출된다
-public void surfaceDestroyed(SurfaceHolder holder)  //SurfaceView 메모리 헤제됬을때 호출된다

4. 예제
android version = jelly bean
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Toast;


// SurfaceView를 이용해 미리보기 화면을 만든 후 사진찍기를 하는 방법에 대해 알 수 있습니다.
public class MainActivity extends Activity {
 
 // 캡처한 사진파일 저장할 이름
 public static String IMAGE_FILE = "capture.jpg";

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // FrameLayout에 재정의한 CameraSurfaceView 추가
  final CameraSurfaceView cameraView = new CameraSurfaceView(
    getApplicationContext());
  FrameLayout previewFrame = (FrameLayout) findViewById(R.id.previewFrame);
  previewFrame.addView(cameraView);

  // 버튼을 눌렀을때 캡처
  Button saveBtn = (Button) findViewById(R.id.saveBtn);
  saveBtn.setOnClickListener(new View.OnClickListener() {
   public void onClick(View v) {
    
    // cameraView에 있는 capture() 메서드 실행
    cameraView.capture(new Camera.PictureCallback() {
     
     // JPEG 사진파일 생성후 호출됨
     // 찍은 사진을 처리
     // PictureCallback 인터페이스 에 있는 onPictureTaken() 메서드
     // byte[] data - 사진 데이타
     public void onPictureTaken(byte[] data, Camera camera) {
      try {
       
       // 사진데이타를 비트맵 객체로 저장
       Bitmap bitmap = BitmapFactory.decodeByteArray(data,0, data.length);
       
       // bitmap 이미지를 이용해 앨범에 저장
       // 내용재공자를 통해서 앨범에 저장
       String outUriStr = MediaStore.Images.Media.insertImage(getContentResolver(), bitmap,"Captured Image","Captured Image using Camera.");

       if (outUriStr == null) {
        Log.d("SampleCapture", "Image insert failed.");
        return;
       } else {
        Uri outUri = Uri.parse(outUriStr);
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,outUri));
       }

       Toast.makeText(getApplicationContext(),"카메라로 찍은 사진을 앨범에 저장했습니다.",Toast.LENGTH_LONG).show();

       // 다시 미리보기 화면 보여줌
       camera.startPreview();
      } catch (Exception e) {
       Log.e("SampleCapture", "Failed to insert image.", e);
      }
     }
    });
   }
  });

 }

 // 카메라 미리보기를 위해 SurfaceView 클래스 재정의
 private class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
  
  // SurfaceView를 컨트롤할 SurfaceHolder 객체 선언
  private SurfaceHolder mHolder;

  // 미리보기를 위한 카메라 객체 선언
  private Camera camera = null;

  public CameraSurfaceView(Context context) {
   super(context);
   
   // SurfaceHolder 객체 생성 getHolder()는 SurfaceView 내부 함수
   mHolder = getHolder();

   // SurfaceHolder.Callback 인터페이스 장착
   mHolder.addCallback(this);

   // 카메라가 SurfaceView를  독점하기 위한 타입 설정
   // 버퍼를 사용하지않음
   mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  }

  // SurfaceView 가 메모리에 생성될때 호출된다.
  public void surfaceCreated(SurfaceHolder holder) {
   // 카메라를 사용할려고 한다는 설정
   camera = Camera.open();

   try {
    // 미리보기를 설정
    camera.setPreviewDisplay(mHolder);
   } catch (Exception e) {
    Log.e("CameraSurfaceView", "Failed to set camera preview.", e);
   }
  }
  
  // 보통 SurfaceView가 보여지기전 과 사이즈가 변화가있을때 호출된다.
  // 그래서 보여지기전인 surfaceCreated()가 호출된 다음 호출된다. 
  public void surfaceChanged(SurfaceHolder holder, int format, int width,
    int height) {

   // 카메라의 파라미터 값을 가져와서 미리보기 크기를 설정한다
   Camera.Parameters parameters= camera.getParameters();
   parameters.setPreviewSize(width, height);
   camera.setParameters(parameters);
   
   // 미리보기화면을 뿌려준다
   camera.startPreview();
  }

  // SurfaceView의 메모리가 해제되었을때 호출된다.
  // SurfaceView가 화면에 표시되지않을때(액티비티가 비활성화 될때) 호출된다.
  public void surfaceDestroyed(SurfaceHolder holder) {
   
   // 미리보기 중지
   camera.stopPreview();
   // 메모리 해제
   camera.release();
   camera = null;
  }

  
  // 사진을 찍을때 호출되는 함수 (스냅샷)
  public boolean capture(Camera.PictureCallback handler) {
   if (camera != null) {
    // 셔터후
    // Raw 이미지 생성후
    // JPE 이미지 생성후
    camera.takePicture(null, null, handler);
    return true;
   } else {
    return false;
   }
  }

 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 }
}