NFC技术介绍及代码示例

最近项目中需要使用NFC(近场通信),在网上找了一些资料,摘抄汇总如下。

近距离无线通信(NFC)是一种简单的,非触控式的互联技术,可以让消费者简单直观的交换信息,访问内容和服务,在电子消费领域有着广泛的应用。NFC整合非接触式读卡器、非接触式智能卡和点对点(Peer-to-Peer)通信功能,为消费者开创全新便捷生活方式。

NFC的技术优势?

与蓝牙相比:NFC操作简单,配对迅速

与RFID相比:NFC适用范围广泛、可读可写,能直接集成在手机中

与红外线相比:数据传输较快、安全性高、能耗低

与二维码相比:识别迅速、信息类型多样

将来与移动支付相结合,势必简化支付的购买流程,重塑消费者的购物模式。

    1.NFC技术

NFC终端有三种工作模式:
1)主动模式,NFC终端作为一个读卡器,主动发出自己的射频场去识别和读/写别的NFC设备;
2)被动模式,NFC终端可以模拟成一个智能卡被读写,它只能在其它设备发出的射频场中被动响应;
3)双向模式,双方都主动发出射频场来建立点对点的通信。

NFC 在android架构中采用Service和Manager基本结构模型,通过Binder和Service通信,如图一所示android基于Binder的IPC的基本模型是基于会话的客户/服务器(C/S)架构的。Android使用内核模块Binder来中转各个进程之间的会话数据,它是一个字符驱动程序,主要通过IOCTL与用户空间的进程交换数据。一次会话是在一个代理Binder对象和服务Binder对象之间进行,可以在同一进程也可以在不同进程。会话是一个同步操作,由代理Binder对象发起请求,一直要等到服务Binder对象将回复传递给代理Binder对象才算完成。

NFC架构

 

2.Android所支持的NFC标签技术

在使用NFC标签和Android设备来进行工作的时候,使用的读写NFC标签上数据的主要格式是NDEF。当设备扫描到带有NDEF的数据时,Android会提供对消息解析的支持,并在可能的时候,会以NdefMessage对象的形式来发送它。但是,有些情况下,设备扫描到的NFC标签没有包含NDEF数据,或者该NDEF数据没有被映射到MIME类型或URI。在这些情况下,你需要打开跟NFC标签的通信,并用自己的协议(原始的字节形式)来读写它。Android用android.nfc.tech包提供了对这些情况的一般性支持,这个包在下表1中介绍。你能够使用getTechList()方法来判断NFC标签所支持的的技术,并且用android.nfc.tech提供的一个类来创建对应的TagTechnology对象。

 

表1.NFC标签所支持的技术

介绍

TagTechnology 所有的NFC标签技术类必须实现的接口。
NfcA 提供对NFC-A(ISO 14443-3A)属性和I/O操作的访问。
NfcB 提供对NFC-B(ISO 14443-3B)属性和I/O操作的访问。
NfcF 提供对NFC-F(ISO 6319-4)属性和I/O操作的访问。
NfcV 提供对NFC-V(ISO 15693)属性和I/O操作的访问。
IsoDep 提供对NFC-A(ISO 14443-4)属性和I/O操作的访问。
Ndef 提供对NDEF格式的NFC标签上的NDEF数据和操作的访问。
NdefFormatable 提供了对可以被NDEF格式化的NFC标签的格式化操作。

表2.可选的NFC标签所支持的技术

介绍

MifareClassic 如果Android设备支持MIFARE,那么它提供了对经典的MIFARE类型标签属性和I/O操作的访问。
MifareUltralight 如果Android设备支持MIFARE,那么它提供了对超薄的MIFARE类型标签属性和I/O操作的访问。

 

 

3.标签识别过程

在标签识别开始前,确认NFC设备使用正常,可获取NDEF设备。NFC HAL探测到有效距离范围内有标签存在,则读取数据,向NFC Service发送标签识别事件,NFC Service 广播NfcAdapter.ACTION_TAG_DISCOVERED Intent消息,应用程序通过接受该消息即可获取标签数据。 readTag

4.Tag发布系统

当android设备扫描到一个NFC tag,通用的行为是自动找最合适的Activity会处理这个tag Intent而不需要用户来选择哪个Activity来处理。因为设备扫描NFC tags是在很短的范围和时间,如果让用户选择的话,那就有可能需要移动设备,这样将会打断这个扫描过程。你应该开发你只处理需要处理的tags的Activity,以防止让用户选择使用哪个Activity来处理的情况。Android提供两个系统来帮助你正确的识别一个NFC tag是否是你的Activity想要处理的:Intent发布系统和前台Activity发布系统。

Intent发布系统检查所有Activities的intent filters,找出那些定义了可以处理此tag的Activity,如果有多个Activity都配置了处理同一个tag Intent,那么将使用Activity选择器来让用户选择使用哪个Activity。用户选择之后,将使用选择的Activity来处理此Intent.

前台发布系统允许一个Activity覆盖掉Intent发布系统而首先处理此tag Intent,这要求你将要处理Tag Intent的Activity运行在前台,这样当一个NFC tag被扫描到,系统先检测前台的Activity是否支持处理此Intent,如果支持,即将此Intent传给此Activity,如果不支持,则转到Intent发布系统。

使用Intent发布系统

Intent发布系统指定了3个intent有不同的优先级。通常当一个tag被检测到之后,Intent就被启动(start)了,这个启动遵循以下行为:

  • android.nfc.action.NDEF_DISCOVERED: 这个intent是在一个包含NDEF负载的tag被检测到时启动,这是最高优先级的intent, android系统不会让你指定一个Intent能处理所有的NFC数据类型,你必须在AndroidManifest.xml中指定与NFC tag对应的<data>元素,这样当扫描到的tag传过来的数据类型与你定义的相匹配时,你的Activity就会被调用。例如想处理一个包含plain text 的 NDEF_DISCOVERED intent ,你要按照如下定义AndroidManifest.xml file:

<intent-filter>
<action android:name=”android.nfc.action.NDEF_DISCOVERED”/>
<data android:mimeType=”text/plain” />
</intent-filter>

如果NDEF_DISCOVERED intent 已经被启动,TECH_DISCOVERED 和 TAG_DISCOVERED intents 将不会被启动。假如一个未知的tag或者不包含NDEF负载的tag被检测到,此Intent就不会被启动。

  • android.nfc.action.TECH_DISCOVERED: 如果 NDEF_DISCOVERED intent没启动或者没有一个Activity的filter检测NDEF_DISCOVERED ,并且此tag是已知的,那么此TECH_DISCOVERED Intent将会启动. TECH_DISCOVERED intent要求你在一个资源文件里(xml)里指定你要支持technologies列表。更多细节请看下面的Specifying tag technologies to handle.
  • android.nfc.action.TAG_DISCOVERED: 如果没有一个activity处理_DISCOVERED and TECH_DISCOVEREDintents或者tag被检测为未知的,那么此Intent将会被启动。

Specifying tag technologies to handle指定处理的technologies

假如你的Activity在AndroidManifest.xml文件里声明了处理android.nfc.action.TECH_DISCOVERED intent ,你必须创建一个Xml格式的资源文件,并加上你的activity支持的technologies到tech-list集合里。这样你的activity将被认作能处理这些tech-list的处理者,如果tag使用的technology属于你的定义的list里,你的Activity将接收此Intent。你可以用getTechList()来获得tag支持的technologies。

例如:如果一个tag被检测到支持MifareClassic, NdefFormatable, 和 NfcA,你的tech-list集合必须指定了其中的一项或者多项来保证你的Activity能处理此Intent。

下面是一个资源文件例子,定义了所有的technologies. 你可以根据需要删掉不需要的项,将此文件以任意名字+.xml保存到<project-root>/res/xml文件夹.

<resources xmlns:xliff=”urn:oasis:names:tc:xliff:document:1.2″>
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.NfcF</tech>
<tech>android.nfc.tech.NfcV</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NdefFormatable</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>

你也可以指定多个tech-list集合,每个集合都认做独立的。如果任何单个tech-list集合是getTechList()返回的technologies集合的子集,那么你的Activity将被认为匹配了。这个还提供’与’和’或’操作。下面的例子表示支持 NfcA和NDef的卡,或者支持NfcB和NDef的卡:

<resources xmlns:xliff=”urn:oasis:names:tc:xliff:document:1.2″>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>

<resources xmlns:xliff=”urn:oasis:names:tc:xliff:document:1.2″>
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>

在 AndroidManifest.xml 文件中, 指定这个tech-list资源文件的方法是在<activity> 元素中创建<meta-data>元素,例如下面例子:

<activity>

<intent-filter>
<action android:name=”android.nfc.action.TECH_DISCOVERED”/>
</intent-filter>

<meta-data android:name=”android.nfc.action.TECH_DISCOVERED”
android:resource=”@xml/nfc_tech_filter” />

</activity>

 

使用前台发布系统Using the foreground dispatch system

前台发布系统允许一个Activity 拦截一个tag Intent 获得最高优先级的处理,这种方式很容易使用和实现:

  1. 添加下列代码到Activity的onCreate() 方法里
  2. 创建一个 PendingIntent 对象, 这样Android系统就能在一个tag被检测到时定位到这个对象

PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, new Intent(this,getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

  1. 在Intent filters里声明你想要处理的Intent,一个tag被检测到时先检查前台发布系统,如果前台Activity符合Intent filter的要求,那么前台的Activity的将处理此Intent。如果不符合,前台发布系统将Intent转到Intent发布系统。如果指定了null的Intent filters,当任意tag被检测到时,你将收到TAG_DISCOVERED intent。因此请注意你应该只处理你想要的Intent。

IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType(“*/*”);    /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
}
catch (MalformedMimeTypeException e) {
throw new RuntimeException(“fail”, e);
}
intentFiltersArray = new IntentFilter[] {
ndef,
};

  1. 设置一个你程序要处理的Tag technologies的列表,调用Object.class.getName() 方法来获得你想要支持处理的technology类。

techListsArray = new String[][] { new String[] { NfcF.class.getName() } };

  1. 覆盖下面的方法来打开或关闭前台发布系统。比如onPause()和onResume()方法。必须在主线程里调用enableForegroundDispatch(Activity, PendingIntent, IntentFilter[], String[][]) 而且Activity在前台(可以在onResume()里调用来保证这点)。你也要覆盖onNewIntent回调来处理得到的NFC tag数据。

public void onPause() {
super.onPause();
mAdapter.disableForegroundDispatch(this);
}

public void onResume() {
super.onResume();
mAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray,techListsArray);
}

public void onNewIntent(Intent intent) {
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent
}

See the ForegroundDispatch sample from API Demos for the complete sample.

5.读一个NFC tag

当一个NFC tag靠近一个NFC设备,一个相应的Intent将在设备上被创建。然后通知合适的程序来处理此Intent
下面的方法处理TAG_DISCOVERED intent并且使用迭代器来获得包含在NDEF tag负载的数据

NdefMessage[] getNdefMessages(Intent intent) {
// Parse the intent
NdefMessage[] msgs = null;
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
Parcelable[] rawMsgs =intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
}
}
else {
// Unknown tag type
byte[] empty = new byte[] {};
NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty,empty);
NdefMessage msg = new NdefMessage(new NdefRecord[] {record});
msgs = new NdefMessage[] {msg};
}
}
else {
Log.e(TAG, “Unknown intent ” + intent);
finish();
}
return msgs;
}

请记住NFC设备读到的数据是byte类型,所以你可能需要将他转成其他格式来呈现给用户。NFCDemo例子展示了怎样用com.example.android.nfc.record中的类来解析NDEF消息,比如纯文本和智慧型海报。

6.NFC tag

NFC tag写东西涉及到构造一个NDEF 消息和使用与tag匹配的Tag技术。下面的代码展示怎样写一个简单的文本到NdefFormatable tag

NdefFormatable tag = NdefFormatable.get(t);
Locale locale = Locale.US;
final byte[] langBytes = locale.getLanguage().getBytes(Charsets.US_ASCII);
String text = “Tag, you’re it!”;
final byte[] textBytes = text.getBytes(Charsets.UTF_8);
final int utfBit = 0;
final char status = (char) (utfBit + langBytes.length);
final byte[] data = Bytes.concat(new byte[] {(byte) status}, langBytes, textBytes);
NdefRecord record = NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, newbyte[0], data);
try {
NdefRecord[] records = {text};
NdefMessage message = new NdefMessage(records);
tag.connect();
tag.format(message);
}
catch (Exception e){
//do error handling
}

7.点对点的数据交换

前台推送技术支持简单点对点的数据交换,你可以用enableForegroundNdefPush(Activity, NdefMessage) 方法来打开此功能. 为了用这个功能:

  • 推送数据的Activity必须是前台Activity。
  • 你必须将你要发送的数据封装到NdefMessage对象里。
  • 接收推送数据的设备必须支持com.android.npp NDEF推送协议,这个对于Android设备是可选的

假如你的Activity打开了前台推送功能并且位于前台,这时标准的Intent发布系统是禁止的。然而,如果你的Activity允许前台发布系统,那么此时检测tag的功能仍然是可用的,不过只适用于前台发布系统。

要打开前台推送:

  1. 创建一个你要推送给其他NFC设备的包含NdefRecords的NdefMessage。
  2. 在你的Activity里实现onResume()onPause() 的回调来正确处理前台推送的生命周期。你必须在你的Activity位于前台并在主线程里调用enableForegroundNdefPush(Activity, NdefMessage) (可以在onResume()里调用来保证这点).

public void onResume() {
super.onResume();
if (mAdapter != null)
mAdapter.enableForegroundNdefPush(this, myNdefMessage);
}
public void onPause() {
super.onPause();
if (mAdapter != null)
mAdapter.disableForegroundNdefPush(this);
}

当Activity位于前台,你可以靠近另外一个NFC设备来推送数据。请参考例子ForegroundNdefPush来了解点对点数据交换。

8.代码示例

首先要在AndroidManifest.xml中声明如下配置信息:

使用<uses-permission>元素允许设备访问NFC硬件:

<uses-permission android:name=“android.permission.NFC” />  

使用<uses-sdk>元素设置最小SDK版本,笔者基于android 4.0环境,因此声明如下:

<uses-sdk android:minSdkVersion=”14″ android:targetSdkVersion=”14″ />

下面这项不一定需要,如果你希望你的软件可以在android market中显示有NFC硬件,可以使用<uses-feature>元素声明:

<uses-feature android:name=”android.hardware.nfc” android:required=”true” />

让一个Activity“监听”扫描NFC标签时的事件:

下面这个是最高级别的,扫描后会立马弹出此Activity:

<intent-filter>
<action android:name=”android.nfc.action.NDEF_DISCOVERED” ></action>
<category android:name=”android.intent.category.DEFAULT” ></category>
<data android:mimeType=”text/plain” ></data>
</intent-filter>

也可以通过以下方式监听:

<intent-filter>
<action android:name=”android.nfc.action.TECH_DISCOVERED” />
<action android:name=”android.nfc.action.TAG_DISCOVERED” />
<category android:name=”android.intent.category.DEFAULT”/>
</intent-filter>
<meta-data
android:name=”android.nfc.action.TECH_DISCOVERED”
android:resource=”@xml/nfc_tech_filter” />

在activity中初始化adapter:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc);
nfcTView=(TextView)findViewById(R.id.nfc_textview);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
// nfcTView.setText(“设备不支持NFC!”);
Toast.makeText(this, “设备不支持NFC!”, Toast.LENGTH_LONG).show();
finish();
return;
}
if (nfcAdapter!=null&&!nfcAdapter.isEnabled()) {
// nfcTView.setText(“请在系统设置中先启用NFC功能!”);
Toast.makeText(this, “请在系统设置中先启用NFC功能!”,Toast.LENGTH_LONG).show();
finish();
return;
}
}

@Override
protected void onResume() {
super.onResume();
NdefMessage[] mesArr = NFCUtils.getNdefMessages(getIntent());
if (NFCUtils.isNFCEntry(mesArr)) {
NFCUtils.sendNFCBroadcast(this,NFCConsts.NFC_OPERATE_TYPE_ENTRY);
}else if(NFCUtils.isNFCExit(mesArr)) {
NFCUtils.sendNFCBroadcast(this,NFCConsts.NFC_OPERATE_TYPE_EXIT);
}
finish();
}

下面开始解析:

public static NdefMessage[] getNdefMessages(Intent intent) {
// Parse the intent
NdefMessage[] msgs = null;
String action = intent.getAction();
//目前只监听ACTION_NDEF_DISCOVERED的action,NfcAdapter.ACTION_TAG_DISCOVERED先不管
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
}
}
else {
// Unknown tag type
byte[] empty = new byte[] {};
NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
NdefMessage msg = new NdefMessage(new NdefRecord[] {record});
msgs = new NdefMessage[] {msg};
}
}
return msgs;
}

private static String parseNdefRecord(NdefRecord r) {
byte[] payload = r.getPayload();
try {
String textEncoding = ((payload[0] & 0200) == 0) ? “UTF-8” : “UTF-16”;
int languageCodeLength = payload[0] & 0077;
// String languageCode = new String(payload, 1, languageCodeLength, “US-ASCII”);
String text =new String(payload, languageCodeLength + 1,payload.length – languageCodeLength – 1, textEncoding);
return text;
} catch (UnsupportedEncodingException e) {
// should never happen unless we get a malformed tag.
//throw new IllegalArgumentException(e);
LogUtils.e(TAG, “NFC读取不支持此编码格式”, e);
}
return StringUtils.EMPTY;
}

 

Android NFC Links

基于Android平台的NFC技术的应用实现:http://www.ctiforum.com/html/tougaozhuanlan/zuixinlaigao/349587.html

Android系统上十款最佳NFC标签应用:http://mobile.163.com/13/0913/08/98L0D7RG0011671M.html

笔记之NFC近距离无线通讯技术(Dean):http://yelinsen.iteye.com/blog/1018678

Android NFC 开发实例:http://blog.csdn.net/pku_android/article/details/7430788

APP nfcard源码:https://code.google.com/p/nfcard/source/checkout

NFC Demo及有用链接:http://download.csdn.net/detail/xxhjd/5214162

怎样使用NFC:http://bbs.xiaomi.cn/thread-8477165-1-1.html

                         http://blog.csdn.net/forlong401/article/details/8229784
android平台上读写NFC标签代码:http://blog.csdn.net/yeruby/article/details/9287337