3가지 모드를 지원한다
NFC 태그는 여러개의 NdefMessage로 구성된다.
NDEF : NFC Formum Data Exchange Format의 약자로 NFC Tag에 NFC data를 저장하기 위한 컨테이너 포맷
정의딘 여러 타입이 있다
: URI, TextRecord, AAR
NdefMessage는 여러 개의 NdefRecord로 구성된다.
태그 정보를 인텐트에 데이터와 식별 정보로 담아 전달한다.
3가지로 식별하는데
<uses-permission android:name="android.permission.NFC" />
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
nfc 태그가 인식되면 백그라운드에서도 해당 액티비티로 바로 이동하도록 했다. text타입의 nfc 데이터를 읽을 수 있도록 하였다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val action = intent.action
binding.textview.text = action
Toast.makeText(this, "onCreate$action", Toast.LENGTH_SHORT).show()
Log.d(TAG, "onCreate: $action")
if(action == NfcAdapter.ACTION_NDEF_DISCOVERED){
val data = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
val message = data?.get(0) as NdefMessage
val record = message.records[0] as NdefRecord
val byteArr = record.payload
Log.d(TAG, "onCreate: ${String(byteArr)}")
}
}
NFC를 통해 오는 데이터 형식이 뭔지 모르니 가장 범용적인
getParcelableArrayExtra
로 받아와서 비트단위 배열을 다시 내가 받을 타입으로 바꿔준다.
시스템으로 받아오는 NFC를 관리하는 manager
NFCAdapter를 얻을 때 사용하는 클래스
NFC 하드웨어에 접근하는 기능을 제공하는 클래스
getDefaultAdapter 함수를 통해 얻어옴
시스템으로부터 NFC Manager를 얻은 후에 manager로부터 기본 adapter를 가져올 때 사용
주요함수
위 예제 코드에서 알 수 있는데,
NFC 태그 안에는 NDEF message들로 이뤄져 있고, 그 message는 NDEF Record로 이루어져 있다.
NDEF Message를 intent에서 getParcelableArrayExtra
로 가져온다.
NDEF REcord는 getPayload
로 안에 포함된 어플리케이션 데이터 정보를 얻는다.
이들로 NDEF를 구성하는데,
NDEF 주요 메서드로는
connect, writeNdefMessage, getMaxSize, makeReadOnly, canMakeReadOnly, isWritable 등이 있다.
NDEF 상위 개념인 TAG가 지원하는 함수는
getTechList, getId
예제 코드와 같이 필요한 정보를 가져오기 위한 action 이름을 써서 인텐트에서 값을 받아온다.
대표적을
NfcAdapter.EXTRA_NDEF_MESSAGE : Tag에 저장된 NDEF Message 조회시
가 있다.
fun processIntent(intent: Intent){
if(intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED){
//태그의 ID 정보 조회
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
Log.d(TAG, "processIntent: ${tag?.id}")
//태그 안의 데이터의 정보
val data = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
val message = data!![0] as NdefMessage
val record = message.records[0]
val byteArr = record.payload
Log.d(TAG, "processIntent: ${String(byteArr)}")
binding.textview.text = String(byteArr)
}
}
NFC 앱을 스마트폰에서 사용하다 보면 태그 데이터를 다른 NFC 앱보다 가장 먼저 처리하고 싶을 수 있다. 더 나아가 시스템보다 먼저 NFC 요청을 처리하고 싶을 수 있다. 즉, intent-filter를 scan 하기 전에 동작하도록 하는 것을 foreground 모드로 해결할 수 있다.
Foreground 모드에서는 pending intent를 만든다.
그리고,
nfc adapter를 nAdapter.enableForegroundDispatch(this, pIntent, filters, null)
enableForegroundDispatch 해주어 포그라운드 동작을 하도록 한다.
class MainActivity : AppCompatActivity() {
private lateinit var nAdapter: NfcAdapter
private lateinit var pIntent: PendingIntent
private lateinit var filters: Array<IntentFilter>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val action = intent.action
binding.textview.text = action
Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show()
nAdapter = NfcAdapter.getDefaultAdapter(this)
val i = Intent(this, MainActivity::class.java) //자기자신으로 다시 호출. MainActivity::class.java == javaClass
i.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
// 당장 실행하지 않으므로 pending intent 처리
pIntent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_MUTABLE)
//수신할 태그 데이터 관련 필터 생성
//Text Record 수신하기 위한 필터
val ndef_filter = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
// ndef_filter.addDataType("text/plain")
ndef_filter.addDataScheme("https")
filters = arrayOf(ndef_filter)
}
override fun onResume() {
super.onResume()
nAdapter.enableForegroundDispatch(this, pIntent, filters, null)
}
override fun onPause() {
super.onPause()
nAdapter.disableForegroundDispatch(this)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Toast.makeText(this, "onNewIntent", Toast.LENGTH_SHORT).show()
val action = intent.action
Log.d(TAG, "New Intent action : $action")
parseData(intent)
}
private fun parseData(intent: Intent) {
if(intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED){
//태그의 ID 정보 조회
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
Log.d(TAG, "processIntent: ${tag?.id}")
//태그 안의 데이터의 정보
val data = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
val message = data!![0] as NdefMessage
val record = message.records[0]
val byteArr = record.payload
Log.d(TAG, "processIntent: ${String(byteArr)}")
binding.textview.text = String(byteArr)
}
}
}
ndef_filter.addDataScheme("https")
와 같은 필터로 읽히지 않는 nfc태그라면 포그라운드 모드로 내가 호출되는 것이 아닌 nfc를 키는 앱을 고르는 chooser가 나타난다.
intent flag값이 singleTop으로 설정된 상태에서 인텐트를 수신하려면 onNewIntent
함수를 오버라이딩 해야 한다.
참고로 여러개의 필터 조건을 넣고 싶다면 각각 마다 intent filter를 만들어줘야 한다.
val ndef_filter = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
// ndef_filter.addDataType("text/plain")
ndef_filter.addDataScheme("https")
//둘다 쓰려면
val ndef_filter1 = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
ndef_filter1.addDataType("text/plain")
filters = arrayOf(ndef_filter , ndef_filter1)
모든 태그를 포그라운드에서 감지하려면 필터를
val ndef_filter = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
filters = arrayOf(ndef_filter)
해주면 될 것이다.
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// 이후 다른 method에서 getIntent() 호출시 newIntent에서 받은 Intent를 사용하기 위함.
// set하지 않으면 기존의 intent인 action Main이 나옴.
setIntent(intent)
getNFCData(getIntent())
}
onNewIntent의 intent는 onresume, oncreate의 intent와 별개의 intent로 불릴 때마다 생성된다. 이를 라이프사이클에 전달해주기 위해선
setIntent(intent)
를 해주면 된다.
다양한 유형의 정보를 NFC 태그에 쓸 수 있다.
AAR은 태그에 실행할 앱을 명시적으로 지정할 수 있다.
이 안에는 앱을 실행시킬 애플리케이션의 패키지명이 저장된다. 태그 내 AAR이 포함되어 있는 경우 Tag Dispatch System의 처리 순서는
어플리케이션 실행 -> 없으면 구글 플레이스토어 순이다.
var ndefR: NdefRecord? = null
...
ndefR1 = NdefRecord.createApplicationRecord("com.ssafy.android.tag_recognition")
record에 위처럼 createApplicationRecord로 패키지명을 넣어주면 해당 패키지에 해당되는 앱이 실행된다.
private lateinit var nfcAdapter: NfcAdapter
private lateinit var pIntent: PendingIntent
private lateinit var filters: Array<IntentFilter>
private lateinit var tagType: String
private lateinit var tagData: String
private lateinit var binding: ActivityTagWriteBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTagWriteBinding.inflate(layoutInflater)
setContentView(binding.root)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
if (nfcAdapter == null) {
finish()
}
//넘어온 데이터를 변수에 저장한다.
tagType = intent.getStringExtra("type").toString()
tagData = intent.getStringExtra("data").toString()
Toast.makeText(this, "type : $tagType, data : $tagData", Toast.LENGTH_SHORT).show()
//태그 정보가 포함된 인텐트를 처리할 액티비티 지정
val intent = Intent(this, TagWriteActivity::class.java)
//SingleTop설정
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
pIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE)
val tagFilter = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
filters = arrayOf(tagFilter)
}
public override fun onResume() {
super.onResume()
nfcAdapter.enableForegroundDispatch(this, pIntent, filters, null)
}
public override fun onPause() {
super.onPause()
nfcAdapter.disableForegroundDispatch(this)
}
public override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
//태그에 데이터를 write 작업을 수행해야 한다..
val action = intent.action
if (action == NfcAdapter.ACTION_NDEF_DISCOVERED || action == NfcAdapter.ACTION_TECH_DISCOVERED || action == NfcAdapter.ACTION_TAG_DISCOVERED) {
//Log.d(TAG, "ACTION_NDEF_DISCOVERED...")
//1. 태그 detect.... Tag 객체
val detectTag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
//writeTag 함수 호출
writeTag(makeNdefMessage(tagType, tagData), detectTag)
}
}
//T, "SSAFY" / U, "https://m.naver.com"
private fun makeNdefMessage(type: String?, data: String?): NdefMessage {
var ndefR: NdefRecord? = null
var ndefR1: NdefRecord? = null
if (type == "T") {//TextRecord
ndefR = NdefRecord.createTextRecord("en", data)
//AAR
ndefR1 = NdefRecord.createApplicationRecord("com.ssafy.android.tag_recognition")
} else if (type == "U") {//URI
ndefR = NdefRecord.createUri(data)
} else {
//다른 형태....
}
return NdefMessage(arrayOf(ndefR,ndefR1))
}
//NFC tag 에 데이터를 write 코드...
private fun writeTag(msg: NdefMessage, tag: Tag?) {
//Ndef 객체를 얻는다 : Ndef.get(tag)
val ndef = Ndef.get(tag)
val msgSize = msg.toByteArray().size
try {
if (ndef != null) {
//ndef 객체를 이용해서 connect
ndef.connect()
//tag가 write모드를 지원하는지 여부 체크
if (!ndef.isWritable) {
Toast.makeText(this, "Write를 지원하지 않습니다..", Toast.LENGTH_SHORT).show()
return
}
if (ndef.maxSize < msgSize) {
Toast.makeText(this, "Write할 데이터가 태그 크기보다 큽니다..", Toast.LENGTH_SHORT).show()
return
}
//ndef객체의 writeNdefMessage(msg) 태그에 write 한다...
ndef.writeNdefMessage(msg)
Toast.makeText(this, "태그에 데이터를 write 하였습니다..", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "Failed to write tag", Toast.LENGTH_SHORT).show()
}
}
}