有几天没有更新博客了,不过本篇却准备了许久,希望能带给每一位开发者最简单高效的学习方式。废话到此为止,下面开始正文。

NFC(Near Field Communication,近场通信)是一种数据传输技术。与Wi-Fi、蓝牙、红外线等数据传输技术的一个主要差异就是有效距离一般不能超过4厘米。但是NFC传输速度要比红外快。目前NFC已经出现了一些应用,例如电子标签识别、刷手机、点对点付款、身份识别、信息记录等,本篇文章的目的是为大家揭开NFC标签的面纱。

下面我们先从NFC的工作模式开始阐述NFC,开发NFC必先了解NFC。

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版本、安装的设备:

2)定义可接收Tag的Activity

Activity清单需要配置一下launchMode属性:

android:name=".TagTextActivity"

android:launchMode="singleTop"/>

而Activity中,我们也抽取了一个通用的BaseNfcActivity,如下(后面的Activity实现都继承于BaseNfcActivity):

/**

* 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对象

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

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

Ndef ndef = Ndef.get(tag);

3)写入数据

ndef.writeNdefMessage(ndefMessage);

详细实现代码如下:

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已经为我们提供好了这样的方法:

//直接接受一个Uri

public NdefRecord createUri(String uriString);

//接受一个Uri的对象

public NdefRecord createUri(Uri uri);

实现代码对比“3.利用NFC标签让Android自动运行程序”部分只是修改了writeNFCTag方法中

NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord

.createApplicationRecord(mPackageName)});

NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord

.createUri(Uri.parse(http://www.baidu.com))});

其余不变。

上面这个功能还是比较有用的,例如我们往某些商品上贴上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标签文本数据

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属性(后面一样要注意):

android:name=".ReadTextActivity"

android:launchMode="singleTop"/>

2.写NFC标签文本数据

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格式深度解析

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

Name

偏移

大小

描述

识别码

0

1byte

Uri识别码

用于存储已知Uri的前缀

Uri字段

1

N

UTF-8类型字符串

用于存储剩余字符串

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

十进制

十六进制

协议

十进制

十六进制

协议

0

0x00

N/A

1

0x01

2

0x02

3

0x03

http://

4

0x04

https://

5

0x05

tel:

8

0x08

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前缀:

public class UriPrefix {

public static final Map URI_PREFIX_MAP = new HashMap();

// 预先定义已知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的相关配置:

android:name=".ReadWriteUriActivity"

android:label="读写NFC标签的Uri"

android:launchMode="singleTop" >

好了,接下来就可以进行读写NFC标签中的Uri数据了:

1.读NFC标签中的Uri数据

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数据

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格式数据

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格式数据

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格式的数据了。

源码下载:demo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

android nfc读写demo,android nfc常用标签读取总结相关推荐

  1. android usb读写,安卓(Android)下如何开发USB NFC读写器app

    对安卓工程师来说,在安卓下使用USB设备需要了解很多硬件的内容,这可能会导致工程周期的延长或者app的不稳定.为了将这种风险降到最低,友我科技发布了NFC读写器在安卓下的sdk,使用NFC读写器的类接 ...

  2. android 谷歌定位demo,android实现定位与目的地的导航示例代码

    今天无意中看到技术大神利用百度地图定位并实现目的地导航的Demo.觉得很不错,就转载过来一起分享,下面我们看实现效果: 进入后首先会得到当前位置,在地图上显示出来,在输入框中输入目的地后,就会在地图上 ...

  3. android mvvm官方demo,Android MVVM实战Demo完全解析

    最新 [重大更新说明] 感谢各位读者的阅读,这篇文章由于年代久远,存在一些错误的观点,再次向大家道歉,也做一个技术上的解释说明. 受限于当时的技术水平和网络论调,在那个时代,网上绝大多数人 都认为加了 ...

  4. android bitmap 饱和度 demo,Android GPUImage实现多种图像滤镜效果

    前言 GPUImage 是iOS下一个开源的基于GPU的图像处理库,提供各种各样的图像处理滤镜,并且支持照相机和摄像机的实时滤镜.GPUImage for Android是它在Android下的实现, ...

  5. android 自定义progressbar demo,Android 自定义进度条ColorfulProgressbar,原理简单、效果还行...

    效果图: demo效果演示 演示Demo 特性 与原生Progress相比,感觉更漂亮一点,可以显示进度值,背景凹凸感明显,进度条效果更加立体. 原理说明 额,挺简单的.不过感觉我的做法有点复杂了,我 ...

  6. android自动更新demo,Android程序自动更新功能模块的实现方法【附完整demo源码下载】...

    本文实例讲述了Android程序自动更新功能模块的实现方法.分享给大家供大家参考,具体如下: 在程序启动的时候检测服务器上有没有对应版本更新,如果有更新,提示用户是否更新. 在程序启动的时候首先调用更 ...

  7. NFC读写(android代码)

    [实例简介] 可以实现NFC标签卡的读写操作 NFC写入工具,包括对NTAG213开启密码保护和关闭密码保护的功能 文件:url80.ctfile.com/f/25127180-740793371-6 ...

  8. android收藏功能demo,Android使用Realm数据库实现App中的收藏功能(代码详解)

    前 言 App数据持久化功能是每个App必不可少的功能,而Android最常用的数据持久化方式主要有以下的五种方式: 使用SharedPreferences存储数据: 文件存储数据: SQLite数据 ...

  9. android xml解析demo,Android解析自定义xml文件--Sax解析xml文件,测试demo(方案二)...

    转载请注明出处:http://blog.csdn.net/droyon/article/details/9346657 Sax解析xml 以下是测试Demo 运行程序类 public class Te ...

  10. android 键盘开发demo,Android自定义键盘之中文键盘demo

    [实例简介] Android自定义键盘之中文键盘demo,演示了汉字键盘的实现方法.更详细描述见相关博客. [实例截图] [核心代码] keydemo └── keydemo ├── AndroidM ...

最新文章

  1. IDEA新建springboot项目发生错误
  2. 遇见BUG(2)去掉你的增量编译使能!
  3. JS实现——俄罗斯方块
  4. 行高 line-height
  5. 【TweenMax】实例TimelineMax
  6. 可怕的DefaultAbstractHelperImpl
  7. 使用 HTML5 Canvas 绘制出惊艳的水滴效果
  8. Java笔记-JdbcTemplate批量执行insert及update
  9. Web上的支持的图片格式以及它们之间的区别
  10. 合并账号_亚马逊账号最新死法:合并listing和折扣促销
  11. 广东省东莞市谷歌卫星地图下载
  12. Windows游戏编程大师技巧(第2版)
  13. 16进制发送 mqtt客户端调试工具_MQTT客户端调试工具(MQTT Simulate Device)
  14. Linux下使用QQ
  15. 用javascript实现节假日自动切换风格
  16. 基于Node.js的ORM框架 Prisma的上手使用
  17. WPF开发经验-实现Win10虚拟触摸键盘
  18. Efficient Heterogeneous Collaborative Filtering without Negative Sampling for Recommendation (2020)
  19. 柠萌影业三闯IPO,“爆款制造机“更像盲盒?
  20. [Swift]LeetCode61. 旋转链表 | Rotate List

热门文章

  1. 微型计算机安装调试维修中级题库,调试维修工中级题库(答案).doc
  2. python 高斯烟羽模型_高斯扩散模型-高斯烟羽大气污染扩散模型
  3. UI Maker,界面设计sample
  4. Axure中推动拉动元件不生效_mac系统axure元件不能拖动的非正常解决方法
  5. mtk驱动sensor移植
  6. 工艺仿真Process Simulate新亮点
  7. [转]中国著名黑客你知道多少?
  8. Drool实战系列(一)之入门程序
  9. java newtonsoft.json_(转载)Newtonsoft.Json使用总结
  10. C语言易错知识点总结