그 밖에 참고하면 좋은 자료
NFC란?
NFC는 핸드폰 안에 들어가는 RFID 기술의 총체입니다. RFID는 무선 주파수를 이용해 통신을 하는
기술인데, 쉽게는 교통카드를 생각하면 됩니다. 버스에 있는 교통카드를 읽는 장치(리더기라고
합시다)에 티머니 등의 교통카드를 갖다 대면, 리더기가 교통카드를 인식하고 삑 소리와 함께
요금이 결제됩니다.
(사진 출처: 서울 특별시 교통카드 사용 방법 Q&A)
간단해 보이는 이 동작의 순간에 여러 가지 일들이 발생합니다. 일단 리더기에서 나온 전자기장이 교통카드에 전원을 공급합니다. (교통카드 자체에는 별도의 배터리가 없습니다.) 교통카드는 순간적으로 부팅을 하고, 리더기와 통신을 합니다. 이 과정에서 교통카드의 유효성 여부도 체크할 것이고, 잔액을 차감하는 일도 할 것입니다.
NFC는 여기서 일단 '리더기'의 역할을 할 수 있습니다. NFC를 지원하는 안드로이드 폰에 티머니 등의 교통카드를 갖다 대면, 뭔가 인식이 되는 듯한 동작이 보일 것입니다. 안드로이드 단말에 들어가는 NFC 칩이 교통카드를 인식한 것인데, 교통카드 외에도 몇 가지 종류의 RFID 태그를 읽을 수 있습니다. ('태그'는 교통카드처럼 내부에 데이터를 저장할 수도 있고, 간단한 연산도 가능한 장치라고 일단 정리해두겠습니다.) 그런데 '읽는다'고 하는 것은, 사실은 태그에 '읽기' 명령을 내리는 것입니다. 그렇다면 같은 방식으로 '쓰기' 명령을 내릴 수도 있을 것입니다. 그래서 '리더기'라 함은 태그에 데이터를 읽거나 쓸 수 있는 장치를 말합니다.
NFC는 또한 '교통카드'의 역할을 할 수도 있습니다. USIM 칩이 이 역할을 하기도 하고, Secure Element라고 하는 별도의 칩이 존재하기도 합니다.
이 USIM 칩 혹은 Secure Element 칩은 일종의 스마트 카드(SmartCard)인데, 내부에 애플릿이라고 하는 작은 프로그램이 설치될 수 있는,
독립적인 컴퓨터 같은 존재입니다.
여기에 티머니 교통카드 애플릿이 설치되면 티머니 카드가 되고, 신한카드 애플릿이 설치되면 신한카드가
되는 것입니다. NFC의 또 한가지 기능은 P2P인데, NFC 단말 간에 주소록이나 사진 등을 전송하는 기능입니다.
실제로는 한 단말이 태그 역할을 하고, 다른 단말이 리더기 역할을 하는 식으로 동작합니다.
그래서 양방향으로 자유롭게 통신 되는 것은 아니고, 한번에 한 방향으로만 통신됩니다.
(사진 출처: http://nexus-s.tistory.com/?page=67)
NFC 특징
- 비접촉 무선 통신 기술 : 리더기와 교통카드를 물리적으로 연결하지 않고 근처에 갖다 대는 것으로만 통신 가능하므로,
비접촉(Contactless) 무선(Wireless) 통신이라고 합니다. - 13.56 MHz 대역을 사용하고, 몇 cm 정도의 아주 가까운 거리에서 동작
- RFID 태그와 통신. 혹은 두 개의 NFC 단말 사이의 통신.
- 태깅(Tagging) 혹은 터칭(Touching) : 태그를 단말에 갖다 대는 행위를 말합니다.
NFC의 세 가지 모드
위에서 얘기했듯이, NFC 기능은 아래와 같이 크게 세 가지로 구분합니다.
- 카드 모드 (Card Mode) : NFC 단말이 스마트 카드처럼 동작합니다.
예를들어, 안드로이드 폰을 교통카드로 사용하는 경우입니다. - 태그 읽기/쓰기 모드 (R/W Mode) : NFC 단말이 NFC 태그로부터 데이터를 읽거나, NFC 태그에 데이터를 씁니다.
예를 들어 태그 읽어서 특정 웹 사이트로 연결하는 경우입니다. - P2P 모드 (P2P Mode) : NFC 단말이 다른 NFC 단말로부터 데이터를 읽어옵니다.
예를 들어, NFC 단말 사이에 연락처를 전달하는 경우입니다.
안드로이드 NFC
NFC가 대중에게 알려지기 시작한 것은 안드로이드 폰에 NFC 기능이 추가되면서부터인 것 같습니다.
정확하게는 안드로이드 버전 2.3 (진저브레드) 부터 NFC 기능이 추가됐습니다. 그 후로 몇 번의 버전 업에 따라 NFC API에도 변화가 있었습니다.
안드로이드 버전에 따라 NFC API기 지원하는 기능을 정리해보면, 아래와 같습니다.
버전 | 지원 기능 |
2.3 | - 단말의 NFC 기능 지원 여부 판단 |
2.3.3 | - RFID 태그 종류별 처리 |
4.0 | - Android Beam 지원 |
RFID 태그 인식이나 NDEF 처리, 태그 전달 시스템 등은 모두 '태그 읽기/쓰기 모드'에 해당합니다.
P2P 나 Android Beam은 'P2P 모드'에 해당합니다. '카드 모드'에 해당하는 기능에 대한 지원은 NFC API에는 없습니다.
(일반 애플리케이션 개발자가 사용할 수 있는 API가 없다는 의미이고, 단말에 기능이 없다는 뜻은 아닙니다.)
NFC 애플리케이션 개발을 위해 알아야 하는 것들
안드로이드에서 NFC 애플리케이션을 개발하려면 물론 NFC API를 알아야 합니다.
하지만 NFC 자체가 대부분의 개발자에게 매우 생소한 분야이므로, 그냥 API만 살펴본다고 해서 곧바로 사용하기는 힘듭니다.
일단 NDEF(NFC Data Exchange Format)라고 하는 것에 대해 알아야 하는데, NFC에서 데이터를 교환할 때 사용하는 데이터 포맷을 말합니다.
NFC도 결국 통신 기술(리더기와 태그 사이의 통신, 혹은 단말과 단말 사이의 통신)이기 때문에 주고 받는 데이터에 대한 규약이 필요하고,
이것이 NDEF입니다.
애플리케이션이 태그를 사용한다면, 태그에 대해서도 알아야 합니다.
NFC 표준에는 네 가지 타입의 태그가 정의되어 있고, 이외에 Mifare Classic 같은 확장된 표준도 존재합니다.
필요하다면 ISO 15693 태그에 대해서도 알아야 합니다.
그래서 종합해보면 태그 종류가 몇 가지 정도 되고, 각 태그별로 특성이 다르기 때문에 처음에는 이 부분이 좀 헷갈립니다.
여기에서는 이와 관련된 내용들을 앞으로 계속 포스팅 할 예정입니다.
하지만 일단, 무엇보다 먼저, Hello NFC 애플리케이션을 만들어보도록 하겠습니다.
Hello NFC
이 포스트에서는 간단한 NFC 애플리케이션을 만들어보도록 하겠습니다.
이 애플리케이션은 간단하게 NFC 태그를 읽어, 태그 ID를 화면에 출력하는 애플리케이션입니다.
결과 화면은 다음과 같습니다.
준비물
- NFC를 지원하는 안드로이드 단말. 안드로이드 2.3.3 이상.
- 티머니 혹은 캐시비 교통카드. 혹은 최근 몇 년 이내에 발급받은 신용카드. 혹은 NFC 태그가 있다면 더욱 좋음.
- 안드로이드 애플리케이션 개발 환경. (JDK, Eclipse, Android SDK...)
안드로이드 애플리케이션 프로젝트 생성
이클립스에서 일반적인 안드로이드 애플리케이션 프로젝트 만들듯이 생성합니다.
SDK 버전을 2.3.3 이상으로 하는 것만 주의하면 됩니다.
AndroidManifest.xml 에 NFC 기능/권한 추가
애플리케이션이 NFC 기능을 사용하기 위해서, AndroidManifest.xml 에 다음과 같이 <uses-feature> 와 <uses-permission> 항목을 추가합니다.
<uses-feature android:name="android.hardware.nfc" required="false" />
<uses-permission android:name="android.permission.NFC" />
<uses-feature>는 이 애플리케이션이 NFC 기능을 사용한다는 것을 의미합니다.
android:required 속성을 true로 하면 Google Play에서 NFC 기능을 가진 단말에서만 애플리케이션이 보이도록 할 수도 있습니다.
<uses-permission>은 애플리케이션이 NFC 접근 권한을 필요로 한다는 의미이고, 애플리케이션 설치 시 사용자에 의해 권한 확인을 받게 됩니다.
NfcAdapter
NFC 단말에 태그가 인식되면, Intent를 통해서 Activity로 전달됩니다.
Activity가 이 Intent를 받기 위해서는 NfcAdapter 클래스의 enableForegroundDispatch(..) 를 이용합니다.
public void enableForegroundDispatch(Activity activity, PendingIntent pendingIntent, IntentFilter[] filters, String[][] techLists)
activity는 Intent를 전달 받을 Activity이고, pendingIntent는 전달할 때 사용할 intent를 갖고 있는 PendingIntent 객체입니다.
PendingIntent는 나중에 전달할 intent를 갖고 있는 녀석 정도로 생각하면 됩니다.
말하자면 'NFC 태그가 인식되면, 이 PendingIntent 안에 있는 intent를 전달해 달라'는 의미입니다.
filters와 techLists를 이용해 인식할 태그의 종류를 지정할 수 있는데, 모두 null로 하면 모든 태그를 인식하게 됩니다.
이 메소드를 호출하기 전에 NfcAdapter 객체와 PendingIntent 객체가 필요하므로, onCreate(..) 에서 이 두 객체를 아래와 같이 생성합니다.
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
Intent intent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
}
NfcAdapter.getDefaultAdapter(Context)는 디폴트 NfcAdapter 객체를 가져올 때 사용하는데, NFC를 지원하지 않는 단말에서는 null을 리턴합니다.
Intent 객체를 생성할 때 Intent.FLAG_ACTIVITY_SINGLE_TOP 플래그를 준 것은, 태그를 계속 인식할 때 새로운 Activity를 만들지 않고
현재 Activity에서 Intent를 받기 위해서입니다.
Activity가 화면에 보이고 있을 때에만 NFC 태그를 인식하기 위해서, onResume(..)에서 enableForegroundDispatch(..)를 호출하고,
onPause(..)에서 disableForegroundDispatch(..)를 호출합니다.
@Override
protected void onResume() {
super.onResume();
if (nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
}
@Override
protected void onPause() {
if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(this);
}
super.onPause();
}
이제 태그가 인식되면 onNewIntent(..)로 Intent가 전달될 것입니다.
onNewIntent(..)에서는 이 Intent가 태그 인식으로 인한 Intent인지를 판단하고, 태그 ID를 화면에 출력합니다.
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) {
byte[] tagId = tag.getId();
tagDesc.setText("TagID: " + toHexString(tagId));
}
}
NFC 태그 인식으로 발생한 intent에는 NfcAdapter.EXTRA_TAG 이름으로 Tag 객체가 전달됩니다.
이것을 확인하면 NFC 태그 인식으로 인한 intent인지 확인할 수 있습니다.
Tag 의 getId() 메소드는 byte[] 형식의 태그 ID를 리턴합니다.
toHexString(..) 메소드는 byte[]를 String으로 변환하는 역할을 하는데, 소스는 전체 소스코드에서 확인할 수 있습니다.
MainActivity.java 전체 소스 코드
package com.startnfc.blog.hellonfc; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends Activity { private NfcAdapter nfcAdapter; private PendingIntent pendingIntent;
private TextView tagDesc;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tagDesc = (TextView)findViewById(R.id.tagDesc); nfcAdapter = NfcAdapter.getDefaultAdapter(this); Intent intent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); } @Override protected void onPause() { if (nfcAdapter != null) { nfcAdapter.disableForegroundDispatch(this); } super.onPause(); } @Override protected void onResume() { super.onResume(); if (nfcAdapter != null) { nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null); } } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent);
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); if (tag != null) { byte[] tagId = tag.getId(); tagDesc.setText("TagID: " + toHexString(tagId)); } }
public static final String CHARS = "0123456789ABCDEF";
public static String toHexString(byte[] data) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < data.length; ++i) { sb.append(CHARS.charAt((data[i] >> 4) & 0x0F)) .append(CHARS.charAt(data[i] & 0x0F)); } return sb.toString(); } }
|
NDEF
NFC 애플리케이션 개발을 위해서는 NDEF에 대한 이해가 필수적입니다.
여기에서는 먼저 NDEF에 대한 기본적인 내용을 소개하고, 이후 포스트에서 각 타입별 NDEF 메시지 형식에 대한 소개를 하겠습니다.
NDEF
NDEF (NFC Data Exchange Format)는 NFC에서 데이터를 교환하기 위한 포맷입니다.
NFC 포럼에서 정의 했습니다. NFC 디바이스가 NFC 태그로부터 데이터를 가져올 때, 그리고 NFC 디바이스 사이에 P2P로 데이터를 전송할 때 이 포맷을 사용합니다.
(데이터 '저장' 포맷이 아니라 '교환' 포맷인 이유는, 태그나 디바이스에 데이터를 저장할 때는 꼭 NDEF 형식으로 저장할 필요가 없기 때문이지 않을까 짐작해봅니다.
실제로는, 태그에 데이터를 저장할 때는 NDEF 형식의 데이터를 그대로 저장합니다.
하지만 P2P의 경우 즉, 안드로이드 폰에서 주소 정보를 다른 안드로이드 폰으로 전송하는 경우, 주소 정보를 NDEF 형식으로 저장하지는 않을 것입니다.
저장은 별도의 작업이고, 전송할 때 NDEF 형식으로 변경해서 전송할 것입니다. 별로 중요한 얘기는 아니었습니다.)
NDEF 메시지
기본적인 메시지 단위 하나를 'NDEF 메시지 (NDEF Message)'라고 부릅니다.
하나의 NDEF 메시지는 여러 개의 'NDEF 레코드 (NDEF Record)'로 구성되어 있습니다.
NDEF 레코드
하나의 NDEF 레코드는 하나의 데이터를 담고 있는데, 이 데이터를 '페이로드'라고 합니다.
결국 이 페이로드를 전달하기 위해서 NDEF 레코드가 필요한 것입니다.
그런데 이 데이터는 그냥 byte 덩어리이기 때문에, 이것만 보고는 이 데이터가 그냥 텍스트인지, URL인지, 이미지인지, 동영상인지 알 수가 없습니다.
그래서 '페이로드 타입'이 필요합니다.
인터넷을 예로 들어보겠습니다. 인터넷에 있는 수많은 콘텐츠들은 모두 MIME 타입이라고 하는 타입을 갖고 있습니다.
PNG 이미지라면 'image/png', mpeg 동영상이라면 'video/mpeg', 그냥 텍스트라면 'text/plain'... 이와 마찬가지로 페이로드도 타입을 갖고 있습니다.
하나의 메시지에 여러 개의 레코드가 존재하므로, 어떤 레코드가 다른 레코드를 참조하는 경우가 생길 수 있습니다.
이 경우 레코드에 대한 식별자가 필요할 것입니다. 이것을 페이로드 'ID'라고 합니다.
대부분의 경우에는 이 ID가 필요하지 않은데, 그래서 ID는 생략될 수 있습니다.
이렇게 '페이로드', '타입', 'ID' 세 가지가 NDEF 레코드의 주요 구성 요소가 됩니다.
실제로는 각 구성요소의 길이와 '레코드 헤더'가 추가되기 때문에, NDEF 레코드는 아래와 같이 구성됩니다.
항목 | 길이 | 설명 |
레코드 헤더 (header) | 1byte | 레코드에 대한 기본적인 정보 |
타입 길이 (type length) | 1byte | 데이터 타입의 길이 |
페이로드 길이 (payload length) | 1byte 혹은 4byte | 페이로드의 길이 |
ID 길이 (id length) | 1byte | ID의 길이 |
타입 (type) | '타입 길이' byte | 레코드가 담고 있는 페이로드의 타입 |
ID | 'ID 길이' byte | 페이로드 ID |
페이로드 (payload) | '페이로드 길이' byte | 레코드가 담고 있는 페이로드 |
[표: NDEF 레코드의 구성]
1byte의 '레코드 헤더'에는 아래와 같은 정보가 포함되어 있습니다.
헤더 항목 | 길이 | 설명 |
MB (Message Begin) | 1bit | NDEF 메시지의 첫 레코드는 이 비트가 1 입니다. |
ME (Message End) | 1bit | NDEF 메시지의 마지막 레코드는 이 비트가 1입니다. |
CF (Chunk Flag) | 1bit | 하나의 페이로드를 여러 개의 레코드로 나누어 전송하는 경우가 있는데, 이때 사용합니다. |
SR (Short Record) | 1bit | 이 비트가 1이면 '페이로드 길이'의 크기는 1byte이고, 0이면 4byte입니다. |
IL (ID Length) | 1bit | 레코드 ID가 존재하는 경우 이 비트가 1입니다. |
TNF (Type Name Format) | 3bit | (별도로 설명) |
[표: NDEF 레코드 헤더 정보]
MB ME는 별로 어려울 것이 없습니다.
첫 번째 레코드는 MB가 1이고, 마지막 레코드는 ME가 1입니다.
만약 NDEF 메시지 안에 하나의 레코드만 있다면, MB ME 모두 1로 세팅됩니다.
CF는 사용하는 경우가 별로 없습니다. 전송하고자 하는 데이터가 매우 크다면 여러 조각으로 나누어 전송할 수가 있는데
(chunked record), 이렇게 하면 모든 데이터가 전송되기 전에 액션을 실행할 수 있다는 장점이 있다고 합니다.
하지만 안드로이드에서는 NDEF 메시지를 모두 전송 받은 다음에 Intent가 발생하므로, 별로 의미가 없습니다.
위의 [NDEF 레코드의 구성] 표에서 '페이로드 길이' 항목이 1byte 혹은 4byte라고 했는데,
그렇다면 이에 대한 구분자가 필요할 것입니다. 이것이 SR입니다.
SR이 1로 설정되어 있으면 '페이로드 길이' 항목의 크기는 1byte이고, SR이 0이면 4byte입니다.
그렇다면 실제 '페이로드'의 길이는 어디까지 가능할까요? SR이 설정되어 있으면 (2^8 - 1) = 255 byte가 될 것이고,
SR이 설정되어 있지 않으면 (2^32 - 1) = 약 4GB 가 될 것입니다.
그래서 약 4GB가 페이로드의 이론적인 최대 길이가 됩니다. (제법 크죠?)
위에서 '페이로드'와 '타입'은 반드시 존재해야 하지만, 'ID'는 존재하지 않을 수도 있다고 했습니다.
이에 대한 구분자도 필요할텐데, 이것이 IL입니다. ID가 존재하는 경우 IL이 1로 설정됩니다.
1byte의 헤더에 참 많은 내용을 꾸역꾸역 넣었다는 생각이 들 것입니다.
이렇게 한 이유는 레코드를 '가볍게' 만들어야 했기 때문입니다. NFC 태그의 용량이 생각보다 작기도 하고 (수십 byte ~ 수 killo byte),
NFC의 전송 속도가 그리 빠르지 않기 때문에 (106 kbit/s ~ 424 kbit/s) 한 byte라도 크기를 줄여야 했던 것입니다.
TNF 와 Type
TNF에 대한 설명을 지금까지 미루어 왔습니다.
TNF는 개인적으로 NDEF를 공부하면서 가장 헷갈렸던 부분입니다.
페이로드 타입이 이미 존재하는데, 왜 TNF라는 것이 별도로 필요할까?
위에서 MIME 타입에 대해 잠깐 얘기를 했었습니다.
인터넷에 존재하는 수많은 콘텐츠들은 대부분 MIME 타입으로 구분이 가능합니다.
따라서 NDEF에서도 MIME 타입을 그대로 사용했어도, 웬만한 데이터의 타입을 명시할 수 있었을 것입니다.
그런데 NDEF 표준을 만든 사람들은 이것으로 만족하지 못했던 모양입니다.
MIME 타입 이외의 다른 타입들도 포함하고자 했습니다.
그렇다면 '타입' 외에 이 타입이 MIME 타입 형식으로 정의된 타입인지, 아니면 다른 형식의 타입인지를 구분할 필요가 있을 것입니다.
이것이 TNF 입니다. Type Name Format, 즉 '타입이 어떤 형식으로 되어있는가'를 나타냅니다.
TNF는 3bit의 크기를 가지므로, 8 가지의 TNF를 정의할 수 있습니다.
하지만 실제로는 4가지가 의미있는 TNF이고, 나머지는 특수한 용도로 사용합니다.
TNF (Type Name Format) | 값 | 설명 |
Empty | 0x00 | 비어있는 레코드. 즉, 페이로드가 없음. |
WKT (NFC Forum well-known type) | 0x01 | NFC 포럼에서 정의한 타입 형식. (예: URI, Text, Smart Poster) |
MIME (MIME Media type) | 0x02 | MIME 타입 형식. (예: plain/text, image/jpeg) |
AURI (Absolute URI type) | 0x03 | 예를 들어 XML의 경우 URI 형식의 DTD나 XML Schema를 타입으로 사용함. (예: http://www.w3.org/TR/html4/strict.dtd, http://www.w3.org/2000/svg) |
EXT (NFC Forum external type) | 0x04 | NFC 포럼에서 정의한 규칙대로, 임의의 타입 형식을 만들어 사용할 수 있음. (예: startnfc.com:U) |
Unknown | 0x05 | 알 수 없는 형식의 페이로드. 그냥 byte 덩어리로 취급됨. |
Unchanged | 0x06 | 데이터를 여러 조각으로 나누어 전송하는 경우 (chunked record) 이전 레코드의 타입과 같은 타입이라는 것을 나타내기 위해 사용. |
Reserved | 0x07 | 사용하지 않음. |
NDEF 메시지의 예
지금까지 설명했던 내용으로, NDEF 메시지를 하나 만들어보겠습니다.
'http://blog.startnfc.com' 이라고 하는 URL을 NDEF 메시지로 만들면 아래와 같이 됩니다. (모두 16진수입니다.)
어떻게 이렇게 만들어지는지에 대한 상세한 설명은 다음 포스트에서 할 것이므로, 일단은 이런 형태라는 것만 알아두면 됩니다.
D1 01 12 55 03 62 6C 6F 67 2E 73 74 61 72 74 6E 66 63 2E 63 6F 6D
항목 | 데이터 | 설명 |
레코드 헤더 | 0xD1 | 이진수로 하면 11010001 MB=1 ME=1 : 첫 레코드인 동시에 마지막 레코드임 CF=0 : Chunked record가 아님 SR=1 : 따라서 페이로드 길이의 크기가 1byte임 IL=0 : 따라서 ID는 존재하지 않음 TNF=1 : TNF는 Well-Known type |
타입 길이 | 0x01 | 타입의 길이가 1byte임 |
페이로드 길이 | 0x12 | 페이로드의 길이가 18byte임 |
타입 | 0x55 ('U') | Well-Known type에서 'U'는 URI를 나타냄 |
페이로드 | 0x03 0x62 ... 0x6D | 실제 데이터. 첫번째 바이트 0x03은 URI 페이로드의 헤더이고, 이후 0x62부터 "blog.startnfc.com"의 아스키코드. (URI를 표현하는 방식에 대한 것은 이후 포스팅에서 자세히 다룰 것임) |
NDEF 포맷의 특징
지금까지 설명한 내용을 바탕으로 NDEF 포맷의 특징을 정리해보면 이렇습니다.
- 가볍다. 즉 크기가 작다. 헤더가 1byte 밖에 되지 않고, 크기가 작은 페이로드의 경우 페이로드 길이를 1byte로 표현할 수 있다.
또한 Well-Known type의 경우 타입이 'U', 'T', 'Sp' 처럼 1~2byte밖에 되지 않는다. 위에서 예로 든 'http://blog.startnfc.com' URL을 그대로 쓰면 24byte인데,
NDEF 메시지로 만들면 22byte로 오히려 크기가 줄어든다. - 다양한 타입의 데이터를 담을 수 있다. 일반적인 MIME 타입 뿐만 아니라 Absolute URI를 이용해 좀 더 세밀하게 타입을 지정할 수 있다.
적합한 타입이 없으면 External 타입을 이용해 별도의 타입을 정의해 사용할 수도 있다. 물론 이렇게 되면 다른 애플리케이션과의 호환성에 제한이 생기지만 말이다.
다음 포스팅에서는 WKT 즉, NFC Forum Well-Known type 에서 정의되어 있는 몇 가지 타입에 대해 설명하겠습니다.
NDEF : Well-known Type URI
이 포스트에서는 URI를 NDEF 메시지로 만드는 방법에 대해 설명합니다.
먼저 URI에 대해서 좀 더 자세히 알아보겠습니다.
URI, URL, URN
URI (Uniform Resource Indicator)는 URL(Uniform Resource Locator)과 URN(Uniform Resource Name)을 모두 포함해서 이르는 말입니다.
URL은 다들 알고 있는 것 처럼, 인터넷 상의 특정 리소스를 가리키는 주소입니다. 몇 개를 예로 들면 이렇습니다.
- http://blog.startnfc.com
- http://dl.google.com/android/installer_r20.0.3-windows.exe
- ftp://ftp.trolltech.com/
- mailto:admin@startnfc.com
- tel:01012345678
URL은 이렇게 'http', 'ftp' 등의 프로토콜과 서버 주소, 그리고 리소스 파일 위치 등으로 구성됩니다.
'mailto'나 'tel' 처럼, 경우에 따라 서버 주소가 필요없는 경우도 있습니다.
URN은 인터넷 상의 리소스에 대한 ID를 나타냅니다. URN은 'urn:'으로 시작하는데, 예를 들면 아래와 같습니다.
- urn:isbn:8964200391 : '자바 프로그래밍 언어' 책의 ISBN
- urn:ietf:rfc:2648 : RFC 2648
예를 들어 '자바 프로그래밍 언어' 책에 대한 URN은 하나 이지만, URL은 여러 개일 수 있습니다.
'예스24'에도 있고, '인터파크 도서'에도 있고. 이것이 URL과 URN의 차이입니다.
WKT U
예를 들어 http://blog.startnfc.com 을 NDEF 레코드로 만들어보겠습니다.
URI는 NFC Forum Well-known type (줄여서 WKT) TNF를 사용해서 나타낼 수 있습니다.
'NDEF' 포스트에서 설명했듯이, 페이로드 ID를 사용하지 않는다고 하면, NDEF 레코드의 형식은 아래와 같습니다.
(레코드 헤더) (타입 길이) (페이로드 길이) (타입) (페이로드)
WKT의 TNF는 1이고, 페이로드가 255byte보다는 작을 것이므로 SR=1 로 하면, 레코드 헤더는 아래와 같이 0xD1이 됩니다.
MB | ME | CF | SR | IL | TNF |
|
1 | 1 | 0 | 1 | 0 | 001 | = 0xD1 |
타입은 WKT 규격에서 "U" 로 정해져 있습니다. 타입은 대소문자를 구분하고, 여기서는 대문자 U 입니다.
이것의 아스키 코드 값은 0x55 입니다. 타입이 1byte이므로, 타입 길이는 1 입니다.
여기까지 보면,NDEF 레코드는 아래와 같이 됩니다. (모두 16진수입니다.)
D1 01 (페이로드 길이) 55 (페이로드)
WKT U 타입의 페이로드는 1바이트의 ID 코드로 시작합니다.
이 ID 코드는 URI의 앞부분을 코드화 시킨 것인데, 예를들어 'http://'는 코드 값이 0x03입니다.
그리고 뒤에 'http://'를 제외한 나머지, 즉 'blog.startnfc.com' 을 붙입니다.
그래서 페이로드는 아래와 같이 됩니다.
ID코드 | blog.startnfc.com |
0x03 | (16진수) 62 6C 6F 67 2E 73 74 61 72 74 6E 66 63 2E 63 6F 6D |
이렇게 ID코드를 만든 이유는 URI의 길이를 줄이기 위한 것입니다.
실제로 'http://blog.startnfc.com'을 그대로 쓰면 24바이트 이지만, 이렇게 NDEF 레코드 페이로드로 만들면 18바이트가 됩니다.
ID 코드의 값은 규격에서 아래와 같이 정의되어 있습니다.
10진수 | 16진수 | 설명 |
0 | 0x00 | ID코드를 사용하지 않는 경우. |
1 | 0x01 | http://www. |
2 | 0x02 | https://www. |
3 | 0x03 | http:// |
4 | 0x04 | https:// |
5 | 0x05 | tel: |
6 | 0x06 | mailto: |
7 | 0x07 | ftp://anonymous:anonymous@ |
8 | 0x08 | ftp://ftp. |
9 | 0x09 | ftps:// |
10 | 0x0A | sftp:// |
11 | 0x0B | smb:// |
12 | 0x0C | nfs:// |
13 | 0x0D | ftp:// |
14 | 0x0E | dav:// |
15 | 0x0F | news: |
16 | 0x10 | telnet:// |
17 | 0x11 | imap: |
18 | 0x12 | rtsp:// |
19 | 0x13 | urn: |
20 | 0x14 | pop: |
21 | 0x15 | sip: |
22 | 0x16 | sips: |
23 | 0x17 | tftp: |
24 | 0x18 | btspp:// |
25 | 0x19 | btl2cap:// |
26 | 0x1A | btgoep:// |
27 | 0x1B | tcpobex:// |
28 | 0x1C | irdaobex:// |
29 | 0x1D | file:// |
30 | 0x1E | urn:epc:id: |
31 | 0x1F | urn:epc:tag: |
32 | 0x20 | urn:epc:pat: |
33 | 0x21 | urn:epc:raw: |
34 | 0x22 | urn:epc: |
35 | 0x23 | urn:nfc: |
36 ~ 255 | 0x24 ~ 0xFF | RFU (나중을 위해 예약되었음) |
ID코드 0x00 값은 ID코드를 사용하지 않음을 의미합니다.
따라서 'http://blog.startnfc.com'의 페이로드는 아래와 같이 나타낼 수도 있습니다.
ID코드 | http://blog.startnfc.com |
0x00 | (16진수) 68 74 74 70 3A 2F 2F 62 6C 6F 67 2E 73 74 61 72 74 6E 66 63 2E 63 6F 6D |
하지만 페이로드 크기가 25바이트로 커지기만 할 뿐, 얻을 수 있는 장점이 없습니다.
이렇게 할 이유가 전혀 없습니다. 그래서 ID 코드를 0x03 으로 사용하는 것으로 하면, NDEF 레코드는 아래와 같이 됩니다.
D1 01 12 55 03 62 6C 6F 67 2E 73 74 61 72 74 6E 66 63 2E 63 6F 6D
몇 가지 예제
몇 가지 예를 더 들어보겠습니다.
ftp://ftp.trolltech.com/
D1 01 13 55 0D 66 74 70 2E 74 72 6F 6C 6C 74 65 63 68 2E 63 6F 6D 2F
mailto:admin@startnfc.com
D1 01 13 55 06 61 64 6D 69 6E 40 73 74 61 72 74 6E 66 63 2E 63 6F 6D
tel:01012345678
D1 01 0C 55 05 30 31 30 31 32 33 34 35 36 37 38
태그 디스패치 시스템
안드로이드 장비는 NFC가 환경설정 메뉴에서 비활성화되어져 있지 않다면 일반적으로 스크린이 잠금 해제되어져 있을 때 NFC 태그들을 찾을 것입니다.
안드로이드 장비가 NFC 태그를 찾았을 때 적절한 동작은 사용하는 앱이 무엇인지 사용자에게 물어보지 않고
대부분 인텐트를 처리할 수 있는 적절한 액티비티가 처리하는 것입니다.
왜냐하면 장비들은 매우 가까운 범위에서만 NFC 태그들을 스캔하므로 사용자가 수동적으로 액티비티를 선택하게 만드는 것은
사용자가 태그로 부터 멀어질 때 연결이 끊어져 원하는 작업을 수행하지 못하기 때문입니다.
그러므로 당신은 액티비티 선택기가 나타나는 것을 방지되도록 오직 관심있는 태그만을 처리하는 액티비티를 개발해야 합니다.
이렇게 개발되도록 돕기 위하여 안드로이드는 스캔된 NFC 태그를 분석하고 파싱하고 파싱된 데이터에 관심있는 앱을 탐색하는
특별한 태그 디스패치 시스템을 제공합니다. 이 시스템은 다음과 같은 것을 합니다.
1. NFC 태그 파싱과 MIME 타입 혹은 태그안에 있는 데이터 페이로드를 구분하는 URI를 알아내기.
2. 인텐트에 MIME 타입 혹은 URI와 페이로드를 캡슐화하기. 여기까지는 NFC 태그가 MIME 형식들과
URI들에 매핑되는 방법을 설명할 때 자세히 살펴보겠습니다.
3. 인텐트에 기반한 액티비티 실행하기.
이것은 앱에 NFC 태그 운송하는 방법에서 설명하겠습니다.
NFC 태그가 MIME 형식들과 URI들에 매핑되는 방법
NFC 앱을 작성하기에 앞서 태그 디스패치 시스템에서 각기 다른 NFC 태그를 어떻게 파싱하는지와 NDEF 메시지가 감지될 때 태그 디스패치 시스템이
어떻게 수행되는지를 이해하는 것이 중요합니다. NFC 태그들은 많은 기술들이 있으며 여러 다른 방법으로 데이터를 태그에 쓸 수 있습니다.
안드로이드는 NFC 포럼에서 정의하고 있는 NDEF 표준의 대부분을 지원하고 있습니다.
NDEF 데이터는 하나 혹은 그 이상의 레코드들(NdefRecord)을 포함하고 있는 메시지(NdefMessage) 안에 캡슐화되어져 있습니다.
각각의 NDEF 레코드는 당신이 생성하고자 한 레코드의 유형에 대한 규약서를 잘 따르는 well-formed 레코드입니다.
안드로이드는 또한 NDEF 데이터가 포함되지 않은 다른 태그 형식들도 지원합니다.
이러한 기능과 연관된 클래스들을 android.nfc.tech 패키지에 정의되어져 있습니다.
다른 형식의 태그와 작업하는 것은 태그와 통신하기 위해 쓰기 위해 당신만이 사용하는 프로토콜 스택을 만들어야 하는 것이므로
당신이 많은 안드로이드 장비들에서 지원되고 가능한 쉽게 개발하기 위해 NDEF를 사용할 것을 권합니다.
참고
NDEF 스펙을 다운받기 위해서는 NFC Forum Specification Download 사이트에서 받을 수 있고
NDEF 레코드들을 어떻게 구성할 수 있는지에 대한 예제는 Creating common types of NDEF records 에서 참조할 수 있습니다.
NFC 태그 배경 지식을 가지고 다음 섹션에서는 어떻게 안드로이드에서 NDEF 형식의 태그들을 핸들링하는 지 자세히 살펴보겠습니다.
안드로이드 장비가 NDEF 형식의 데이터를 NFC 태그에서 스캔하면 메시지를 파싱하고 데이터의 MIME 타입 혹은 구별을 위한 URI을 찾는 것을 시도합니다.
이것이 수행하려면 먼저 시스템은 전체 NDEF 메시지를 해석하는 방법을 결정하기 위해 NdefMessage 안에 있는 NdefRecord를 읽는다.
(하나의 NDEF 메시지는 여러개의 NDEF 레코드들을 가질 수 있습니다)
well-formed NDEF 메시지안에서 첫번째에는 아래의 필드가 포함되어져 있는 NdefRecord가 있습니다.
3-bit TNF (Type Name Format)
변수 길이 타입 필드를 해석하는 방법을 가리킵니다.
Variable length type
레코드의 타입을 설명합니다. 만약 TNF_WELL_KNOWN을 사용하면 이 필드에 RTD(Record Type Definition)을 명시합니다.
유효한 RTD 값들은 Table 2를 참조하기 바랍니다.
Variable length ID
레코드를 구분하기 위한 ID. 이 필드는 종종 사용되지 않지만 만약 사용자가 태그를 구분하는데 필요할지도 모릅니다.
Variable length payload
당신이 읽거나 쓰기 원하는 실제 데이터 페이로드. 하나의 NDEF 메시지는 여러개의 NDEF 레코드들을 가질 수 있습니다.
그래서 전체 페이로드가 NDEF 메시지의 첫번째 NDEF 레코드라고 생각하고 처리하지 말아야 합니다.
태그 디스패치 시스템은 TNF와 NDEF 메시지에서 MIME 타입 혹은 URI와 매핑을 위한 타입 필드들을 사용합니다.
만약 성공적이면 실제 페이로드와 함께 ACTION_NDEF_DISCOVERED 인텐트에 정보를 캡슐화합니다.
그러나 태그 디스패치 시스템이 첫번째 NDEF 레코드에 기반한 데이터의 유형을 결정할 수 없을 때가 있는데
이것은 NDEF 데이터가 MIME 타입 혹은 URI와 매핑되어져 있지 않거나 NFC 태그가 그 안에
어떠한 NDEF 데이터도 포함하고 있지 않을 때 발생합니다. 이러한 경우 태그의 기술과 페이로드에 대한 정보를 가지고 있는 Tag 객체가
ACTION_NDEF_DISCOVERED 인텐트에 캡슐화됩니다.
Table 1은 태그 디스패치 시스템이 TNF와 MIME 타입 혹은 URI를 위한 타입 필드들이 매핑되는 방법을 설명하고 있거나
TNF들이 MIME 타입 혹은 URI와 매핑될 수 없다는 것을 설명하고 있습니다.
후자의 경우 태그 디스패치 시스템은 ACTION_TECH_DISCOVERED 로 대체합니다.
예를 들어 만약 태그 디스패치 시스템이 TNF_ABSOLUTE_URI 형식의 레코드를 만나면 레코드의 variable length type 필드가 URI에 매핑됩니다.
태그 디스패치 시스템은 페이로드와 같은 태그에 대한 다른 정보와 함께 ACTION_NDEF_DISCOVERED 인텐트의 데이터 필드안에 URI를 캡슐화 시킵니다.
그리고 만약 TNF_UNKNOWN 형식의 레코드라면 TNF_ABSOLUTE_URI 형식의 작업 대신에 태그의 기술들을 캡슐화한 인텐트를 생성합니다.
NFC 태그들이 앱에 전달되는 방법
태그 디스패치 시스템은 NFC 태그와 태그를 식별하는 정보를 캡슐화한 인텐트를 생성하고 인텐트에 대한 필터를 등록한 앱에게 인텐트를 전송합니다.
만약 하나 이상의 앱에서 인텐트를 받기 원한다면 Activity Chooser가 사용자로 하여금 액티비티를 선택하도록 표시됩니다.
태그 디스패치 시스템은 3개의 인텐트를 정의할 수 있습니다.
높은 우선순위부터 낮은 우선순위로 정리하면 다음과 같습니다.
1. ACTION_NDEF_DISCOVERED
: NDEF 페이로드가 포함된 태그가 스캔되고 인식가능한 타입을 포함하고 있을 때 액태비티를 시작시키는데
사용되는 인텐트로 제일 높은 우선순위의 인텐트입니다. 그리고 태그 디스패치 시스템은 가능하다면
언제든지 다른 인텐트보다 먼저 이 인텐트와 함께 액티비티를 실행합니다.
2. ACTION_TECH_DISCOVERED
: ACTION_NDEF_DISCOVERED 인텐트를 핸들링을 위한 액티비티가 없다면 태그 디스패치 시스템은
이 인텐트와 함께 앱이 실행되도록 시도합니다. 만약 스캔된 태그가 MIME 타입 혹은 URI와 매핑될 수 없는 NDEF 데이터를 포함하고 있거나
잘 알려진 태그 기술외에 다른 NDEF 데이터를 포함하고 있지 않다면 ACTION_NDEF_DISCOVERED를 먼저 시작되는 것없이 바로 이 인텐트가 시작됩니다.
3. ACTION_TAG_DISCOVERED
: ACTION_NDEF_DISCOVERED 혹은 ACTION_TECH_DISCOVERED 인텐트를 핸들링하는 액티비티가 없다면 이 인텐트가 시작됩니다.
태그 디스패치 시스템은 기본적으로 다음과 같은 방법으로 동작합니다.
1. NFC 태그를 파싱할 때 태그 디스패치 시스쳄에 의해 생성된 인텐트와 함께 액티비티가 시작되도록 시도합니다.
(ACTION_NDEF_DISCOVERED 혹은 ACTION_TECH_DISCOVERED 중 하나)
2. 만약 인텐트에 대한 어떠한 액티비티도 없다면 인텐트에 대한 필터링하는 앱이 나타나거나 태그 디스패치 시스템이 가능한 모든 인텐트를 시도할 때까지
다음 낮은 순위의 인텐트 (ACTION_TECH_DISCOVERED 혹은 ACTION_TAG_DISCOVERED 중 하나) 와 함께 액티비티가 시작되도록 시도합니다.
3. 만약 어떠한 앱도 인텐트들에 대해 필터링되지 않으면 어떠한 것도 하지 않습니다.
가능하다면 언제든지 위 3가지 인텐트 중 가장 구체적인 것이기 때문에 NDEF 메시지들과 ACTION_NDEF_DISCOVERED 인텐트와 함께 동작한다.
ACTION_NDEF_DISCOVERED는 사용자에게 최상의 경험을 제공하므로 다른 2가지 인텐트 보다 더 명확하게 앱을 시작시키는 인텐트입니다.
NFC는 이러한 내부시스템을 통해서 서비스가 되고있습니다.
다음 포럼에는 실제 개발하는데에 필요한 정보들을 올리도록 하겠습니다.
안드로이드 매니페스트에 NFC 접근 요청하기
장비의 NFC 하드웨어에 접근하고 제대로 NFC 인텐트들을 처리하려면 먼저 AndroidManifest.xml 파일에 다음과 같은 항목을 선언해야 합니다.
§ NFC 하드웨어에 접근하기 위한 NFC <uses-permission> 엘리먼트:
<uses-permission android:name="android.permission.NFC"/>
§ 앱이 지원하는 최소 SDK 버전. API level 9는 오직 ACTION_TAG_DISCOVERED 만을 지원하고 EXTRA_NDEF_MESSAGES extra를 통해
NDEF 메시지를 접근할 수 있으며 다른 태그의 프로퍼티 혹은 I/O 연산을 지원하지 않습니다.
API level 10은 포괄적인 reader/writer를 지원할 뿐만 아니라 포그라운드(foreground) NDEF 푸싱을 지원하며 API level 14 는 안드로이드 빔을 통해
다른 장비에 NDEF 메시지를 쉽게 푸시하는 방법을 제공하고 NDEF 레코드들을 생성하기 위한 여분의 편리한 메서드들을 제공합니다.
<uses-sdk android:minSdkVersion="10"/>
§ NFC 하드웨어를 지원하는 장비들만 안드로이드 마켓에서 찾을 수 있기 위한 uses-feature 엘리먼트:
<uses-feature android:name="android.hardware.nfc" android:required="true"/>
만약 NFC 기능을 사용하지만 굳이 없어도 동작할 수 있는 앱이라면 생략할 수 있습니다.
만약 사용할 때면 런타임시에 NfcAdapter의 getDefaultAdapter() 가 null인지를 통해 NFC를 사용할 수 있는지를 확인할 수 있습니다.
NFC인텐트에 대한 필터링
핸들링하기 원하는 NFC 태그가 스캔될 때 앱이 시작되기 위해서는 안드로이드 매니페스트에 NFC 인텐트 중 하나, 둘 혹은 세가지 모두에 대한 필터를 등록할 수 있다.
그러나 보통 앱이 시작할 때 대부분을 제어하는데는 ACTION_NDEF_DISCOVERED 인텐트에 대한
필터만 등록하면 됩니다. ACTION_NDEF_DISCOVERED에 대한 필터를 등록한 앱이 없거나 페이로드가 NDEF가 아닐때는
ACTION_TECH_DISCOVERED 인텐트가 대체됩니다.
ACTION_TAG_DISCOVERED에 대한 필터링은 너무 일반적인 필터입니다.
많은 앱이 ACTION_NDEF_DISCOVERED 혹은 ACTION_TECH_DISCOVERED를 등록하므로 앱은 제일 낮은 우선순위로 시작될 것입니다.
ACTION_TAG_DISCOVERED는 오직 ACTION_NDEF_DISCOVERED 혹은 ACTION_TECH_DISCOVERED 에 대한 앱이 존재하지 않을 때
최후의 수단으로 사용하기 위할 때만 유효합니다.
항상 그렇지는 않지만 NFC 태그 배포들은 다양하고 매번 다른 2가지 인텐트들로 대체되는 것이
당신의 제어안에 없기 때문에 당신의 태그에는 쓰여진 데이터와 태그의 유형에서 제어하기 위해
NDEF 형식을 사용할 것을 권유합니다.
다음 섹션은 인텐트의 각 유형에 따라 필터링 하는 방법을 설명합니다.
ACTION_NDEF_DISCOVERED
ACTION_NDEF_DISCOVERED 인텐트를 필터링 하려면 원하는 데이터의 유형과 함께 인텐트 필터를 선언하면 된다.
다음의 예제는 text/plan MIME 타입의 데이터와 함께한 ACTION_NDEF_DISCOVERED 인텐트를 위한 필터 선언입니다.
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="text/plain"/> </intent-filter> |
다음은 http://developer.android.com/index.html 형식을 갖는 URI에 대한 필터 선언 예제입니다.
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="http" android:host="developer.android.com" android:pathPrefix="/index.html"/> </intent-filter> |
ACTION_TECH_DISCOVERED
만약 당신의 앱이 ACTION_TECH_DISCOVERED 인텐트에 대해 필터링 하고 싶다면 XML 자원 파일을 생성해야 하며
파일에는 액티비티가 지원하는 기술들이 tech-list 집합에 명시되어 있어야 합니다.
당신의 액티비티가 tech-list 집합이 태그에 의해 지원되는 기술들이 부분 집합에 속하는 것인지를 고려하고 싶다면
Tag 클래스의 getTechList() 메서드를 통해 지원되는 기술의 클래스명들을 획득하면 됩니다.
예를 들어 스캔된 태그가 MifareClassic, NdefFormatable 그리고 NfcA를 지원한다면
당신의 tech-list 집합은 세가지 모두, 두개 혹은 기술들 중 하나라도 명시해야 당신의 액티비티와 매칭됩니다.
다음의 샘플은 모든 기술들을 선언한 것입니다. 당신은 필요없는 것을 제거하여 사용하기 바랍니다.
이 파일은 <project-root>/res/xml 폴더안에 저장되어야 합니다.
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.IsoDep</tech> <tech>android.nfc.tech.NfcA</tech> <tech>android.nfc.tech.NfcB</tech> <tech>android.nfc.tech.NfcF</tech> <tech>android.nfc.tech.NfcV</tech> <tech>android.nfc.tech.Ndef</tech> <tech>android.nfc.tech.NdefFormatable</tech> <tech>android.nfc.tech.MifareClassic</tech> <tech>android.nfc.tech.MifareUltralight</tech> </tech-list> </resources> |
또한 여러개의 tech-list로 분류할 수 있습니다. 각각의 tech-list 집합은 독립적으로 고려되고 액티비티가 어떤 tech-list 집합이 getTechList() 메서드에 의해
반환되는 기술들의 부분 집합인지를 매칭 하는데 고려됩니다. 이것은 기술의 매칭을 위해 AND 와 OR 의미를 제공합니다.
아래의 예제는 NfcA와 Ndef 기술을 지원하거나 NfcB와 Ndef 기술을 지원하는 태그들과 매칭됩니다.
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.NfcA</tech> <tech>android.nfc.tech.Ndef</tech> </tech-list> </resources> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.NfcB</tech> <tech>android.nfc.tech.Ndef</tech> </tech-list> </resources> |
AndroidManifest.xml 파일에 <activity> 엘리먼트안에 <meta-data> 엘리먼트를 사용하여 자원 파일을 명시합니다.
<activity> ... <intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED"/> </intent-filter> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter"/> ... </activity> |
태그 기술들과 작업하는 것과 ACTION_TECH_DISCOVERED 인텐트에 대한 자세한 것은
Advanced NFC 문서의 지원되는 태그 기술들과 작업하기(Working with Supported Tag Technologies) 를 참조하기 바랍니다.
ACTION_TAG_DISCOVERED
ACTION_TAG_DISCOVERED 에 대한 필터 선언은 다음과 같이 하면 됩니다.
<intent-filter> <action android:name="android.nfc.action.TAG_DISCOVERED"/> </intent-filter> |
인텐트로 부터 정보 획득
액티비티가 NFC 인텐트에 의해 시작되면 인텐트로 부터 스캔된 NFC 태그에 대한 정보를 획득할 수 있습니다.
인텐트에는 다음과 같은 스캔된 태그에 대한 extra들이 포함되어져 있습니다.
§ EXTRA_TAG (required): 스캔된 태그를 표현하는 Tag 객체.
§ EXTRA_NDEF_MESSAGES (optional): 태그로 부터 파싱된 NDEF 메시지들의 배열.
이 extra는 인텐트들에서는 필수입니다.
§ {@link android.nfc.NfcAdapter#EXTRA_ID (optional): 태그의 low-level ID.
extra들을 얻기 위해서는 액티비티가 스캔된 태그를 확신시킬 수 있는 NFC 인텐트들 중 하나와 함께 런칭되었는지 체크한 후 인텐트에서 획득합니다.
아래의 예제는 ACTION_NDEF_DISCOVERED 인텐트인지 확인 후 인텐트에서 NDEF 메시지를 얻습니다.
public void onResume(){ super.onResume(); ... if(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())){ Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); if(rawMsgs != null){ msgs = new NdefMessage[rawMsgs.length]; for(int i =0; i < rawMsgs.length; i++){ msgs[i]=(NdefMessage) rawMsgs[i]; } } }//process the msgs array } |
선택적으로 당신은 인텐트로 부터 Tag 객체를 얻을 수 있습니다.
이 객체에는 페이로드가 포함되어 있으며 태그의 기술들을 열람할 수 있습니다.
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); |
NDEF레코드의 일반적 타입들 생성하기
다음의 모든 예제는 태그 혹은 빔을 통해 NDEF 메시지의 첫번째 NDEF 레코드에 작성하는 예입니다.
TNF_ABSOLUTE_URI
TNF_ABSOLUTE_URI 레코드는 다음과 같이 생성하여 NdefMessage 의 첫번 째 레코드에 저장합니다.
NdefRecord uriRecord = new NdefRecord( NdefRecord.TNF_ABSOLUTE_URI , "http://developer.android.com/index.html".getBytes(Charset.forName("US-ASCII")), newbyte[0], newbyte[0]); |
인텐트 필터는 다음과 같습니다.
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="http" android:host="developer.android.com" android:pathPrefix="/index.html"/> </intent-filter> |
TNF_MIME_MEDIA
TNF_MIME_MEDIA NDEF 레코드는 다음과 같이 생성하여 NdefMessage 의 첫번 째 레코드에 저장합니다.
NdefRecord mimeRecord = new NdefRecord( NdefRecord.TNF_MIME_MEDIA , "application/com.example.android.beam".getBytes(Charset.forName("US-ASCII")), new byte[0], "Beam me up, Android!".getBytes(Charset.forName("US-ASCII"))); |
인텐트 필터는 다음과 같습니다.
RTD_TEXT를 포함한 TNF_WELL_KNOWN
다음과 같이 TNF_WELL_KNOWN NDEF 레코드를 생성한 후 NdefMessage 의 첫번 째 레코드에 저장합니다.
public NdefRecord createTextRecord(String payload, Locale locale, boolean encodeInUtf8){ byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII")); Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8"):Charset.forName("UTF-16"); byte[] textBytes = payload.getBytes(utfEncoding); int utfBit = encodeInUtf8 ? 0:(17); char status = (char)(utfBit + langBytes.length); byte[] data =new byte[1+ langBytes.length + textBytes.length]; data[0]=(byte) status; System.arraycopy(langBytes, 0, data, 1, langBytes.length); System.arraycopy(textBytes, 0, data,1+ langBytes.length, textBytes.length); NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT,newbyte[0], data); return record; } |
인텐트 필터는 다음과 같습니다.
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="text/plain"/> </intent-filter> |
RTD_URI를 포함한 TNF_WELL_KNOWN
다음과 같이 TNF_WELL_KNOWN NDEF 레코드를 생성한 후 NdefMessage 의 첫 번째 레코드에 저장합니다.
byte[] uriField = "example.com".getBytes(Charset.forName("US-ASCII")); byte[] payload = new byte[uriField.length +1]; //URI Prefix로 1을 추가. payload[0] = 0x01; //접두사 http://www. System.arraycopy(uriField, 0, payload, 1, uriField.length); //payload에 URI 삽입 NdefRecord rtdUriRecord = new NdefRecord( NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI,new byte[0], payload); |
인텐트 필터는 다음과 같습니다.
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="http" android:host="example.com" android:pathPrefix=""/> </intent-filter> |
TNF_EXTERNAL_TYPE
다음과 같이 TNF_EXTERNAL_TYPE NDEF 레코드를 생성한 후 NdefMessage 의 첫 번째 레코드에
저장합니다.
byte[] payload;
|
인텐트 필터는 다음과 같습니다.
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="vnd.android.nfc" android:host="ext" android:pathPrefix="/example.com:externalType"/> </intent-filter> |
안드로이드 장비와 안드로이드 기반이 아닌 장비간에 향상된 방법을 지원하는 위해 보다 일반적인 NFC 태그에 배포가 가능한 TNF_EXTERNAL_TYPE을 사용합니다.
참고
TNF_EXTERNAL_TYPE에 대한 URNs는 urn:nfc:ext:example.com:externalType의 표준형식을 가집니다.
그러나 NFC 포럼의 RTD 규약서에는 URN의 urn:nfc:ext: 부분은 NDEF 레코드로 부터 생략합니다.
그래서 당신은 도메인(example.com)과 유형(externalType)을 콜론(':')으로 구분하여 제공해야 합니다.
TNF_EXTERNAL_TYPE이 디스패치될 때 안드로이드는 urn:nfc:ext:example.com:externalType URN을 인텐트 필터에 선언한 것에 따라
vnd.android.nfc://ext/example.com:externalType URI로 변경합니다.
Android Application Records (AAR)
안드로이드 4.0 (API level 14)에서 소개되는 Android Application Record (AAR)은 NFC 태그가 스캔될 때 시작되는 앱에게 강력한 확실성을 제공합니다.
AAR은 NDEF 레코드에 포함된 앱의 패키지 이름을 가집니다. NDEF 메시지의 어떠한 NDEF 레코드에도 AAR를 추가할 수 있다.
왜냐하면 안드로이드는 NDEF 메시지 전체에서 AAR들을 찾기 때문입니다. 만약 AAR이 발견되면 AAR에
있는 패키지 명에 근거하여 앱을 시작합니다. 만약 장비에 해당하는 앱이 없다면 앱을 다운로드 받기 위해 안드로이드 마켓이 런칭됩니다.
AAR은 만약 당신이 같은 인텐트에 대한 필터링을 가진 다른 앱으로 부터 보호하고 잠재적으로 당신이
배치한 특정 태그를 처리하는데 유용합니다. AAR은 패키지 이름이 반드시 사용되므로 인텐트 필터링 같은 액티비티 레벨이 아닌 오직 애플리케이션 레벨에서만
지원됩니다. 만약 액티비티 레멜에서 인텐트를 처리하기를 원한다면 AAR이 아닌 intent-filter 를 사용하기 바랍니다.
만약 태그가 AAR을 포함하고 있다면 태그 디스패치 시스템은 다음과 같은 방법으로 태그 정보를 전달합니다.
1. 먼저 인텐트 필터를 사용하는 액티비티를 시작하도록 시도합니다.
만약 인텐트와 매칭되는 액티비티와 AAR과도 매칭이 되면 해당 액티비티를 시작합니다.
2. 만약 AAR이 매칭되지 않으며 여러 액티비티가 인텐트를 처리할 수 있거나 어떠한 액티비티도
인텐트를 처리할 수 없다면 AAR에 명시된 앱의 메인 액티비티를 시작합니다.
3. 만약 어떠한 앱도 없다면 AAR에 해당하는 앱을 다운로드 받기 위해 안드로이드 마켓으로 이동합니다.
안드로이드는 AAR을 생성하기 위해 createApplicationRecord() 와 같은 간단한 API를 제공합니다.
오직 NdefMessage안 어디든지 AAR을 포함하면 된는데 AAR이 NdefMessage의 유일한 레코드가 아니라면 NdefMessage의 첫번째 레코드로 사용하지 않아야 합니다.
이것은 안드로이드 시스템이 앱이 필터링하는 인텐트를 생성하는데 사용하는 MIME 타입 혹은 태그의 URI를 결정하기 위해 NdefMessage 의 첫번째 레코드를
확인하기 때문입니다. 다음은 AAR을 생성하는 예제입니다
NdefMessage msg = new NdefMessage( new NdefRecord[]{ ..., NdefRecord.createApplicationRecord("com.example.android.beam")}); |
다른 장비에 NDEF 메시지들을 빔으로 전송하기
안드로이드 빔은 2개의 안드로이드 장비간에 P2P 데이터 교환이 가능하게 합니다.
빔을 통해 데이터를 다른 장비에 전송하기 원하는 앱은 포그라운드로 동작하고 있어야 하고 데이터를 받는 장비는 연결 해제 되어져 있어야 합니다다.
수신 장비와 충분히 근접하게 접촉되면 빔을 쏘는 장비에는 "Touch to Beam" 이라는 사용자 인터페이스가 표시되어
사용자가 수신 장비에 메시지를 전송할지 말지를 선택할 수 있습니다.
참고
포그라운드 NDEF 푸싱은 API level 10에서도 가능하며 안드룅드 빔과 유사한 기능을 제공합니다. 하지만 deprecated 상태입니다.
더 자세한 것은 enableForgroundNdefPush() 를 살펴 보기 바랍니다.
앱에서 안드로이드 빔을 활성화하기 위해서는 2개의 메서드 중 하나를 호출하면 됩니다.
§ setNdefPushMessage(): NdefMessage 를 빔을 통해 메시지를 전송합니다. 자동적으로 2개의 장비가 근접해졌을 때 메시지가 전송됩니다.
§ setNdefPushMessageCallback(): 장비가 데이터를 빔을 통해 전송할 수 있는 범위안에 들어올때 createNdefMessage() 가 포함되어져 있는 콜백을 호출합니다.
콜백은 전송이 가능할 때 NDEF 메시지를 생성하기 위함입니다.
액티비티는 오직 한번에 NDEF 메시지를 보낼 수 있습니다.
그래서 양쪽다 사용할 경우 setNdefPushMessageCallback()이 setNdefPushMessage() 보다 우선순위가 높다 먼저 설정됩니다.
안드로이드 빔을 사용하기 위해서는 다음과 같은 가이드라인을 따라주면 됩니다.
§ 데이터를 빔을 통해 전송하는 액티비티는 포그라원드에 있어야 하며 양쪽의 장비는 스크린이 잠금해제된 상태여야 합니다.
§ 보낼 데이터는 NdefMessage 객체로 캡슐화하여야 합니다.
§ 빔으로 전송되는 데이터를 수신하는 NFC 장비는 com.android.npp NDEF 푸시 프로토콜(NDEF push protocol) 혹은 NFC 포럼의 SNEP
(Simple NDEF Exchange Protocol)을 지원해야 합니다. com.android.npp 프로토콜은 안드로이드 2.3(API Level 9)에서 안드로이드 3.2(API Level 13)까지의
장비를 요구하며 com.android.npp 프로토콜과 SNEP 둘 다 지원은 안드로이드 4.0(API Level 14) 이상의 장비를 요구합니다.
안드로이드 빔을 활성화 하려면
1. 다른 장비에 푸시하기 원하는 NdefRecord들이 포함된 NdefMessage 객체를 생성합니다.
2. NdefMessage 객체와 함게께 setNdefPushMessage() 를 호출 하거나 액티비티의 onCreate() 에서NfcAdapter.CreateNdefMessageCallback 을 파라메터로 하여
setNdefPushMessageCallback() 를 호출합니다.
일반적으로, 2개의 장비들이 통신할 수 있는 영역에 있을 때 액티비티가 한번에 같은 NDEF 메시지를 푸시하는데는 setNdefPushMessage()를 사용하고
앱이 현재의 상황정보(context)에 따르고 사용자의 앱 안에서 기능의 사용에 따라 NDEF 메시지를 푸시하기 원하면 setNdefPushMessageCallback()을 사용합니다.
다음의 예제는 앱티비티의 onCreate() 메서드에서 NfcAdapter.CreateNdefMessageCallback을 호출하는 것입니다.
package com.example.android.beam;
import android.app.Activity; public class BeamextendsActivity implements CreateNdefMessageCallback { NfcAdapter mNfcAdapter;
@Override // NFC Adapter가 유효한지 체크 // 콜백 등록
@Override
@Override
@Override
/**
/**
|
위와 같이 인텐트 필터를 선언하면 com.example.android.beam 앱은 현재 NFC 태그를 스캔했거나 com.example.android.beam 형식의
AAR에 대한 안드로이드 빔을 수신했거나 NDEF 형식의 메시지가 android/com.example.android.beam 형식의 MIME 레코드가 포함되어져 있을 때 시작될 것입니다.
'IT_Programming > Android_Java' 카테고리의 다른 글
[Android] px 과 dip 단위 변환 (0) | 2013.08.11 |
---|---|
[펌] 안드로이드 apk 디컴파일(Decompile) 및 리패키징(Repackaging) 하기 (0) | 2013.08.10 |
안드로이드 바코드 인식기(OCR) 오픈 소스 zxing 분석 (0) | 2013.08.05 |
Android Streaming 예제 (0) | 2013.08.05 |
[android] Volley - Network & ImageLoading 라이브러리 ( google I/O 2013 ) (0) | 2013.08.03 |