本文档介绍了在Android中执行的基本NFC任务。 它解释了如何以NDEF消息的形式发送和接收NFC数据,并描述了支持这些功能的Android框架API。 有关更高级的主题,包括讨论使用非NDEF数据的讨论,请参阅高级NFC。
使用NDEF数据和Android时,有两个主要的用例:
1、从NFC标签读取NDEF数据。
2、使用Android Beam™将NDEF消息从一个设备发送到另一个设备
从NFC标签读取NDEF数据由标签调度系统处理,标签调度系统分析发现的NFC标签,对数据进行适当分类,并启动对分类数据感兴趣的应用程序。 想要处理扫描的NFC标签的应用程序可以声明一个意图过滤器并请求处理数据。
Android Beam™功能允许设备将NDEF消息推送到另一个设备上,同时通过物理轻敲设备。 这种交互提供了一种比其他无线技术(如蓝牙)更容易发送数据的方式,因为使用NFC,无需手动设备发现或配对。 当两个设备进入范围时,连接自动启动。 Android Beam可通过一组NFC API获得,因此任何应用程序都可以在设备之间传输信息。 例如,联系人,浏览器和YouTube应用程序使用Android Beam与其他设备共享联系人,网页和视频。
一、标签调度系统
除非在设备的“设置”菜单中禁用NFC,否则Android设备通常会在屏幕解锁时查找NFC标签。 当Android设备发现NFC标签时,所需的行为是让最合适的活动处理意图,而不要求用户使用什么应用程序。 由于设备会在非常短的范围内扫描NFC标签,所以很可能使用户手动选择一个活动会强制他们将设备从标签移开,并断开连接。 您应该开发您的活动,只处理您的活动关心的NFC标签,以防止活动选择器出现。
为了帮助您实现这一目标,Android提供了一个特殊的标签调度系统,可分析扫描的NFC标签,解析它们,并尝试查找对扫描数据感兴趣的应用程序。 它做到这一点:
1、解析NFC标签,找出MIME类型或标识标签中数据有效内容的URI。
2、将MIME类型或URI和有效内容封装成意图。 NFC标签如何映射到MIME类型和URI中描述了前两个步骤。
3、根据意图启动活动。 这在NFC标签如何分派到应用程序中有所描述。
一)、NFC标签如何映射到MIME类型和URI
在开始编写NFC应用程序之前,了解NFC标签的不同类型,标签分发系统如何解析NFC标签以及标签分发系统在检测到NDEF消息时所做的特殊工作非常重要。 NFC标签具有广泛的技术,并且还可以以许多不同的方式将数据写入它们。 Android最受支持的是NDEF标准,由NFC论坛定义。
NDEF数据封装在包含一个或多个记录(NdefRecord)的消息(NdefMessage)内。 每个NDEF记录必须根据您要创建的记录类型的规范形成。 Android还支持不包含NDEF数据的其他类型的标签,您可以使用android.nfc.tech包中的类来处理。 要了解有关这些技术的更多信息,请参阅Advanced NFC主题。 使用这些其他类型的标签涉及编写自己的协议栈以与标签进行通信,因此我们建议您尽可能使用NDEF进行开发,并最大限度地支持Android设备。
注意:要下载完整的NDEF规范,请访问NFC论坛规范下载站点,并参阅创建NDEF记录的常见类型,以获取如何构建NDEF记录的示例。
现在您在NFC标签中有一些背景,以下部分将更详细地介绍Android如何处理NDEF格式的标签。 当Android驱动的设备扫描包含NDEF格式化数据的NFC标签时,它将解析该消息,并尝试找出数据的MIME类型或标识URI。 为此,系统读取NdefMessage内的第一个NdefRecord,以确定如何解释整个NDEF消息(NDEF消息可以有多个NDEF记录)。 在一个格式良好的NDEF消息中,第一个NdefRecord包含以下字段:
1、3位TNF(类型名称格式)
指示如何解释可变长度类型字段。 有效值如表1所述。
2、可变长度类型
描述记录的类型。 如果使用TNF_WELL_KNOWN,请使用此字段指定记录类型定义(RTD)。 有效的RTD值在表2中描述。
3、可变长度ID
记录的唯一标识符。 此字段不经常使用,但如果您需要唯一标识标签,您可以为其创建一个ID。
4、可变长度有效载荷
您要读取或写入的实际数据有效负载。 NDEF消息可以包含多个NDEF记录,因此不要将全部有效载荷置于NDEF消息的第一个NDEF记录中。
标签分发系统使用TNF和类型字段来尝试将MIME类型或URI映射到NDEF消息。 如果成功,它会将该信息与实际有效载荷一起封装在ACTION_NDEF_DISCOVERED意图内。 但是,有时标签分派系统无法根据第一个NDEF记录确定数据的类型。 当NDEF数据无法映射到MIME类型或URI时,或者当NFC标签不包含NDEF数据时,会发生这种情况。 在这种情况下,具有关于标签技术和有效载荷的信息的Tag对象被封装在ACTION_TECH_DISCOVERED意图内。
表1描述了标签分发系统如何将TNF和类型字段映射到MIME类型或URI。 它还描述了哪些TNF不能映射到MIME类型或URI。 在这种情况下,标签调度系统将恢复为ACTION_TECH_DISCOVERED。
例如,如果标签分发系统遇到类型为TNF_ABSOLUTE_URI的记录,则会将该记录的可变长度类型字段映射为URI。 标签调度系统将该URI封装在ACTION_NDEF_DISCOVERED意图的数据字段中以及关于该标签的其他信息,例如有效载荷。 另一方面,如果遇到类型为TNF_UNKNOWN的记录,则会创建一种封装标签技术的意图。
表1.支持的TNF及其映射
Type Name Format (TNF) | Mapping |
---|---|
TNF_ABSOLUTE_URI |
基于类型字段的URI。 |
TNF_EMPTY |
Falls back to ACTION_TECH_DISCOVERED . |
TNF_EXTERNAL_TYPE |
URI based on the URN in the type field. The URN is encoded into the NDEF type field in a shortened form: . Android maps this to a URI in the form: vnd.android.nfc://ext/ . |
TNF_MIME_MEDIA |
MIME type based on the type field. |
TNF_UNCHANGED |
Invalid in the first record, so falls back to ACTION_TECH_DISCOVERED . |
TNF_UNKNOWN |
Falls back to ACTION_TECH_DISCOVERED . |
TNF_WELL_KNOWN |
MIME type or URI depending on the Record Type Definition (RTD), which you set in the type field. See Table 2 for more information on available RTDs and their mappings. |
表2.支持的TNF_WELL_KNOWN的RTD及其映射
Record Type Definition (RTD) | Mapping |
---|---|
RTD_ALTERNATIVE_CARRIER |
Falls back to ACTION_TECH_DISCOVERED . |
RTD_HANDOVER_CARRIER |
Falls back to ACTION_TECH_DISCOVERED . |
RTD_HANDOVER_REQUEST |
Falls back to ACTION_TECH_DISCOVERED . |
RTD_HANDOVER_SELECT |
Falls back to ACTION_TECH_DISCOVERED . |
RTD_SMART_POSTER |
URI based on parsing the payload. |
RTD_TEXT |
MIME type of text/plain . |
RTD_URI |
URI based on payload. |
二)、NFC标签如何分派到应用程序
当标签调度系统完成创建封装NFC标签及其识别信息的意图时,它将意图发送给过滤意图的感兴趣的应用程序。 如果多个应用程序可以处理意图,则会显示活动选择器,以便用户可以选择活动。 标签调度系统定义了三个意图,按照从最高到最低优先级的顺序列出:
1、ACTION_NDEF_DISCOVERED:当扫描包含NDEF有效负载的标签并且是公认类型时,此意图用于启动“活动”。 这是最高优先级的意图,标签发送系统尽可能尝试在任何其他意图之前以此意图启动活动。
2、ACTION_TECH_DISCOVERED:如果没有活动注册以处理ACTION_NDEF_DISCOVERED意图,则标记分发系统尝试以此意图启动应用程序。 如果扫描的标签包含无法映射到MIME类型或URI的NDEF数据,或者标签不包含NDEF数据但是已知标签技术,则此意图也将直接启动(首先不启动ACTION_NDEF_DISCOVERED)。
3、ACTION_TAG_DISCOVERED:如果没有活动处理ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED意图,则此意图启动。
标签发送系统的基本工作方式如下:
1、尝试在解析NFC标签(ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED)时由标记分派系统创建的意图启动活动。
2、如果没有为此意图过滤活动,请尝试启动具有下一个最低优先级意图(ACTION_TECH_DISCOVERED或ACTION_TAG_DISCOVERED)的活动,直到应用程序过滤意图或直到标签分派系统尝试所有可能的意图。
3、如果没有应用程序过滤任何意图,不做任何事情。
图1.标签调度系统
只要有可能,使用NDEF消息和ACTION_NDEF_DISCOVERED意图,因为它是三分之一中最具体的。 这个意图允许您在比其他两个意图更合适的时间启动应用程序,为用户提供更好的体验。
二、在Android清单中请求NFC访问
在您可以访问设备的NFC硬件并正确处理NFC意图之前,请在AndroidManifest.xml文件中声明这些项目:
1、NFC
三、过滤NFC意图
要扫描您要处理的NFC标签时启动应用程序,您的应用程序可以过滤Android清单中的一个,两个或所有三个NFC意图。 但是,您通常希望过滤ACTION_NDEF_DISCOVERED意图,以便您的应用程序启动时的最多控制。 ACTION_TECH_DISCOVERED意图是针对ACTION_NDEF_DISCOVERED的ACTION_NDEF_DISCOVERED的回退,当ACTION_NDEF_DISCOVERED没有应用程序过滤时,或者当有效负载不是NDEF时。 ACTION_TAG_DISCOVERED的过滤通常是过滤的类别。 许多应用程序将在ACTION_TAG_DISCOVERED之前过滤ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED,因此您的应用程序的启动概率很低。 ACTION_TAG_DISCOVERED仅适用于在没有其他应用程序安装以处理ACTION_NDEF_DISCOVERED或ACTION_TECH_DISCOVERED意图的情况下过滤的应用程序。
由于NFC标签的部署不同,因此许多时间不在您的控制之下,这并不总是可能的,这就是为什么在必要时可以追溯到其他两个意图。 当您控制所写入的标签和数据类型时,建议您使用NDEF格式化标签。 以下部分介绍如何对每种类型的意图进行过滤。
一)、ACTION_NDEF_DISCOVERED
要过滤ACTION_NDEF_DISCOVERED意图,请声明intent过滤器以及要过滤的数据类型。 以下示例使用MIME类型为text / plain过滤ACTION_NDEF_DISCOVERED意图:
二)、ACTION_TECH_DISCOVERED
如果您的活动过滤ACTION_TECH_DISCOVERED意图,则必须创建一个XML资源文件,该文件指定您的活动在技术列表集中支持的技术。 如果技术列表集是标记支持的技术的一部分,则您的活动被认为是匹配的,您可以通过调用getTechList()获取。
例如,如果扫描的标签支持MifareClassic,NdefFormatable和NfcA,则您的技术列表集必须指定所有三种,两种或一种技术(而不是其他技术),以使您的活动得以匹配。
以下示例定义了所有技术。 您可以删除不需要的那些。 在
android.nfc.tech.IsoDep
android.nfc.tech.NfcA
android.nfc.tech.NfcB
android.nfc.tech.NfcF
android.nfc.tech.NfcV
android.nfc.tech.Ndef
android.nfc.tech.NdefFormatable
android.nfc.tech.MifareClassic
android.nfc.tech.MifareUltralight
android.nfc.tech.NfcA
android.nfc.tech.Ndef
android.nfc.tech.NfcB
android.nfc.tech.Ndef
...
...
三)、ACTION_TAG_DISCOVERED
要过滤ACTION_TAG_DISCOVERED,请使用以下意图过滤器:
如果由于NFC意图开始活动,则可以从意图获取有关扫描的NFC标签的信息。 意图可以包含以下附件,具体取决于扫描的标签:
1)、EXTRA_TAG(必需):表示扫描标签的标签对象。
2)、EXTRA_NDEF_MESSAGES(可选):从标签解析的NDEF消息数组。 ACTION_NDEF_DISCOVERED意图是强制性的。
3)、EXTRA_ID(可选):标签的低级ID。
要获得这些附加功能,请检查您的活动是否与其中一个NFC意图一起启动,以确保标记已被扫描,然后从意图中获取附加组件。 以下示例检查ACTION_NDEF_DISCOVERED意图,并从意图额外获取NDEF消息。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
...
if (intent != null && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
Parcelable[] rawMessages =
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMessages != null) {
NdefMessage[] messages = new NdefMessage[rawMessages.length];
for (int i = 0; i < rawMessages.length; i++) {
messages[i] = (NdefMessage) rawMessages[i];
}
// Process the messages array.
...
}
}
}
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
四、创建NDEF记录的常见类型
本节介绍如何创建常见类型的NDEF记录,以帮助您在写入NFC标签或使用Android Beam发送数据时。 从Android 4.0(API级别14)开始,createUri()方法可用于帮助您自动创建URI记录。 从Android 4.1(API级别16)开始,createExternal()和createMime()可用于帮助您创建MIME和外部类型的NDEF记录。 尽可能使用这些帮助方法,以便在手动创建NDEF记录时避免错误。
本节还介绍如何为记录创建相应的意图过滤器。 所有这些NDEF记录示例都应该在您写入标签或发送的NDEF消息的第一个NDEF记录中。
一)、TNF_ABSOLUTE_URI
注意:我们建议您使用RTD_URI类型而不是TNF_ABSOLUTE_URI,因为它更有效率。
您可以通过以下方式创建一个TNF_ABSOLUTE_URI NDEF记录:
NdefRecord uriRecord = new NdefRecord(
NdefRecord.TNF_ABSOLUTE_URI ,
"http://developer.android.com/index.html".getBytes(Charset.forName("US-ASCII")),
new byte[0], new byte[0]);
二)、TNF_MIME_MEDIA
您可以通过以下方式创建一个TNF_MIME_MEDIA NDEF记录:
使用createMime()方法:
NdefRecord mimeRecord = NdefRecord.createMime("application/vnd.com.example.android.beam",
"Beam me up, Android".getBytes(Charset.forName("US-ASCII")));
NdefRecord mimeRecord = new NdefRecord(
NdefRecord.TNF_MIME_MEDIA ,
"application/vnd.com.example.android.beam".getBytes(Charset.forName("US-ASCII")),
new byte[0], "Beam me up, Android!".getBytes(Charset.forName("US-ASCII")));
三)、使用RTD_TEXT的TNF_WELL_KNOWN
您可以通过以下方式创建一个TNF_WELL_KNOWN NDEF记录:
public NdefRecord createTextRecord(String payload, Locale locale, boolean encodeInUtf8) {
byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
byte[] textBytes = payload.getBytes(utfEncoding);
int utfBit = encodeInUtf8 ? 0 : (1 << 7);
char status = (char) (utfBit + langBytes.length);
byte[] data = new byte[1 + langBytes.length + textBytes.length];
data[0] = (byte) status;
System.arraycopy(langBytes, 0, data, 1, langBytes.length);
System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_TEXT, new byte[0], data);
return record;
}
四)、TNF_WELL_KNOWN与RTD_URI
您可以通过以下方式创建一个TNF_WELL_KNOWN NDEF记录:
使用createUri(String)方法:
NdefRecord rtdUriRecord1 = NdefRecord.createUri("http://example.com");
Uri uri = new Uri("http://example.com");
NdefRecord rtdUriRecord2 = NdefRecord.createUri(uri);
byte[] uriField = "example.com".getBytes(Charset.forName("US-ASCII"));
byte[] payload = new byte[uriField.length + 1]; //add 1 for the URI Prefix
byte payload[0] = 0x01; //prefixes http://www. to the URI
System.arraycopy(uriField, 0, payload, 1, uriField.length); //appends URI to payload
NdefRecord rtdUriRecord = new NdefRecord(
NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], payload);
五)、TNF_EXTERNAL_TYPE
byte[] payload; //assign to your data
String domain = "com.example"; //usually your app's package name
String type = "externalType";
NdefRecord extRecord = NdefRecord.createExternal(domain, type, payload);
byte[] payload;
...
NdefRecord extRecord = new NdefRecord(
NdefRecord.TNF_EXTERNAL_TYPE, "com.example:externalType", new byte[0], payload);
NdefMessage msg = new NdefMessage(
new NdefRecord[] {
...,
NdefRecord.createApplicationRecord("com.example.android.beam")}
package com.example.android.beam;
import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.TextView;
import android.widget.Toast;
import java.nio.charset.Charset;
public class Beam extends Activity implements CreateNdefMessageCallback {
NfcAdapter mNfcAdapter;
TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView textView = (TextView) findViewById(R.id.textView);
// Check for available NFC Adapter
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();
finish();
return;
}
// Register callback
mNfcAdapter.setNdefPushMessageCallback(this, this);
}
@Override
public NdefMessage createNdefMessage(NfcEvent event) {
String text = ("Beam me up, Android!\n\n" +
"Beam Time: " + System.currentTimeMillis());
NdefMessage msg = new NdefMessage(
new NdefRecord[] { createMime(
"application/vnd.com.example.android.beam", text.getBytes())
/**
* The Android Application Record (AAR) is commented out. When a device
* receives a push with an AAR in it, the application specified in the AAR
* is guaranteed to run. The AAR overrides the tag dispatch system.
* You can add it back in to guarantee that this
* activity starts when receiving a beamed message. For now, this code
* uses the tag dispatch system.
*/
//,NdefRecord.createApplicationRecord("com.example.android.beam")
});
return msg;
}
@Override
public void onResume() {
super.onResume();
// Check to see that the Activity started due to an Android Beam
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
processIntent(getIntent());
}
}
@Override
public void onNewIntent(Intent intent) {
// onResume gets called after this to handle the intent
setIntent(intent);
}
/**
* Parses the NDEF Message from the intent and prints to the TextView
*/
void processIntent(Intent intent) {
textView = (TextView) findViewById(R.id.textView);
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
NfcAdapter.EXTRA_NDEF_MESSAGES);
// only one message sent during the beam
NdefMessage msg = (NdefMessage) rawMsgs[0];
// record 0 contains the MIME type, record 1 is the AAR, if present
textView.setText(new String(msg.getRecords()[0].getPayload()));
}
}