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"
)
|