NFC支持如下3種工作模式:讀卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、點對點模式(P2P mode)。
下來分別看一下這三種模式:
數據在NFC芯片中,可以簡單理解成「刷標籤」。本質上就是通過支持NFC的手機或其它電子設備從帶有NFC芯片的標籤、貼紙、名片等媒介中讀寫信息。通常NFC標籤是不需要外部供電的。當支持NFC的外設向NFC讀寫數據時,它會發送某種磁場,而這個磁場會自動的向NFC標籤供電。
數據在支持NFC的手機或其它電子設備中,可以簡單理解成「刷手機」。本質上就是將支持NFC的手機或其它電子設備當成借記卡、公交卡、門禁卡等IC卡使用。基本原理是將相應IC卡中的信息憑證封裝成數據包存儲在支持NFC的外設中 。
在使用時還需要一個NFC射頻器(相當於刷卡器)。將手機靠近NFC射頻器,手機就會接收到NFC射頻器發過來的信號,在通過一系列複雜的驗證後,將IC卡的相應信息傳入NFC射頻器,最後這些IC卡數據會傳入NFC射頻器連接的電腦,並進行相應的處理(如電子轉帳、開門等操作)。
該模式與藍牙、紅外差不多,用於不同NFC設備之間進行數據交換,不過這個模式已經沒有有「刷」的感覺了。其有效距離一般不能超過4釐米,但傳輸建立速度要比紅外和藍牙技術快很多,傳輸速度比紅外塊得多,如過雙方都使用Android4.2,NFC會直接利用藍牙傳輸。這種技術被稱爲Android Beam。所以使用Android Beam傳輸數據的兩部設備不再限於4釐米之內。
點對點模式的典型應用是兩部支持NFC的手機或平板電腦實現數據的點對點傳輸,例如,交換圖片或同步設備聯繫人。因此,通過NFC,多個設備如數字相機,計算機,手機之間,都可以快速連接,並交換資料或者服務。
下面看一下NFC、藍牙和紅外之間的差異:
對比項 | NFC | 藍牙 | 紅外 |
---|---|---|---|
網絡類型 | 點對點 | 單點對多點 | 點對點 |
有效距離 | <=0.1m | <=10m,最新的藍牙4.0有效距離可達100m | 一般在1m以內,熱技術連接,不穩定 |
傳輸速度 | 最大424kbps | 最大24Mbps | 慢速115.2kbps,快速4Mbps |
建立時間 | <0.1s | 6s | 0.5s |
安全性 | 安全,硬件實現 | 安全,軟件實現 | 不安全,使用IRFM時除外 |
通信模式 | 主動-主動/被動 | 主動-主動 | 主動-主動 |
成本 | 低 | 中 | 低 |
不同的NFC標籤之間差異很大,有的只支持簡單的讀寫操作,有時還會採用支持一次性寫入的芯片,將NFC標籤設計成只讀的。當然,也存在一些複雜的NFC標籤,例如,有一些NFC標籤可以通過硬件加密的方式限制對某一區域的訪問。還有一些標籤自帶操作環境,允許NFC設備與這些標籤進行更復雜的交互。這些標籤中的數據也會採用不同的格式。但Android SDK API主要支持NFC論壇標準(Forum Standard),這種標準被稱爲NDEF(NFC Data Exchange Format,NFC數據交換格式)。
NDEF格式其實就類似於硬盤的NTFS,下面我們看一下NDEF數據:
Android SDK API支持如下3種NDEF數據的操作:
1)從NFC標籤讀取NDEF格式的數據。
2)向NFC標籤寫入NDEF格式的數據。
3)通過Android Beam技術將NDEF數據發送到另一部NFC設備。
用於描述NDEF格式數據的兩個類:
1)NdefMessage:描述NDEF格式的信息,實際上我們寫入NFC標籤的就是NdefMessage對象。
2)NdefRecord:描述NDEF信息的一個信息段,一個NdefMessage可能包含一個或者多個NdefRecord。
NdefMessage和NdefRecord是Android NFC技術的核心類,無論讀寫NDEF格式的NFC標籤,還是通過Android Beam技術傳遞Ndef格式的數據,都需要這兩個類。
對於某些特殊需求,可能要存任意的數據,對於這些數據,我們就需要自定義格式。這些數據格式實際上就是普通的字節流,至於字節流中的數據代表什麼,就由開發人員自己定義了。
1
2
3
4
|
<
uses
-
sdk
android
:
minSdkVersion
=
"14"
/
>
<
uses
-
permission
android
:
name
=
"android.permission.NFC"
/
>
<
!
--
要求當前設備必須要有
NFC芯片
--
>
<
uses
-
feature
android
:
name
=
"android.hardware.nfc"
android
:
required
=
"true"
/
>
|
Activity清單需要配置一下launchMode屬性:
1
2
3
|
<
activity
android
:
name
=
".TagTextActivity"
android
:
launchMode
=
"singleTop"
/
>
|
而Activity中,我們也抽取了一個通用的BaseNfcActivity,如下(後面的Activity實現都繼承於BaseNfcActivity):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
/**
* 1.子類需要在onCreate方法中做Activity初始化。
* 2.子類需要在onNewIntent方法中進行NFC標籤相關操作。
* 當launchMode設置爲singleTop時,第一次運行調用onCreate方法,
* 第二次運行將不會創建新的Activity實例,將調用onNewIntent方法
* 所以我們獲取intent傳遞過來的Tag數據操作放在onNewIntent方法中執行
* 如果在棧中已經有該Activity的實例,就重用該實例(會調用實例的onNewIntent())
* 只要NFC標籤靠近就執行
*/
public
class
BaseNfcActivity
extends
AppCompatActivity
{
private
NfcAdapter
mNfcAdapter
;
private
PendingIntent
mPendingIntent
;
/**
* 啓動Activity,界面可見時
*/
@Override
protected
void
onStart
(
)
{
super
.
onStart
(
)
;
mNfcAdapter
=
NfcAdapter
.
getDefaultAdapter
(
this
)
;
//一旦截獲NFC消息,就會通過PendingIntent調用窗口
mPendingIntent
=
PendingIntent
.
getActivity
(
this
,
0
,
new
Intent
(
this
,
getClass
(
)
)
,
0
)
;
}
/**
* 獲得焦點,按鈕可以點擊
*/
@Override
public
void
onResume
(
)
{
super
.
onResume
(
)
;
//設置處理優於所有其他NFC的處理
if
(
mNfcAdapter
!=
null
)
mNfcAdapter
.
enableForegroundDispatch
(
this
,
mPendingIntent
,
null
,
null
)
;
}
/**
* 暫停Activity,界面獲取焦點,按鈕可以點擊
*/
@Override
public
void
onPause
(
)
{
super
.
onPause
(
)
;
//恢復默認狀態
if
(
mNfcAdapter
!=
null
)
mNfcAdapter
.
disableForegroundDispatch
(
this
)
;
}
}
|
注意:通常來說,所有處理NFC的Activity都要設置launchMode屬性爲singleTop或者singleTask,保證了無論NFC標籤靠近手機多少次,Activity實例只有一個。
接下來看幾個具體的NFC標籤應用實例,通過情景學習快速掌握NFC技術:
場景是這樣的:現將應用程序的包寫到NFC程序上,然後我們將NFC標籤靠近Android手機,手機就會自動運行包所對應的程序,這個是NFC比較基本的一個應用。下面以貼近標籤自動運行Android自帶的「短信」爲例。
向NFC標籤寫入數據一般分爲三步:
1
|
Tag
tag
=
intent
.
getParcelableExtra
(
NfcAdapter
.
EXTRA_TAG
)
;
|
1
|
Ndef
ndef
=
Ndef
.
get
(
tag
)
;
|
1
|
ndef
.
writeNdefMessage
(
ndefMessage
)
;
|
詳細實現代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
public
class
RunAppActivity
extends
BaseNfcActivity
{
private
String
mPackageName
=
"com.android.mms"
;
//短信
@
Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
activity_main
)
;
}
@
Override
public
void
onNewIntent
(
Intent
intent
)
{
if
(
mPackageName
==
null
)
return
;
//1.獲取Tag對象
Tag
detectedTag
=
intent
.
getParcelableExtra
(
NfcAdapter
.
EXTRA_TAG
)
;
writeNFCTag
(
detectedTag
)
;
}
/**
* 往標籤寫數據的方法
*
* @param tag
*/
public
void
writeNFCTag
(
Tag
tag
)
{
if
(
tag
==
null
)
{
return
;
}
NdefMessage
ndefMessage
=
new
NdefMessage
(
new
NdefRecord
[
]
{
NdefRecord
.
createApplicationRecord
(
mPackageName
)
}
)
;
//轉換成字節獲得大小
int
size
=
ndefMessage
.
toByteArray
(
)
.
length
;
try
{
//2.判斷NFC標籤的數據類型(通過Ndef.get方法)
Ndef
ndef
=
Ndef
.
get
(
tag
)
;
//判斷是否爲NDEF標籤
if
(
ndef
!=
null
)
{
ndef
.
connect
(
)
;
//判斷是否支持可寫
if
(
!
ndef
.
isWritable
(
)
)
{
return
;
}
//判斷標籤的容量是否夠用
if
(
ndef
.
getMaxSize
(
)
<
size
)
{
return
;
}
//3.寫入數據
ndef
.
writeNdefMessage
(
ndefMessage
)
;
Toast
.
makeText
(
this
,
"寫入成功"
,
Toast
.
LENGTH_SHORT
)
.
show
(
)
;
}
else
{
//當我們買回來的NFC標籤是沒有格式化的,或者沒有分區的執行此步
//Ndef格式類
NdefFormatable
format
=
NdefFormatable
.
get
(
tag
)
;
//判斷是否獲得了NdefFormatable對象,有一些標籤是隻讀的或者不允許格式化的
if
(
format
!=
null
)
{
//連接
format
.
connect
(
)
;
//格式化並將信息寫入標籤
format
.
format
(
ndefMessage
)
;
Toast
.
makeText
(
this
,
"寫入成功"
,
Toast
.
LENGTH_SHORT
)
.
show
(
)
;
}
else
{
Toast
.
makeText
(
this
,
"寫入失敗"
,
Toast
.
LENGTH_SHORT
)
.
show
(
)
;
}
}
}
catch
(
Exception
e
)
{
}
}
}
|
注意:設置 RunAppActivity 的 launchMode 屬性爲 singleTop。
現在看一下效果圖:
將NFC標籤貼近手機背面,自動寫入數據,此時退出所有程序,返回桌面,然後再將NFC標籤貼近手機背面,將會看到自動打開了「短信」。
如何讓NFC標籤貼近手機,手機可以自動打開一個網頁呢?
首先我們創建一個NdefRecord,Android已經爲我們提供好了這樣的方法:
1
2
3
4
|
//直接接受一個Uri
public
NdefRecord
createUri
(
String
uriString
)
;
//接受一個Uri的對象
public
NdefRecord
createUri
(
Uri
uri
)
;
|
實現代碼對比「3.利用NFC標籤讓Android自動運行程序」部分只是修改了writeNFCTag方法中
1
2
|
NdefMessage
ndefMessage
=
new
NdefMessage
(
new
NdefRecord
[
]
{
NdefRecord
.
createApplicationRecord
(
mPackageName
)
}
)
;
|
爲
1
2
|
NdefMessage
ndefMessage
=
new
NdefMessage
(
new
NdefRecord
[
]
{
NdefRecord
.
createUri
(
Uri
.
parse
(
"http://www.nfchome.org"
)
)
}
)
;
|
其餘不變。
上面這個功能還是比較有用的,例如我們往某些商品上貼上NFC標籤,裏面寫入該商品的詳細介紹網頁Uri,當用戶貼近商品時,就會自動打開該商品的詳情介紹。
通過上面這兩個案例的學習相信很多人已經對NFC感起了興趣,那麼下來滲透式的分析一下NDEF文本格式,看看NDEF到底是個什麼東西。
獲取NFC標籤中的數據要通過 NdefRecord.getPayload 方法完成。當然,在處理這些數據之前,最好判斷一下NdefRecord對象中存儲的是不是NDEF文本格式數據。
1)TNF(類型名格式,Type Name Format)必須是NdefRecord.TNF_WELL_KNOWN。
2)可變的長度類型必須是NdefRecord.RTD_TEXT。
如果這兩個標準同時滿足,那麼就爲NDEF格式。
不管什麼格式的數據本質上都是由一些字節組成的。對於NDEF文本格式來說,這些數據的第1個字節描述了數據的狀態,然後若干個字節描述文本的語言編碼,最後剩餘字節表示文本數據。這些數據格式由NFC Forum的相關規範定義,可以通過 http://members.nfc-forum.org/specs/spec_dashboard 下載相關的規範。
下面這兩張表是規範中 3.2節 相對重要的翻譯部分:
NDEF文本數據格式:
偏移量 | 長度(bytes) | 描述 |
---|---|---|
0 | 1 | 狀態字節,見下表(狀態字節編碼格式) |
1 | n | ISO/IANA語言編碼。例如」en-US」,」fr-CA」。編碼格式是US-ASCII,長度(n)由狀態字節的後6位指定。 |
n+1 | m | 文本數據。編碼格式是UTF-8或UTF-16。編碼格式由狀態字節的前3位指定。 |
狀態字節編碼格式:
字節位(0是最低位,7是最高位) | 含義 |
---|---|
7 | 0:文本編碼爲UTF-8,1:文本編碼爲UTF-16 |
6 | 必須設爲0 |
5..0 | 語言編碼的長度(佔用的字節個數) |
下面我們動手實現NFC標籤中的文本數據的讀寫操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
public
class
ReadTextActivity
extends
BaseNfcActivity
{
private
TextView
mNfcText
;
private
String
mTagText
;
@
Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
activity_read_text
)
;
mNfcText
=
(
TextView
)
findViewById
(
R
.
id
.
tv_nfctext
)
;
}
@
Override
public
void
onNewIntent
(
Intent
intent
)
{
//1.獲取Tag對象
Tag
detectedTag
=
intent
.
getParcelableExtra
(
NfcAdapter
.
EXTRA_TAG
)
;
//2.獲取Ndef的實例
Ndef
ndef
=
Ndef
.
get
(
detectedTag
)
;
mTagText
=
ndef
.
getType
(
)
+
"\nmaxsize:"
+
ndef
.
getMaxSize
(
)
+
"bytes\n\n"
;
readNfcTag
(
intent
)
;
mNfcText
.
setText
(
mTagText
)
;
}
/**
* 讀取NFC標籤文本數據
*/
private
void
readNfcTag
(
Intent
intent
)
{
if
(
NfcAdapter
.
ACTION_NDEF_DISCOVERED
.
equals
(
intent
.
getAction
(
)
)
)
{
Parcelable
[
]
rawMsgs
=
intent
.
getParcelableArrayExtra
(
NfcAdapter
.
EXTRA_NDEF_MESSAGES
)
;
NdefMessage
msgs
[
]
=
null
;
int
contentSize
=
0
;
if
(
rawMsgs
!=
null
)
{
msgs
=
new
NdefMessage
[
rawMsgs
.
length
]
;
for
(
int
i
=
0
;
i
<
rawMsgs
.
length
;
i
++
)
{
msgs
[
i
]
=
(
NdefMessage
)
rawMsgs
[
i
]
;
contentSize
+=
msgs
[
i
]
.
toByteArray
(
)
.
length
;
}
}
try
{
if
(
msgs
!=
null
)
{
NdefRecord
record
=
msgs
[
0
]
.
getRecords
(
)
[
0
]
;
String
textRecord
=
parseTextRecord
(
record
)
;
mTagText
+=
textRecord
+
"\n\ntext\n"
+
contentSize
+
" bytes"
;
}
}
catch
(
Exception
e
)
{
}
}
}
/**
* 解析NDEF文本數據,從第三個字節開始,後面的文本數據
* @param ndefRecord
* @return
*/
public
static
String
parseTextRecord
(
NdefRecord
ndefRecord
)
{
/**
* 判斷數據是否爲NDEF格式
*/
//判斷TNF
if
(
ndefRecord
.
getTnf
(
)
!=
NdefRecord
.
TNF_WELL_KNOWN
)
{
return
null
;
}
//判斷可變的長度的類型
if
(
!
Arrays
.
equals
(
ndefRecord
.
getType
(
)
,
NdefRecord
.
RTD_TEXT
)
)
{
return
null
;
}
try
{
//獲得字節數組,然後進行分析
byte
[
]
payload
=
ndefRecord
.
getPayload
(
)
;
//下面開始NDEF文本數據第一個字節,狀態字節
//判斷文本是基於UTF-8還是UTF-16的,取第一個字節"位與"上16進制的80,16進制的80也就是最高位是1,
//其他位都是0,所以進行"位與"運算後就會保留最高位
String
textEncoding
=
(
(
payload
[
0
]
&
0x80
)
==
0
)
?
"UTF-8"
:
"UTF-16"
;
//3f最高兩位是0,第六位是1,所以進行"位與"運算後獲得第六位
int
languageCodeLength
=
payload
[
0
]
&
0x3f
;
//下面開始NDEF文本數據第二個字節,語言編碼
//獲得語言編碼
String
languageCode
=
new
String
(
payload
,
1
,
languageCodeLength
,
"US-ASCII"
)
;
//下面開始NDEF文本數據後面的字節,解析出文本
String
textRecord
=
new
String
(
payload
,
languageCodeLength
+
1
,
payload
.
length
-
languageCodeLength
-
1
,
textEncoding
)
;
return
textRecord
;
}
catch
(
Exception
e
)
{
throw
new
IllegalArgumentException
(
)
;
}
}
}
|
注意:Activity清單需要配置一下launchMode屬性(後面一樣要注意):
1
2
3
|
<
activity
android
:
name
=
".ReadTextActivity"
android
:
launchMode
=
"singleTop"
/
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
public
class
WriteTextActivity
extends
BaseNfcActivity
{
private
String
mText
=
"NFC-NewText-123"
;
@
Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
activity_write_text
)
;
}
@
Override
public
void
onNewIntent
(
Intent
intent
)
{
if
(
mText
==
null
)
return
;
//獲取Tag對象
Tag
detectedTag
=
intent
.
getParcelableExtra
(
NfcAdapter
.
EXTRA_TAG
)
;
NdefMessage
ndefMessage
=
new
NdefMessage
(
new
NdefRecord
[
]
{
createTextRecord
(
mText
)
}
)
;
boolean
result
=
writeTag
(
ndefMessage
,
detectedTag
)
;
if
(
result
)
{
Toast
.
makeText
(
this
,
"寫入成功"
,
Toast
.
LENGTH_SHORT
)
.
show
(
)
;
}
else
{
Toast
.
makeText
(
this
,
"寫入失敗"
,
Toast
.
LENGTH_SHORT
)
.
show
(
)
;
}
}
/**
* 創建NDEF文本數據
* @param text
* @return
*/
public
static
NdefRecord
createTextRecord
(
String
text
)
{
byte
[
]
langBytes
=
Locale
.
CHINA
.
getLanguage
(
)
.
getBytes
(
Charset
.
forName
(
"US-ASCII"
)
)
;
Charset
utfEncoding
=
Charset
.
forName
(
"UTF-8"
)
;
//將文本轉換爲UTF-8格式
byte
[
]
textBytes
=
text
.
getBytes
(
utfEncoding
)
;
//設置狀態字節編碼最高位數爲0
int
utfBit
=
0
;
//定義狀態字節
char
status
=
(
char
)
(
utfBit
+
langBytes
.
length
)
;
byte
[
]
data
=
new
byte
[
1
+
langBytes
.
length
+
textBytes
.
length
]
;
//設置第一個狀態字節,先將狀態碼轉換成字節
data
[
0
]
=
(
byte
)
status
;
//設置語言編碼,使用數組拷貝方法,從0開始拷貝到data中,拷貝到data的1到langBytes.length的位置
System
.
arraycopy
(
langBytes
,
0
,
data
,
1
,
langBytes
.
length
)
;
//設置文本字節,使用數組拷貝方法,從0開始拷貝到data中,拷貝到data的1 + langBytes.length
//到textBytes.length的位置
System
.
arraycopy
(
textBytes
,
0
,
data
,
1
+
langBytes
.
length
,
textBytes
.
length
)
;
//通過字節傳入NdefRecord對象
//NdefRecord.RTD_TEXT:傳入類型 讀寫
NdefRecord
ndefRecord
=
new
NdefRecord
(
NdefRecord
.
TNF_WELL_KNOWN
,
NdefRecord
.
RTD_TEXT
,
new
byte
[
0
]
,
data
)
;
return
ndefRecord
;
}
/**
* 寫數據
* @param ndefMessage 創建好的NDEF文本數據
* @param tag 標籤
* @return
*/
public
static
boolean
writeTag
(
NdefMessage
ndefMessage
,
Tag
tag
)
{
try
{
Ndef
ndef
=
Ndef
.
get
(
tag
)
;
ndef
.
connect
(
)
;
ndef
.
writeNdefMessage
(
ndefMessage
)
;
return
true
;
}
catch
(
Exception
e
)
{
}
return
false
;
}
}
|
我們將手機貼近NFC標籤,當寫入成功會彈出「寫入成功」的吐司。下面我們再驗證一下是否成功寫入:
我們看到,數據已經寫入成功了,說明到此我們已經成功的讀寫NFC標籤中的文本數據了。
與NDEF文本格式一樣,存儲在NFC標籤中的Uri也有一定的格式,http://members.nfc-forum.org/specs/spec_dashboard
Name | 偏移 | 大小 | 值 | 描述 |
---|---|---|---|---|
識別碼 | 0 | 1byte | Uri識別碼 | 用於存儲已知Uri的前綴 |
Uri字段 | 1 | N | UTF-8類型字符串 | 用於存儲剩餘字符串 |
十進制 | 十六進制 | 協議 | 十進制 | 十六進制 | 協議 |
---|---|---|---|---|---|
0 | 0x00 | N/A | 1 | 0x01 | http://www. |
2 | 0x02 | https://www. | 3 | 0x03 | http:// |
4 | 0x04 | https:// | 5 | 0x05 | tel: |
6 | 0x06 | mailto: | 7 | 0x07 | ftp://anonymous:[email protected] |
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: |
每一個協議,都是用十六進制來存儲於識別碼位置(佔1byte)。
是不是相對簡單了些,那麼下來我們來解析一個Uri。
這裏我們定義一個UriPrefix類,以便方便的獲取Uri前綴:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
public
class
UriPrefix
{
public
static
final
Map
<
Byte
,
String
>
URI_PREFIX_MAP
=
new
HashMap
<
Byte
,
String
>
(
)
;
// 預先定義已知Uri前綴
static
{
URI_PREFIX_MAP
.
put
(
(
byte
)
0x00
,
""
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x01
,
"http://www."
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x02
,
"https://www."
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x03
,
"http://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x04
,
"https://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x05
,
"tel:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x06
,
"mailto:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x08
,
"ftp://ftp."
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x09
,
"ftps://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x0A
,
"sftp://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x0B
,
"smb://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x0C
,
"nfs://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x0D
,
"ftp://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x0E
,
"dav://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x0F
,
"news:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x10
,
"telnet://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x11
,
"imap:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x12
,
"rtsp://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x13
,
"urn:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x14
,
"pop:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x15
,
"sip:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x16
,
"sips:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x17
,
"tftp:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x18
,
"btspp://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x19
,
"btl2cap://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x1A
,
"btgoep://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x1B
,
"tcpobex://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x1C
,
"irdaobex://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x1D
,
"file://"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x1E
,
"urn:epc:id:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x1F
,
"urn:epc:tag:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x20
,
"urn:epc:pat:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x21
,
"urn:epc:raw:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x22
,
"urn:epc:"
)
;
URI_PREFIX_MAP
.
put
(
(
byte
)
0x23
,
"urn:nfc:"
)
;
}
}
|
然後我們來看一下清單文件中Activity的相關配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<
activity
android
:
name
=
".ReadWriteUriActivity"
android
:
label
=
"讀寫NFC標籤的Uri"
android
:
launchMode
=
"singleTop"
>
<
intent
-
filter
>
<
action
android
:
name
=
"android.nfc.action.NDEF_DISCOVERED"
/
>
<
category
android
:
name
=
"android.intent.category.DEFAULT"
/
>
<
!
--
攔截
NFC標籤中存儲有以下
Uri前綴的
--
>
<
data
android
:
scheme
=
"http"
/
>
<
data
android
:
scheme
=
"https"
/
>
<
data
android
:
scheme
=
"ftp"
/
>
<
/
intent
-
filter
>
<
intent
-
filter
>
<
action
android
:
name
=
"android.nfc.action.NDEF_DISCOVERED"
/
>
<
category
android
:
name
=
"android.intent.category.DEFAULT"
/
>
<
!
--
定義可以攔截文本
--
>
<
data
android
:
mimeType
=
"text/plain"
/
>
<
/
intent
-
filter
>
<
/
activity
>
|
好了,接下來就可以進行讀寫NFC標籤中的Uri數據了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
public
class
ReadUriActivity
extends
BaseNfcActivity
{
private
TextView
mNfcText
;
private
String
mTagText
;
@
Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
activity_read_uri
)
;
mNfcText
=
(
TextView
)
findViewById
(
R
.
id
.
tv_nfctext
)
;
}
@
Override
public
void
onNewIntent
(
Intent
intent
)
{
//獲取Tag對象
Tag
detectedTag
=
intent
.
getParcelableExtra
(
NfcAdapter
.
EXTRA_TAG
)
;
//獲取Ndef的實例
Ndef
ndef
=
Ndef
.
get
(
detectedTag
)
;
mTagText
=
ndef
.
getType
(
)
+
"\n max size:"
+
ndef
.
getMaxSize
(
)
+
" bytes\n\n"
;
readNfcTag
(
intent
)
;
mNfcText
.
setText
(
mTagText
)
;
}
/**
* 讀取NFC標籤Uri
*/
private
void
readNfcTag
(
Intent
intent
)
{
if
(
NfcAdapter
.
ACTION_NDEF_DISCOVERED
.
equals
(
intent
.
getAction
(
)
)
)
{
Parcelable
[
]
rawMsgs
=
intent
.
getParcelableArrayExtra
(
NfcAdapter
.
EXTRA_NDEF_MESSAGES
)
;
NdefMessage
ndefMessage
=
null
;
int
contentSize
=
0
;
if
(
rawMsgs
!=
null
)
{
if
(
rawMsgs
.
length
>
0
)
{
ndefMessage
=
(
NdefMessage
)
rawMsgs
[
0
]
;
contentSize
=
ndefMessage
.
toByteArray
(
)
.
length
;
}
else
{
return
;
}
}
try
{
NdefRecord
ndefRecord
=
ndefMessage
.
getRecords
(
)
[
0
]
;
Log
.
i
(
"JAVA"
,
ndefRecord
.
toString
(
)
)
;
Uri
uri
=
parse
(
ndefRecord
)
;
Log
.
i
(
"JAVA"
,
"uri:"
+
uri
.
toString
(
)
)
;
mTagText
+=
uri
.
toString
(
)
+
"\n\nUri\n"
+
contentSize
+
" bytes"
;
}
catch
(
Exception
e
)
{
}
}
}
/**
* 解析NdefRecord中Uri數據
* @param record
* @return
*/
public
static
Uri
parse
(
NdefRecord
record
)
{
short
tnf
=
record
.
getTnf
(
)
;
if
(
tnf
==
NdefRecord
.
TNF_WELL_KNOWN
)
{
return
parseWellKnown
(
record
)
;
}
else
if
(
tnf
==
NdefRecord
.
TNF_ABSOLUTE_URI
)
{
return
parseAbsolute
(
record
)
;
}
throw
new
IllegalArgumentException
(
"Unknown TNF "
+
tnf
)
;
}
/**
* 處理絕對的Uri
* 沒有Uri識別碼,也就是沒有Uri前綴,存儲的全部是字符串
* @param ndefRecord 描述NDEF信息的一個信息段,一個NdefMessage可能包含一個或者多個NdefRecord
* @return
*/
private
static
Uri
parseAbsolute
(
NdefRecord
ndefRecord
)
{
//獲取所有的字節數據
byte
[
]
payload
=
ndefRecord
.
getPayload
(
)
;
Uri
uri
=
Uri
.
parse
(
new
String
(
payload
,
Charset
.
forName
(
"UTF-8"
)
)
)
;
return
uri
;
}
/**
* 處理已知類型的Uri
* @param ndefRecord
* @return
*/
private
static
Uri
parseWellKnown
(
NdefRecord
ndefRecord
)
{
//判斷數據是否是Uri類型的
if
(
!
Arrays
.
equals
(
ndefRecord
.
getType
(
)
,
NdefRecord
.
RTD_URI
)
)
return
null
;
//獲取所有的字節數據
byte
[
]
payload
=
ndefRecord
.
getPayload
(
)
;
String
prefix
=
UriPrefix
.
URI_PREFIX_MAP
.
get
(
payload
[
0
]
)
;
byte
[
]
prefixBytes
=
prefix
.
getBytes
(
Charset
.
forName
(
"UTF-8"
)
|