본문 바로가기

Android

안드로이드 하드웨어

hw 관련 api

 

  • telephony : 통화, 폰 상태
  • 멀티미디어 기록,재생 라이브러리
  • 카메라: 촬영, 미리보기
  • 센서 하드웨어 확장 지원
  • 가속도 센서 및 나침반 api: 방향, 움직임
  • 블루투스, 네트워크, wi-fi hw 관리

 

 

Media API 이용하기

jpeg, png, ogg, mpeg4, 3gpp, mp3, bitmap 미디어 포맷 지원. 즉 decoder/encoder 존재

 

medea resource 재생

안드로이드 멀티미디어 재생: MediaPlayer

미디어플레이어는 안드로이드 표준 출력 장치(스피커, 헤드셋 등) 이용해 오디오 재생, 전화통화 내용 오디오 재생은 현재 가능하지 않다.

 

 

  • 객체 생성

    • new MediaPlayer
    • MediaPlayer.create(Uri )
  • MediaPlayer.prepare() 미디어 준비
  • MediaPlayer.start() 재생 시작
  • MediaPlayer.stop() 재생 멈춤
  • MediaPlayer.pause()
  • MediaPlayer.seek()
  • MediaPlayer.setLooping() 재생 반복
  • MediaPlayer.release() 미디어플레이어 사용후 리소스 해지!

    • 동시에 존재할 수 있는 객체수 제한으로 해제가 꼭 필요.

 

상태 다이어그램

http://developer.android.com/reference/android/media/MediaPlayer.html

 

  1. private String MEDIA_FILE_PATH = Settings.System.DEFAULT_RINGTONE_URI.toString();
  2. MediaPlayer mpFile = new MediaPlayer();
        try {
          mpFile.setDataSource(MEDIA_FILE_PATH); // 미디어 리소스 uri
          mpFile.setLooping(false);
          mpFile.prepare(); // 리소스 파일 prepare...
          mpFile.start();
          mpFile.stop();
        }
        catch (IllegalArgumentException e) {}
        catch (IllegalStateException e) {}
        catch (IOException e) {}

 

 

  1. // prepare 가 create() 내부에서 호출된다.
  2. MediaPlayer mpRes = MediaPlayer.create(getApplicationContext(), R.raw.my_sound);
    mpRes.start();
    mpRes.stop();
  • 애뮬레이터는 개발 플랫폼의 오디오 출력을 이용해 오디오 재생을 시뮬레이션 한다.

 

 

멀티미디어 기록하기

기록은 MediaRecoder : http://developer.android.com/reference/android/media/MediaRecorder.html

 

  1.           MediaRecorder recorder =newMediaRecorder();
    recorder
    .setAudioSource(MediaRecorder.AudioSource.MIC);
    recorder
    .setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    recorder
    .setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    recorder
    .setOutputFile(PATH_NAME);
    recorder
    .prepare();
    recorder
    .start();// Recording is now started...
    recorder
    .stop();
    recorder
    .reset();// You can reuse the object by going back to setAudioSource() step
    recorder
    .release();// Now the object cannot be reused

 

http://www.benmccann.com/dev-blog/android-audio-recording-tutorial/

          /**
* @author <a href="http://www.benmccann.com">Ben McCann</a>
*/
public class AudioRecorder {

final MediaRecorder recorder = new MediaRecorder();
final String path;

/**
* Creates a new audio recording at the given path (relative to root of SD card).
*/
public AudioRecorder(String path) {
this.path = sanitizePath(path);
}

private String sanitizePath(String path) {
if (!path.startsWith("/")) {
path = "/" + path;
}
if (!path.contains(".")) {
path += ".3gp";
}
return Environment.getExternalStorageDirectory().getAbsolutePath() + path;
}

/**
* Starts a new recording.
*/
public void start() throws IOException {
String state = android.os.Environment.getExternalStorageState();
if(!state.equals(android.os.Environment.MEDIA_MOUNTED)) {
throw new IOException("SD Card is not mounted. It is " + state + ".");
}

// make sure the directory we plan to store the recording in exists
File directory = new File(path).getParentFile();
if (!directory.exists() && !directory.mkdirs()) {
throw new IOException("Path to file could not be created.");
}

recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(path);
recorder.prepare();
recorder.start();
}

/**
* Stops a recording that has been previously started.
*/
public void stop() throws IOException {
recorder.stop();
recorder.release();
}

}

 

 

카메라 이용하기

http://developer.android.com/reference/android/hardware/Camera.html

 

  1. // 카메라 사용을 허용, auto focus는 아님
    <uses-permission android:name="android.permission.CAMERA" />

 

  • Camera class: 카메라 설정, 촬영, 미리보기 가능

    • Camera myCamera = Camera.open() 객체 생성
    • myCamera.release() 객체 해재한다.
  • Camera.Parameters : 카메라 설정 클래스. 이미지 및 미리보기 포맷, 크기, 미리보기 프레임 속도 등을 설정.

    • Camera.Parameters parameters = camera.getParameters();
    • parameters.setPictureFormat(PixelFormat.JPEG);
    • parameters.setPreviewSize(w, h); // 미리보기 크기
      camera.setParameters(parameters); // 카메라에 파라미터를 설정한다.
    •  

 

카메라 미리보기

카메라 미리보기는 SurfaceView 에 실시간으로 표시될 수 있다.

  1. mCamera = Camera.open();
    ...
  2. mCamera.setPreviewDisplay(mySurface);
  3. mCamera.startPreview();
  4. ...
  5. mCamera.stopPreview();

 

미리보기의 프레임을 .setPreviewCallback()으로 새롭게 정의할 수 있다.

  1. mCamera.setPreviewCallback(new PreviewCallback() {         
          public void onPreviewFrame(byte[] _data, Camera _camera) {
            // TODO Do something with the preview image.
          } });

 

 

사진 찍기

takePicture()에 RAW, JPEG 인코딩 이미지와 셔터 callback으로 구현한다.

  • ShutterCallback : 셔터 처리
  • PictureCallback : 이미지 인코딩

 

  1. private void takePicture() {
        camera.takePicture(shutterCallback, rawCallback, jpegCallback);
      }

      ShutterCallback shutterCallback = new ShutterCallback() {
        public void onShutter() {
          // Shutter has closed
        }
      };
       
      PictureCallback rawCallback = new PictureCallback() {
        public void onPictureTaken(byte[] _data, Camera _camera) {
          // TODO Handle RAW image data
        }
      };

      PictureCallback jpegCallback = new PictureCallback() {
        public void onPictureTaken(byte[] _data, Camera _camera) {
          // TODO Handle JPEG image data
        }
      };   

 

 

센서 관리자

세선서비스에 대한 레퍼런스 얻기

http://vissel.tistory.com/90

http://code.google.com/p/moonblink/wiki/Tricorder

 

  1. String service_name = Context.SENSOR_SERVICE;
    SensorManager sensorManager = (SensorManager)getSystemService(service_name);
    // 센서 모니터링 SensorListener().
    SensorListener mySensorListener = new SensorListener() {
  2.      // sensor: 이벤트가 발생한 센서
  3.      // values: 감지된 값들의 배열
  4.       public void onSensorChanged(int sensor, float[] values) {
            // 센서 값의 변화
          }
          public void onAccuracyChanged(int sensor, int accuracy) {
            // 센서 정확도 변화
          }
        };

 

  • onAccuracyChanged()의 accuracy 값

    •     SensorManager.SENSOR_STATUS_ACCURACY_HIGH; 센서가 가장 높은 정확도
    •     SensorManager.SENSOR_STATUS_ACCURACY_LOW; 센서가 가장 낮은 정확도로 보고, 조정이 필요,
    •     SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM; 센서가 평균 정확도, 조정으로 측정이 향상 가능
    •     SensorManager.SENSOR_STATUS_UNRELIABLE; 센서 데이터를 신뢰할 수 없음. 조정필요, 측정 불가능
  • sensor 상수

    • SensorManager.SENSOR_ACCELEROMETER : 가속도센서, 현재 가속도를 xyz 축을 따라 제곱초당 미터(m/s^2) 로 제공.
    • SensorManager.SENSOR_ORIENTATION: 방향센서, xyz축의 현재 방향을 degree 로 제공
    • SensorManager.SENSOR_LIGHT: 광센서(ambient-light), 주변 조도를 lux 로 제공.
    • SensorManager.SENSOR_MAGNETIC_FIELD : xyz축의 자기장을 마이크로테슬라(uT)로 제공
    • SensorManager.SENSOR_PROXIMITY: 장치와 대상 물체간의 거리를 m로 제공
    • SensorManager.SENSOR_TEMPERATURE: 온도 센서,

 

SensorManager.SENSOR_TRICORDER 는 모든 센서?

 

특정 센서를 모티너링 하려면, 리스너와 해당 센서를 등록한다.

  1. sensorManager.registerListener(mySensorListener, SensorManager.SENSOR_TRICORDER, SensorManager.SENSOR_DELAY_FASTEST);
  • 등록 두번째 인수는 센서관리자가 센서의 적합한 업데이트 속도를 찾게 한다. 센서 관리자는 지정한 속도보다 빠른 것을 리턴하는 경향이 있다.

    • SensorManager.SENSOR_DELAY_FASTEST: 가장 빠른 센서 업데이트 속도
    • SensorManager.SENSOR_DELAY_GAME: 게임 제어에 적합한 업데이트 속도
    • SensorManager.SENSOR_DELAY_NORMAL : 기본 업데이트 속도
    • SensorManager.SENSOR_DELAY_UI : UI 기능에 적합한 업데이트 속도

 

 

가속도 센서와 나침반 이용하기

  • 하드웨어의 현재 방향 측정
  • 방향 변화 모니터
  • 사용자가 향하고 있는 방향 식별
  • 위-아래, 좌-우, 앞-뒤 방향 가속도 모니터

 

응용
  • 도난방지: 도서관 같은곳에서 폰이 진동, 움직임이 있으면 경고 - sms, 소리 등등...
  • 맵에 움직이는 위치 표시, 폰의 가로-세로 움직임, 제스처 인터페이스

 

가속도 accelerometer 센서

단위시간당 속도의 변화 검출. 즉, 센서가 직접 속도를 측정하지 않고 시간에 대한 가속도의 변화를 측정해야 한다.

기계식/반도체식. 검출감도(V/g): 가속도당 전압변화, 클수록 우수.

  • 가속도 센서는 움직임에 의한 가속도와 중력에 의한 가속도를 구분하지 못한다. 즉 Z축에 대한 정지상태의 값응 -0.98m/s^2 으로
    SensorManager.STANDARD_GRAVITY 이다.

 

중력가속도 기준 물체의 기울기는 수직(90도) 1G, 즉 중력가속도는 sin(기울어진 각도) 관계. 0.5G 중력가속도=30도. 즉 x축 방향이 0G(누워있고), y축 방향이 1G(서있고).  x축 방향으로 40도 경사를 갖게 됨변 1G x sin45 로 0.707G가 검지. 따라서 기울어진 상태, 지면방향 감지.

  출처: http://makiable.egloos.com/3456635

 

가속도 변화 감지

 

 출처: http://www.societyofrobots.com/sensors_accelerometer.shtml

수직(z 축): + 값은 장치가 위쪽 / 세로(Longitudinal): 앞족-뒤쪽 방향, +값은 앞 - 장치 화면이 위로 향하고 몸체가 세로로 움직임

가로(Lateral): 왼쪽-오른쪽, +값은 오른쪽으로 움직임.

 

SensorManager 참조를 가지고 가속도 센서를 등록해서 사용.

  1. // Accelerometer
     SensorManager sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
  2. SensorListener myAccelSensorListener = new SensorListener() {
              public void onSensorChanged(int sensor, float[] values) {
                if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
                  float xAxis_lateralA = values[SensorManager.DATA_X];
                  float yAxis_longitudinalA = values[SensorManager.DATA_Y];
                  float zAxis_verticalA = values[SensorManager.DATA_Z];
                  float raw_xAxis_lateralA = values[SensorManager.RAW_DATA_X];
                  float raw_yAxis_longitudinalA= values[SensorManager.RAW_DATA_Y];
                  float raw_zAxis_verticalA = values[SensorManager.RAW_DATA_Z];
  3.               // 가속도 변화를 app에 적용...
                }
              }
          public void onAccuracyChanged(int sensor, int accuracy) { }
    };
    sm.registerListener(myAccelSensorListener, SensorManager.SENSOR_ACCELEROMETER, SensorManager.SENSOR_DELAY_UI);

 

속도계 표현

가속도센서는 주어진 방향의 속도 변화를 측정하기 때문에, 각각 가속도 값이 얼마나 오래 적용되었나 측정하여 현재 속도를 확립할 수 있다.

예) 1m/sec^2 고정 속도로 가속한다면 10초 뒤 속도는 10m/sec(or 36km/h) 가 될 것이다.

 

 

예제: com.example.android.apis.os.Sensors, com.example.android.apis.graphics.SensorTest

 

방향 측정

방향(Orientation) 센서: yaw(머리)를 제공하는 나침반, pitch 및 roll 을 측정하는 가속도 센서의 조합이다. SensorManager는 장치가 평평한 곳에 놓였을 때를 헤딩/피치/롤이 0으로 간주.

  • heading 혹은 yaw, bearing: z축 0도 북, 90도 동, 180도 남, 270도 서쪽 방향
  • pitch : y축 방향 장치의 각도, 평평한 곳 0도, 바로 서있을 때 -90도, 거꾸로 서있을 때 90도, 뒤집혀 있을 때 180/-180도
  • roll: x축 방향 장치의 측면 각도. 평평한 곳 0도,

values[]의 값은 SensorManager.DATA_X,Y,Z으로 찾고, 필터/간략화되지 않은 값은 RAW_DATA_X,Y,Z 을 사용한다.

  1.  SensorManager sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
  2. // Orientation   
    SensorListener myOrientationListener = new SensorListener() {
              public void onSensorChanged(int sensor, float[] values) {
                if (sensor == SensorManager.SENSOR_ORIENTATION) {
                  float headingAngle = values[0]; // SensorManager.DATA_X
                  float pitchAngle = values[1];
                  float rollAngle =  values[2];

                  //앱에 방향을 적용.
                }
            }
          public void onAccuracyChanged(int sensor, int accuracy) { }
       };
    sm.registerListener(myOrientationListener, SensorManager.SENSOR_ORIENTATION);

 

 

Telephony

사용자용 다이얼러, 앱에서 전화 처리, 폰 상태 모니터링을 통합해 준다. 단 사용자의 통화중 in call 상태, 걸려오는 전화/발신 전화용 화면을 만들 수는 없다.

 

전화 걸기

전화 걸기는 인텐트의 ACTION_CALL, ACTION_DIAL 액션이 최선의 예제이다. 모두 'tel: ' 스키마로 전화번호를 인텐트 데이터로 지정해 주어야 한다.

 

  • Intent.ACTION_CALL : 자동으로 전화 걸고, 통화중 애플 표시, 네이티브 다이얼러를 대체하는 경우만 이용해야, 그 이외는  action-dial을 이용. 이 액션은 CALL_PHONE 권한을 부여 받아야 한다.
  • Intent.ACTION_DIAL 바로 전화를 걸지 않고, 다이얼러 시작 - 지정된 번호 전달.

 

  1. Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:1234567"));
            startActivity(intent);

 

폰 상태

Telephony api는 폰 상태, 수신 전화번호 검색, 통화 관리. TelephonyManager가 관리.

 

폰의 상태 모니터링 골격

 

상태 읽기 권한을 요구,
  1. <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>

 

  1.         TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
  2. // 리스너로 phone의 상태가 다른 컴포넌트로 알려진다.
  3. //
            PhoneStateListener phoneStateListener = new PhoneStateListener() {
              public void onCallForwardingIndicatorChanged(boolean cfi) {} // 통화 전달.
              public void onCallStateChanged(int state, String incomingNumber) {} // 통화상태(벨, 끊기)
              public void onCellLocationChanged(CellLocation location) {} // 셀위치변경,
              public void onDataActivity(int direction) {}
              public void onDataConnectionStateChanged(int state) {} // 데이터 연결
              public void onMessageWaitingIndicatorChanged(boolean mwi) {} // 대기
              public void onServiceStateChanged(ServiceState serviceState) {} // 폰서비스변경,
              public void onSignalStrengthChanged(int asu) {} // 모바일신호세기
            };
    // 모니터링 할 이벤트를 리스너에 등록한다.
            telephonyManager.listen(phoneStateListener,
                                PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR |
                                PhoneStateListener.LISTEN_CALL_STATE |
                                PhoneStateListener.LISTEN_CELL_LOCATION |
                                PhoneStateListener.LISTEN_DATA_ACTIVITY |
                                PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
                                PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR |
                                PhoneStateListener.LISTEN_SERVICE_STATE |
                                PhoneStateListener.LISTEN_SIGNAL_STRENGTH);
    // 리스너를 해제
            telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);

 

 

전화 통화 모니터링

수신/발신 전화를 감지하고 이에 반응한다.

  1. PhoneStateListener phoneStateListener = new PhoneStateListener() {
              public void onCallStateChanged(int state, String incomingNumber) {// 전화 수신 반응.
  2.                // 착신 전화 번호를 받는다.
  3.                 switch (state) {
                    case TelephonyManager.CALL_STATE_IDLE : break; // 폰이 울리거나 통화중이 아님.
                    case TelephonyManager.CALL_STATE_RINGING : break; // 폰이 울린다.
                    case TelephonyManager.CALL_STATE_OFFHOOK : break; // 폰이 현재 통화 중.
                    default: break;
                  }
  4.           }
  5. }
  6. // 모니터링 할 이벤트를 리스너에 등록한다.
    telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE );
  7.         PhoneStateListener cellLocationListener = new PhoneStateListener() {
              public void onCellLocationChanged(CellLocation location) {
                GsmCellLocation gsmLocation = (GsmCellLocation)location;
                Toast.makeText(getApplicationContext(), String.valueOf(gsmLocation.getCid()), Toast.LENGTH_LONG).show();
              }
            };

 

 

셀 위치 추적

 

 

셀의 위치 변화 권한 요구
  1. <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>

 

  1. PhoneStateListener cellLocationListener = new PhoneStateListener() {
  2. // CellLocation: cell id(getCid)와 LAC(getLac()) 메서드 제공.
              public void onCellLocationChanged(CellLocation location) {
                GsmCellLocation gsmLocation = (GsmCellLocation)location;
                Toast.makeText(getApplicationContext(), String.valueOf(gsmLocation.getCid()), Toast.LENGTH_LONG).show();
              }
            };
  3. telephonyManager.listen(cellLocationListener, PhoneStateListener.LISTEN_CELL_LOCATION);

 

 

서비스 변화 추적

ServiceState 객체로 서비스 변화 상태 감지

  • ServiceState.STATE_IN_SERVICE : 일반적 폰 서비스
  • ServiceState.STATE_EMERGENCY_ONLY : 긴급 통화용
  • ServiceState.STATE_OUT_OF_SERVICE : 서비스 이용 불가
  • ServiceState.STATE_POWER_OFF : 폰 무선 장치가 꺼짐(비행모드에서 활성화된다)
  • getOperator*** 로 시작하는 메서드는 서비스 사업자에 대한 세부 정보를 얻는다.

 

  1. PhoneStateListener serviceStateListener = new PhoneStateListener() {
              public void onServiceStateChanged(ServiceState serviceState) {
                    if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
                  String toastText = serviceState.getOperatorAlphaLong();
                  Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
                }
              }
            };
        telephonyManager.listen(serviceStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);

 

 

데이터 연결 및 활동 모니터링

모바일 데이터 전송, 연결에 대해 모니터링

 

  1. PhoneStateListener dataStateListener = new PhoneStateListener() {
              public void onDataActivity(int direction) {
                switch (direction) {
                  case TelephonyManager.DATA_ACTIVITY_IN : break;
                  case TelephonyManager.DATA_ACTIVITY_OUT : break;
                  case TelephonyManager.DATA_ACTIVITY_INOUT : break;
                  case TelephonyManager.DATA_ACTIVITY_NONE : break;
                }
              }
              public void onDataConnectionStateChanged(int state) {
                switch (state) {
                  case TelephonyManager.DATA_CONNECTED : break;
                  case TelephonyManager.DATA_CONNECTING : break;
                  case TelephonyManager.DATA_DISCONNECTED : break;
                  case TelephonyManager.DATA_SUSPENDED : break;
                }
              }
            };
            telephonyManager.listen(dataStateListener,
                                    PhoneStateListener.LISTEN_DATA_ACTIVITY |
                                    PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);

 

 

폰 속성 및 상태

 

  1. // 사업자 정보.
  2.         String networkCountry = telephonyManager.getNetworkCountryIso();
            String networkOperatorId = telephonyManager.getNetworkOperator();
            String networkName = telephonyManager.getNetworkOperatorName();
            int networkType = telephonyManager.getNetworkType();

            String incomingCall = null;
  3. // 전화가 왔을 때 해당 전화 번호
            if (telephonyManager.getCallState() == TelephonyManager.CALL_STATE_RINGING)
                incomingCall = telephonyManager.getLine1Number();

 

 

 

폰 제어하기

폰의 hw 접근은 SDK 1.x 이전에 삭제되었다. 향후 구현될 가능성에 대비해 가이드 수준으로 남겨둔다.

 

Phone class: 하드웨어 설정 제어, 전화 제어, 걸고/끊고, 컨퍼런스 콜 처리 등 다양한 폰기능 인터페이스

Phone 클래스 얻기
  1. Phone phone = telephonyManager.getPhone();
  2. phone.dial()
  3. phone.call()
  4. phone.endCall()

 

  1. String badPrefix = "+234";
  2. PhoneStateListener dataStateListener = new PhoneStateListener() {
  3.     public void onCallStateChanged(int state, String incomingNumber) {
  4.     if(state == TelephonyManager.CALL_STATE_RINGING ) {
  5.         Phone phone = telephonyManager.getPhone();
  6.         if( incomingNumber.starsWith(badPrefix) ) {
  7.           phone.endCall();
  8.         }
  9. };

 

 

 

블루투스 이용하기

이 책은 1.0기반의 가이드로 다루었다. 대신 현실에 맞게 안드로이드 디벨러퍼 가이드로 대신한다.

 

http://developer.android.com/reference/android/bluetooth/package-summary.html

 

다른 장치와 페어링을 이루면 RfcommSocket 을 시작해서 데이터 스트림을 주고 받을 수 있다.

Cupcake 1.5
  • Stereo Bluetooth support (A2DP and AVCRP profiles)
  • Auto-pairing
  • Improved handsfree experience
Eclair 2.0
  • Bluetooth 2.1
  • New BT profiles: Object Push Profile (OPP) and Phone Book Access Profile (PBAP)

 

 

안드로이드 블루투스 서비스

블루투스 API (android.bluetooth )들은 애플리케이션이 1:1(point-to-point)과 1:N(multipoint) 무선 기능이 가능한 다른 블루투스 디바이스에 무선으로 연결하는 것을 허용한

  • 다른 블루투스 디바이스에 대한 스캔Scan
  • 패어드paird 블루투스 디바이스에 대한 로컬 블루투스 어댑터 쿼리
  • RFCOMM 채널 확립
  • 서비스 검색을 통한 다른 디바이스 연결
  • 다른 디바이스로의 또는 다른 디바이스로부터의 데이터 전송
  • 다중 커넥션 관리

 

주요 클래스

BluetoothAdapter: 블루투스 장치 검색, 페어드된 디바이스 목록 쿼리, mac address로 Bluttoothdeive 객체화, BluetoothServerSocket으로 listen.

BluetoothDevice: 원격 블루투스 장치.

BluetoothSocket: TCP socket과 유사, InputStream, OutpuStream 으로 데이터 교환을 허용하는 연결지점. 서버 연결 성공시 BluetoothSocket이 생성.

BluetoothServerSocket : TCP ServerSocket과 유사, 하나는 Serversocket을 열어주어야 한다.

BluetoothClass : 블루투스 디바이스의 일반적인 특징과 기능, profile을 설명하지 않는다.

 

 

BLUETOOTH권한

커넥션 요청requesting, 커넥션 수락accepting, 그리고 테이터 전송과 같은 임의의 블루투스 커뮤니케이션을 수행

  1. <uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>

 

디바이스 검색discovery을 시작하거나 블루투스 설정을 조작manipulate

  1. <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>
  • BLUETOOTH_ADMIN 퍼미션을 사용한다면, BLUETOOTH 퍼미션 또한 보유해야 한다.

 

Bluetooth 사용

1) BluetoothAdapter

전체 시스템에 대한 하나의 블루투스 어댑터가 있다

  1. BluetoothAdapter mBluetoothAdapter   = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        // Device does not support Bluetooth
    }

 

2) 블루투스 활성화
  1. if (!mBluetoothAdapter.isEnabled()) { // 현재 블루투스 활성화 여부.
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); // 활서화 요청.
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }
  2. ...
  3.     @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            switch (resultCode) {
            case (RESULT_OK):
                break;
            }RESULT_CANCELED가
        }
  • 블루투스를 활성화하기 위해 사용자 퍼미션을 요구하는 하나의 다이얼로그
  • 블루투스 활성화가 성공하면, 여러분의 액티비티는 onActivityResult() 콜백에서 RESULT_OK 결과 코드를 수신할 것이다
  • 검색 가능(discoverability)을 활성화하는 것은 자동으로 블루투스를 활성화할 것이다.

ACTION_STATE_CHANGED 브로드캐스트 인텐트를 수신

블루투스 상태가 변경될 때마다 시스템에 의해 발송된다. 이 브로드캐스트는 엑스트라extra 필드에 각각 신규와 이전 블루투스 상태를 포함하는 EXTRA_STATE와 EXTRA_PREVIOUS_STATE를 포함한다

 

블루투스 검색(Finding)

디바이스 검색discovery은 블루투스가 활성화된 디바이스를 로컬 영역을 찾아서 각각에 대한 약간의 정보를 요청하는 스캐닝scanning 절차이다(이것은 가끔은
“discovering” , “inquiring” or “scanning”으로 언급된다). 디바이스가 검색 가능하다면, 그것은 디바이스 이름, 클래스, 그리고 고유한 MAC 어드레스와 같은 약간의 정보를 공유하는 것에 의해 검색 요청에 응답

 

  • 커넥션이 첫 번째로 원격 디바이스에 만들어진다.
  • 연결 요청 사용자에게 표시된다.
  • 디바이스에 대한 기본정보(디바이스 이름, 클래스, 그리고 MAC 어드레스와 같은)가 저장

원격 디바이스의 알려진 MAC 어드레스를 사용 커넥션 가능.

  • 패어드paired 와 커넥션 차이

    • 패어드paired : 두 개의 디바이스가 각기 인식하고, 인증을 위해 사용되는 공유된 링크 키를 가지고, 암호화된 커넥션을 확립
    • 커넥션: 디바이스들이 RFCOMM 채널을 현재 공유, 데이터를 전송할 수 있는 상태
    • 안드로이드 블루투스 API는 RFCCOMM 커넥션이 확립될 수 있기 전에 디바이스가 패어드paired 될 것을 요구한다.

 

패어드(Paired) 디바이스 쿼리

  1. Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
    // 페어드 디바이스가 있으면
    if (pairedDevices.size() > 0) {
        for (BluetoothDevice device : pairedDevices) {
  2.         // 작업 처리
            mArrayAdapter.add(device.getName() + “\n” + device.getAddress());
        }
    }

 

 

디바이스 검색(Discovering)

  • startDiscovery() : 비동기, 대략 12초 검색 스캔inquiry scan, 각각의 디바이스에 대한 page scan
  • 각각의 디바이스에 대한 정보를 수신하기 위해 ACTION_FOUND 인텐트에 대한 BroadcastReceiver를 등록

    • 엑스트라 필드 EXTRA_DEVICE와 EXTRA_CLASS에 각각에 대응되는 BluetoothDevice와 BluetoochClass를 포함해
      브로드캐스트 방송을 한다.
주의
  • discovery을 수행하는 것은 블루 투스 어댑터에게는 무거운 프로시저이며, 그것의 많은 리소스들을 소모
  • 커넥션을 시도하기 전에 cancelDiscovery()를 사용해서 검색discovery을 항상 중지시키는 것을 확인

    • 동시에 커넥션, discover는 대역폭 손실.
  • discover후 onDestroy시 unregister를 꼭 해야 한다.

 

  1. // Create a BroadcastReceiver for ACTION_FOUND
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // When discovery finds a device
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                 // Get the BluetoothDevice object from the Intent
                 BluetoothDevice device = intent.getParcelableExtra( BluetoothDevice.EXTRA_DEVICE);
                 // discover된 단말의 mac address 를 저장한다.
                 mArrayAdapter.add(device.getName() + “\n”  + device.getAddress());
            }
        }
    };
    // Register the BroadcastReceiver
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    registerReceiver(mReceiver, filter);
  2. // onDestroy시 unregister를 꼭 해야 한다.

 

 

검색 가능(discoverability) 활성화

검색가능discoverability을 활성화하는 것은 오직 여러분의 애플리케이션에 들어오는 커넥션을 수락할 서버 소켓을 보유하길 원할 때 뿐이다.

  1.  Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    discoverableIntent.putExtra( BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
    startActivity(discoverableIntent);

 

  • 장치는 120초동안 검색가능, EXTRA_DISCOVERABLE_DU-RATION  최대 300 초까지 줄 수 있다.
  • 디바이스 검색가능discoverability을 활성화하는 것은 자동으로 블루투스를 활성화

 

  • BluetoothAdapter.ACTION_SCAN_MODE_CHANGED : 그 검색가능한 모드가 변경되었을 때 통보받고자 한다

    • EXTRA_SCAN_MODE, EXTRA_PREVIOUS_SCAN_MODE : 신규 및 이전 스캔 모드를 알려준다
    • 가능한 값

      • SCAN_MODE_CONNECTABLE_DISCOVERABLE :장치 둘 다 discoverable 모드(inquiryscan과 page scan이 활성화 된 상태)
      • SCAN_MODE_CONNECTABLE : discoverable는 아니지만 여전히 커넥션을 수신할 수 있다는 것
      • SCAN_MODE_NONE : 검색가능 모드도 아니고 커넥션을 수신할 수도 없다
  •  

 

디바이스 연결

서버쪽과 클라이언트쪽 메커니즘 ( 소켓과 서버소켓의 연결). 서버 디바이스의 MAC 어드레스를 사용해서) 커넥션

서버와 클라이언트는 동일한 RFCOMM 채널에서 그것들 각각이 연결된 BluetoothSocket 로 데이터 송수신 시작.

서버는 BluetoothSocket을 커넥션이 들어 올 때, 클라이언트는 서버에 RFCOMM을 오픈할 때.

장치가 이전에 페어링된 적이 없으면,  안드로이드 프레임워크는 자동으로 패어링pairing 요청 통보를 한다.

 

 

서버로써의 연결 관리

BluetoothServerSocket  서버로써 역할해야 한다. 들어오는 커넥션 요청을 listen해서 연결된 BluetoothSocket을 제공

 

UUID(Universally Unique Identifier)는 고유하게 정보를 식별하기 위해 사용되는 문자열 ID로 표준화된 128비트 포맷이다.

클라이언트가 이 디바이스에 연결을 시도할 때, 그것은 클라이언트가 연결하길 원하는 서비스를 고유하게 식별하는 UUID를 건낼 것이다. 이러한 UUID는 (다음단계에서) 수락되는 커넥션에 일치해야 한다.

 

서버 연결 절차

1) listenUsingRfcommWithServiceRecord(String, UUID)를 호출해서 BluetoothServerSocket을 얻어라.

  •   String은 여러분의 서비스에 대한 식별가능한 이름, 신규 SDP(Service Discovery Protocol) 데이터베이스 엔트리에 저장

2) accept()를 호출해서 커넥션 요청 청취listening를 시작.

  • 블락킹blocking 호출(커넥션이 수락,예외가 발생).  UUID를 커넥션 요청과 함께 보냈을 때에만 수락된다.

    • accept() 호출은 메인 액티비티 UI 쓰레드에서 실행되어서는 안된다
  • 성공했을 때, accept()는 연결된 BluetoothSocket을 리턴할 것이다.

3) close()

  • BluetoothServerSocket, 리소스를 릴리즈한다. 
  • BluetoothSocket 유지. TCP/IP와 다르게, RFCOMM은 채널당 오직 한번에 하나의 연결된 클라이언트만을 허용한다. 그러므로 대부분의 경우에 그것은 연결된 소켓을 수락한 후 바로BluetoothServerSocket에 close()를 호출하는 것이 타당하다.
  •  

 

  1. private class AcceptThread extends Thread {
        private final BluetoothServerSocket mmServerSocket;
        public AcceptThread() {
            // Use a temporary object that is later assigned
               // to mmServerSocket,
            // because mmServerSocket is final
            BluetoothServerSocket tmp = null;
            try {
                // MY_UUID is the app’s UUID string, also used by the client code
                tmp = mAdapter.listenUsingRfcommWithServiceRecord( NAME, MY_UUID);
            } catch (IOException e) { }
            mmServerSocket = tmp;
        }
        public void run() {
            BluetoothSocket socket = null;
            // Keep listening until exception occurs or a socket is returned
          while (true) {
              try {
                  socket = mmServerSocket.accept();
              } catch (IOException e) {
                  break;
              }
              // If a connection was accepted
              if (socket != null) {
                  // Do work to manage the connection
                        // (in a separate thread)
                  manageConnectedSocket(socket);
                  mmServerSocket.close();
                  break;

              }
          }
      }
      /** Will cancel the listening socket,
              and cause the thread to finish */
      public void cancel() {
          try {
              mmServerSocket.close();
          } catch (IOException e) { }
      }
    }
  • 하나의 BluetoothSocket 획득하고 별도의 쓰레드에 BluetoothSocket을 전달하고 BluetoothServerSocket을 닫고 대기한다.

 

 

클라이언트로써의 연결관리

 

연결 절차

1)     BluetoothDevice.createRfcommSocketToServiceRecord(UUID)

  • BluetoothSocket 획득
  • 서버의 UUID 와 일치

 

2) connect() :커넥션 만들기

  • 서버는 UUID를 일치하기 위해 원격 디바이스에 SDP 룩업을 수행
  • 커넥션 동안 사용할 RFCOMM 채널을 공유할 것이며
  • 블락킹blocking 호출(대략 12초 이후) 타임아웃

    • 메인 activity 쓰레드와 분리된 쓰레드에서 수행
  • 디바이스가 디바이스 검색discovery을 수행되면, 커넥션 시도는 현저하게 느려지게 될 것이며, 아마도 실패할 것이다.
  1. private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
        public ConnectThread(BluetoothDevice device) {
            // Use a temporary object that is later assigned to mmSocket,because mmSocket is final
            BluetoothSocket tmp = null;
            mmDevice = device;
            // Get a BluetoothSocket to connect with the given BluetoothDevice
            try {
                // MY_UUID is the app’s UUID string, also used by the server code
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) { }
            mmSocket = tmp;
        }
        public void run() {
            // Cancel discovery because it will slow down the connection
  2.         // isDiscovering()  를 이용할 수도 있겠다.
            mAdapter.cancelDiscovery();
            try {
                // Connect the device through the socket. This will block
                // until it succeeds or throws an exception
                mmSocket.connect();
            } catch (IOException connectException) {
              // Unable to connect; close the socket and get out
              try {
                  mmSocket.close();
              } catch (IOException closeException) { }
              return;
          }
          // Do work to manage the connection (in a separate thread)
          manageConnectedSocket(mmSocket);

      }
      /** Will cancel an in-progress connection, and close the socket */
      public void cancel() {
          try {
              mmSocket.close();
          } catch (IOException e) { }
      }
    }

 

 

커넥션 관리

BluetoothSocket을 사용해서, 임의의 데이터를 전송하는 일반적인 절차

 

1)  getInputStream(), getOutputStream() 에서 스트림 획득.

2) read(byte[])와 write(byte[])  스트림에 데이터를 읽고 써라.

  • 블락킹blocking 호출, 스트림을 읽고 쓰는 전용 쓰레드를 사용해야 한다.
  • read(byte[])는 스트림에서 읽혀지는 어떤 것이 있을 때까지 블락 block될 것이다.
  • write(byte[])는 통상 블락block되지 않을 것이다. 하지만 만약 원격 디바이스가 충분히 빠르
    게 read(byte[])를 호출하지 않아서 중간 버퍼가 풀full이라면 흐름제어를 위해 블락 block될 수 있다.

 

  1. private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
        public ConnectedThread(BluetoothSocket socket) {
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
            // Get the input and output streams, using temp objects because
            // member streams are final
          try {
              tmpIn = socket.getInputStream();
              tmpOut = socket.getOutputStream();

          } catch (IOException e) { }
          mmInStream = tmpIn;
          mmOutStream = tmpOut;

      }
      public void run() {
          byte[] buffer = new byte[1024]; // buffer store for the stream
          int bytes; // bytes returned from read()
          // Keep listening to the InputStream until an exception occurs
          while (true) {
              try {
                  // Read from the InputStream
                  bytes = mmInStream.read(buffer);
                  // Send the obtained bytes to the UI Activity
                  mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
              } catch (IOException e) {
                  break;
              }
          }
      }
      /* Call this from the main Activity to send data to the remote device */
      public void write(byte[] bytes) {
          try {
              mmOutStream.write(bytes);
          } catch (IOException e) { }
      }
      /* Call this from the main Activity to shutdown the connection */
      public void cancel() {
          try {
              mmSocket.close();
          } catch (IOException e) { }
      }
    }

 

 

SDK 1.0 기준

BluetoothDevice 얻기

  1. final BluetoothDevice bluetooth = (BluetoothDevice)getSystemService(Context.BLUETOOTH_SERVICE);
  • BluetoothDevice.enable() : 블루투스 어댑터 활성화
  • BluetoothDevice.disable() : 블루투스 어댑터 활성화
  • BluetoothDevice.getName(): 장치 이름, setName() 이름 지정
  • BluetoothDevice.getAddress() : 장치 주소
  • BluetoothDevice.getMode() BluetoothDevice.getDiscoverableTimeout() discover mode와 time out 시간 알아 낸다.
블루투스 어댑터를 활성화 하고, 장치가 연결될 때 까지 기다린 다음, 장치 이름을 바꾸고 모드를 discoverable로 변경
  1.    final BluetoothDevice bluetooth = (BluetoothDevice)getSystemService(Context.BLUETOOTH_SERVICE);
        bluetooth.enable(new IBluetoothDeviceCallback.Stub() {
          @Override
          public void onCreateBondingResult(String _address, int _result) throws RemoteException  {
            String friendlyName = bluetooth.getRemoteName(_address);
  2.       }
          @Override
          public void onEnableResult(int _result) throws RemoteException {
            if (_result == BluetoothDevice.RESULT_SUCCESS) {
              bluetooth.setName("BLACKFANG");
              bluetooth.setMode(BluetoothDevice.MODE_DISCOVERABLE);
            }
          }
        });
블루투스 장치 발견

장치를 찾는 것을 discovery 라고 한다.

  • 장치는 setMode(BluetoothDevice.MODE_DISCOVERABLE) 로 설정해야 찾을 수 있다.


출처 : http://gtko.springnote.com/pages/5396297