Demo下载

1.NFC的工作模式

NFC支持如下3种工作模式:读卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、点对点模式(P2P mode)。

下来分别看一下这三种模式:

(1)读卡器模式

数据在NFC芯片中,可以简单理解成“刷标签”。本质上就是通过支持NFC的手机或其它电子设备从带有NFC芯片的标签、贴纸、名片等媒介中读写信息。通常NFC标签是不需要外部供电的。当支持NFC的外设向NFC读写数据时,它会发送某种磁场,而这个磁场会自动的向NFC标签供电。

(2)仿真卡模式

数据在支持NFC的手机或其它电子设备中,可以简单理解成“刷手机”。本质上就是将支持NFC的手机或其它电子设备当成借记卡、公交卡、门禁卡等IC卡使用。基本原理是将相应IC卡中的信息凭证封装成数据包存储在支持NFC的外设中 。
在使用时还需要一个NFC射频器(相当于刷卡器)。将手机靠近NFC射频器,手机就会接收到NFC射频器发过来的信号,在通过一系列复杂的验证后,将IC卡的相应信息传入NFC射频器,最后这些IC卡数据会传入NFC射频器连接的电脑,并进行相应的处理(如电子转帐、开门等操作)。

(3)点对点模式

该模式与蓝牙、红外差不多,用于不同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时除外
通信模式 主动-主动/被动 主动-主动 主动-主动
成本

2.Android对NFC的支持

不同的NFC标签之间差异很大,有的只支持简单的读写操作,有时还会采用支持一次性写入的芯片,将NFC标签设计成只读的。当然,也存在一些复杂的NFC标签,例如,有一些NFC标签可以通过硬件加密的方式限制对某一区域的访问。还有一些标签自带操作环境,允许NFC设备与这些标签进行更复杂的交互。这些标签中的数据也会采用不同的格式。但Android SDK API主要支持NFC论坛标准(Forum Standard),这种标准被称为NDEF(NFC Data Exchange Format,NFC数据交换格式)。

NDEF格式其实就类似于硬盘的NTFS,下面我们看一下NDEF数据:

(1)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格式的数据,都需要这两个类。

(2)非NDEF数据的操作

对于某些特殊需求,可能要存任意的数据,对于这些数据,我们就需要自定义格式。这些数据格式实际上就是普通的字节流,至于字节流中的数据代表什么,就由开发人员自己定义了。

(3)编写NFC程序的基本步骤

1)设置权限,限制Android版本、安装的设备:

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

2)定义可接收Tag的Activity

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技术:

3.两个NFC标签的简单实例

1.利用NFC标签让Android自动运行程序

场景是这样的:现将应用程序的包写到NFC程序上,然后我们将NFC标签靠近Android手机,手机就会自动运行包所对应的程序,这个是NFC比较基本的一个应用。下面以贴近标签自动运行Android自带的“短信”为例。

向NFC标签写入数据一般分为三步:

1)获取Tag对象

1
Tag tag = intent . getParcelableExtra ( NfcAdapter . EXTRA_TAG ) ;

2)判断NFC标签的数据类型(通过Ndef.get方法)

1
Ndef ndef = Ndef . get ( tag ) ;

3)写入数据

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标签贴近手机背面,将会看到自动打开了“短信”。


下来再看一个有趣的例子:

2.利用NFC标签让Android自动打开网页

如何让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到底是个什么东西。

4.NDEF文本格式深度解析

获取NFC标签中的数据要通过 NdefRecord.getPayload 方法完成。当然,在处理这些数据之前,最好判断一下NdefRecord对象中存储的是不是NDEF文本格式数据。

(1)判断数据是否为NDEF格式

1)TNF(类型名格式,Type Name Format)必须是NdefRecord.TNF_WELL_KNOWN。
2)可变的长度类型必须是NdefRecord.RTD_TEXT。

如果这两个标准同时满足,那么就为NDEF格式。

(2)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.读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" / >

2.写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

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标签中的文本数据了。

5.NDEF Uri格式深度解析

与NDEF文本格式一样,存储在NFC标签中的Uri也有一定的格式,http://members.nfc-forum.org/specs/spec_dashboard

(1)Uri的格式规范要比文本格式简单一些:

Name 偏移 大小 描述
识别码 0 1byte Uri识别码 用于存储已知Uri的前缀
Uri字段 1 N UTF-8类型字符串 用于存储剩余字符串

(2)Uri的前缀如下(都是十六进制的一个数):

十进制 十六进制 协议 十进制 十六进制 协议
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: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:

每一个协议,都是用十六进制来存储于识别码位置(占1byte)。

是不是相对简单了些,那么下来我们来解析一个Uri。

(3)预先定义已知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 ) 0x07 , "ftp://anonymous:anonymous@" ) ;
         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.读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" ) ) ;
         byte [ ] fullUri = new byte [ prefixBytes . length + payload . length - 1 ] ;
         System . arraycopy ( prefixBytes , 0 , fullUri , 0 , prefixBytes . length ) ;
         System . arraycopy ( payload , 1 , fullUri , prefixBytes . length , payload . length - 1 ) ;
         Uri uri = Uri . parse ( new String ( fullUri , Charset . forName ( "UTF-8" ) ) ) ;
         return uri ;
     }
}

2.写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

public class WriteUriActivity extends BaseNfcActivity {
     private String mUri = "http://www.baidu.com" ;
     @ Override
     public void onCreate ( Bundle savedInstanceState ) {
         super . onCreate ( savedInstanceState ) ;
         setContentView ( R . layout . activity_write_uri ) ;
     }
     public void onNewIntent ( Intent intent ) {
         Tag detectedTag = intent . getParcelableExtra ( NfcAdapter . EXTRA_TAG ) ;
         NdefMessage ndefMessage = new NdefMessage ( new NdefRecord [ ] { createUriRecord ( mUri ) } ) ;
         boolean result = writeTag ( ndefMessage , detectedTag ) ;
         if ( result ) {
             Toast . makeText ( this , "写入成功" , Toast . LENGTH_SHORT ) . show ( ) ;
         } else {
             Toast . makeText ( this , "写入失败" , Toast . LENGTH_SHORT ) . show ( ) ;
         }
     }
     /**
     * 将Uri转成NdefRecord
     * @param uriStr
     * @return
     */
     public static NdefRecord createUriRecord ( String uriStr ) {
         byte prefix = 0 ;
         for ( Byte b : UriPrefix . URI_PREFIX_MAP . keySet ( ) ) {
             String prefixStr = UriPrefix . URI_PREFIX_MAP . get ( b ) . toLowerCase ( ) ;
             if ( "" . equals ( prefixStr ) )
                 continue ;
             if ( uriStr . toLowerCase ( ) . startsWith ( prefixStr ) ) {
                 prefix = b ;
                 uriStr = uriStr . substring ( prefixStr . length ( ) ) ;
                 break ;
             }
         }
         byte [ ] data = new byte [ 1 + uriStr . length ( ) ] ;
         data [ 0 ] = prefix ;
         System . arraycopy ( uriStr . getBytes ( ) , 0 , data , 1 , uriStr . length ( ) ) ;
         NdefRecord record = new NdefRecord ( NdefRecord . TNF_WELL_KNOWN , NdefRecord . RTD_URI , new byte [ 0 ] , data ) ;
         return record ;
     }
     /**
     * 写入标签
     * @param message
     * @param tag
     * @return
     */
     public static boolean writeTag ( NdefMessage message , Tag tag ) {
         int size = message . toByteArray ( ) . length ;
         try {
             Ndef ndef = Ndef . get ( tag ) ;
             if ( ndef != null ) {
                 ndef . connect ( ) ;
                 if ( ! ndef . isWritable ( ) ) {
                     return false ;
                 }
                 if ( ndef . getMaxSize ( ) < size ) {
                     return false ;
                 }
                 ndef . writeNdefMessage ( message ) ;
                 return true ;
             }
         } catch ( Exception e ) {
         }
         return false ;
     }
}

我们将手机贴近NFC标签,写入成功后验证一下是否成功写入:

我们看到,数据已经写入成功了,说明到此我们已经成功的读写NFC标签中的Uri数据了。

到这里,NDEF格式就大致说完了,那么接下来看一下非NDEF格式的数据。

6.非NDEF格式深度解析

1.MifareUltralight数据格式

将NFC标签的存储区域分为16个页,每一个页可以存储4个字节,一个可存储64个字节(512位)。页码从0开始(0至15)。前4页(0至3)存储了NFC标签相关的信息(如NFC标签的序列号、控制位等)。从第5页开始存储实际的数据(4至15页)。

使用MifareUltralight.get方法获取MifareUltralight对象,然后调用MifareUltralight.connect方法进行连接,并使用MifareUltralight.writePage方法每次写入1页(4个字节)。也可以使用MifareUltralight.readPages方法每次连续读取4页。如果读取的页的序号超过15,则从头开始读。例如,从第15页(序号为14)开始读。readPages方法会读取14、15、0、1页的数据。

2.读MifareUltralight格式数据

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

public class ReadMUActivity extends BaseNfcActivity {
     @ Override
     public void onCreate ( Bundle savedInstanceState ) {
         super . onCreate ( savedInstanceState ) ;
         setContentView ( R . layout . activity_read_mu ) ;
     }
     @ Override
     public void onNewIntent ( Intent intent ) {
         Tag tag = intent . getParcelableExtra ( NfcAdapter . EXTRA_TAG ) ;
         String [ ] techList = tag . getTechList ( ) ;
         boolean haveMifareUltralight = false ;
         for ( String tech : techList ) {
             if ( tech . indexOf ( "MifareUltralight" ) >= 0 ) {
                 haveMifareUltralight = true ;
                 break ;
             }
         }
         if ( ! haveMifareUltralight ) {
             Toast . makeText ( this , "不支持MifareUltralight数据格式" , Toast . LENGTH_SHORT ) . show ( ) ;
             return ;
         }
         String data = readTag ( tag ) ;
         if ( data != null )
             Toast . makeText ( this , data , Toast . LENGTH_SHORT ) . show ( ) ;
     }
     public String readTag ( Tag tag ) {
         MifareUltralight ultralight = MifareUltralight . get ( tag ) ;
         try {
             ultralight . connect ( ) ;
             byte [ ] data = ultralight . readPages ( 4 ) ;
             return new String ( data , Charset . forName ( "GB2312" ) ) ;
         } catch ( Exception e ) {
         } finally {
             try {
                 ultralight . close ( ) ;
             } catch ( Exception e ) {
             }
         }
         return null ;
     }
}

3.写MifareUltralight格式数据

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 WriteMUActivity extends BaseNfcActivity {
     @ Override
     public void onCreate ( Bundle savedInstanceState ) {
         super . onCreate ( savedInstanceState ) ;
         setContentView ( R . layout . activity_write_mu ) ;
     }
     @ Override
     public void onNewIntent ( Intent intent ) {
         Tag tag = intent . getParcelableExtra ( NfcAdapter . EXTRA_TAG ) ;
         String [ ] techList = tag . getTechList ( ) ;
         boolean haveMifareUltralight = false ;
         for ( String tech : techList ) {
             if ( tech . indexOf ( "MifareUltralight" ) >= 0 ) {
                 haveMifareUltralight = true ;
                 break ;
             }
         }
         if ( ! haveMifareUltralight ) {
             Toast . makeText ( this , "不支持MifareUltralight数据格式" , Toast . LENGTH_SHORT ) . show ( ) ;
             return ;
         }
         writeTag ( tag ) ;
     }
     public void writeTag ( Tag tag ) {
         MifareUltralight ultralight = MifareUltralight . get ( tag ) ;
         try {
             ultralight . connect ( ) ;
             //写入八个汉字,从第五页开始写,中文需要转换成GB2312格式
             ultralight . writePage ( 4 , "北京" . getBytes ( Charset . forName ( "GB2312" ) ) ) ;
             ultralight . writePage ( 5 , "上海" . getBytes ( Charset . forName ( "GB2312" ) ) ) ;
             ultralight . writePage ( 6 , "广州" . getBytes ( Charset . forName ( "GB2312" ) ) ) ;
             ultralight . writePage ( 7 , "天津" . getBytes ( Charset . forName ( "GB2312" ) ) ) ;
             Toast . makeText ( this , "写入成功" , Toast . LENGTH_SHORT ) . show ( ) ;
         } catch ( Exception e ) {
         } finally {
             try {
                 ultralight . close ( ) ;
             } catch ( Exception e ) {
             }
         }
     }
}

我们将手机贴近NFC标签,写入成功后验证一下是否成功写入:

我们看到,弹出了“北京上海广州天津”,说明数据已经写入成功了,说明到此我们已经成功的读写NFC非NDEF格式的数据了。

NFC标签开发深度解析到此就结束了!

转载链接: http://blog.csdn.net/smartbetter/article/details/53173217

安卓NFC标签读取快速开发教程(附源代码demo下载)相关推荐

  1. Android APP 快速开发教程(安卓)

    Android APP 快速开发教程(安卓) 前言 本篇博客从开发的角度来介绍如何开发一个Android App,需要说明一点是,这里只是提供一个如何开发一个app的思路,并不会介绍很多技术上的细节, ...

  2. Android移动端音视频的快速开发教程(五)

    接  Android移动端音视频的快速开发教程(四) 3.3. 数据传输事件接口 3.3.1. 接口定义 package com.bairuitech.anychat; // 数据传输通知接口 pub ...

  3. 字节跳动内部资料泄露?音视频开发教程(附面试题+视频教程),全文共301页,包含50个知识点

    前言 男怕入错行,女怕嫁错郎,程序员技术贬值快,只有紧紧跟上行业人才需求的风口才能不断让自己保值增值.唯有不断学习更新迭代自身技能的程序员才能一直在这个行业站稳脚跟. 为什么音视频如此重要? 伴随着短 ...

  4. 微信战狼计划之小程序开发教程 全集百度云下载

    微信战狼计划之小程序开发教程 全集百度云下载 小程序开发教程,需要的可以下载下来看看,

  5. java JFreechart开发报表的实例demo下载

    原文:java JFreechart开发报表的实例demo下载 源代码下载地址:http://www.zuidaima.com/share/1550463472110592.htm 运行效果图如下: ...

  6. Vue:uniapp实现NFC标签读取功能

    微信小程序官方文档中提供了调用NFC功能的基础库: NFC - wx.getNFCAdapter - <微信小程序官方开发文档(全) - 20210305> - 书栈网 · BookSta ...

  7. 一、iVX简介(IVX 快速开发教程)

    一.iVX简介 通过本节你将对 iVX 有一个大致的认识,并且了解 iVX 能够做些什么,有哪一些优势,这将帮助你更好的上手 iVX 进行应用的开发,初步了解 iVX 的强大之处. 文章目录 一.iV ...

  8. APP 快速开发教程(安卓)

    前言 本篇博客从开发的角度来介绍如何开发一个Android App,需要说明一点是,这里只是提供一个如何开发一个app的思路,并不会介绍很多技术上的细节,从整个大局去把握如何去构思一个app的开发,让 ...

  9. 七、功能性组件与事件逻辑(IVX 快速开发教程)

    七.功能性组件与事件逻辑 由于 iVX 极度易用的特性,在 iVX 中开发微信小程序.WebApp.小游戏应用的开发流程大致相同.介绍完基础可视化组件后通过后台的服务.数据库与事件结合即可完成一个应用 ...

  10. 六、WebApp 二手信息站点页面制作(IVX 快速开发教程)

    六.二手信息站点页面制作 在了解了基础可视组件后,我们可以通过这些可视组件进行站点页面开发,在此以一个二手交易网站站点页面为例,本教程示例并不是成熟完善的示例,需要各位读者进行少量完善,示例只是用于功 ...

最新文章

  1. cf776G.Sherlock and the Encrypted Data
  2. 开机报警disk boot sector is to be modified
  3. java 轻量级 web 框架,Fast-FrameWork
  4. linux centos密码忘记,CentOS忘记root密码的解决办法
  5. VxWorks关于任务创建的几个函数的概述
  6. 自定义字符串函数:strlen,strcat,strcpy,strcmp
  7. Timeline中让你事半功倍的工具链
  8. (一)时尚分类的深度学习
  9. (Photo Metadata Remover)Android App 一键去除照片 EXIF 隐私信息
  10. 【解题报告】SRM-08
  11. 基于决策树构建鸢尾花数据的分类模型并绘制决策树模型
  12. C语言实现贪吃蛇小游戏
  13. JAVA语言程序设计
  14. 简单使用WPE进行网页嗅探
  15. 微软走进云南为网吧提供特价正版软件
  16. 2022 年海峡两岸无线科学与技术会议
  17. opencv 绘制内切圆
  18. 数字签名与数字加密的区别
  19. 2017年的6个大数据发展趋势
  20. 基因家族的鉴定-基于Windows系统上的HMMER

热门文章

  1. AI时代,HR何以安身立命?
  2. Python实验七 文件的使用及图像处理
  3. 【京准小课堂】NTP网络时钟系统(子母钟系统)技术方案详解
  4. 放下烦恼,快乐其实很简单
  5. XMind ,熊掌记和 Effie 哪个更适合采编?
  6. Dry计算机术语,计算机英语 信息工程专业术语 31
  7. Error Code: 1064. You have an error in your SQL syntax;
  8. 机器人入门(三)—— ROS环境搭建与ROS Master、Node、Topic、Message
  9. NLP实战:中文文本分类-Pytorch实现
  10. Simulink第一个例子:Using IP Core Generation Workflow: LED Blinking