【又4】Android NFC 开发简介

今天上海终于下雪了,虽然是雨夹雪,不过还是有点惊喜的,元旦休息5天,只要不是连续阴雨,我感觉应该还是满足了吧。昨天在公司的时候和我们部门一起分享了自己开发Android的心得,今天稍微整理一下,这篇文章就能搞定了;

了解AndroidNFC开发,我们需要打开Android的开发文档找到NFC部分:

http://developer.android.com/guide/topics/connectivity/nfc/index.html

如果打不开可以看我上一篇文章——科学上网,稳定的使用Google

1,简介

首先我们需了解什么是NFC?来看一下Google的定义,

Near Field Communication

Near Field Communication (NFC) is a set of short-range wireless technologies, typically requiring a distance of 4cm or less to initiate a connection. NFC allows you to share small payloads of data between an NFC tag and an Android-powered device, or between two Android-powered devices.

Tags can range in complexity. Simple tags offer just read and write semantics, sometimes with one-time-programmable areas to make the card read-only. More complex tags offer math operations, and have cryptographic hardware to authenticate access to a sector. The most sophisticated tags contain operating environments, allowing complex interactions with code executing on the tag. The data stored in the tag can also be written in a variety of formats, but many of the Android framework APIs are based around a NFC Forumstandard called NDEF (NFC Data Exchange Format).

本人才疏学浅,用google的翻译工具翻译并修改了一下:

翻译:

近场通信(NFC)是一种短距离的高频无线通信技术 ,通常建立连接的距离小于4cm。你可以通过它在一个NFC标签与Android的机器之间,或者两台android机器之间,完成小的支付数据交互,
Tag的种类复杂。简单的标签只提供了读和写功能,使用一次性可编程技术可以使卡变成只读卡;更复杂的标签提供了数学运算,并有硬件加密来访问指定区域。最先进的标签中包含的操作环境,可以在标签上执行程式和复杂的数据交换。数据存储在标签也有不同的格式,但许多Android API都是基于NFC Forum上的标准格式——NDEF.

 

 

这里有两个概念要了解,TagNDEF,这个会在下面讲解一下

 

2,基本知识:

Taghttp://www.nfc-forum.org/specs/spec_list/

NFC的基本标签类型有四种,以14来标识,各有不同的格式与容量。这些标签类型格式的基础是:ISO 14443AB类型、Sony FeliCa,前者是非接触式智能卡的国际标准,而后者符合ISO 18092被动式通讯模式标准。 保持NFC标签尽可能简单的优势是:在很多场合,标签可为一次性使用,例如在海报中寿命较短的场合。 

1类标签(Tag 1 Type):此类型基于ISO14443A标准。此类标签具有可读、重新写入的能力,用户可将其配置为只读。存储能力为96字节,用来存网址URL或其他小量数据富富有余。然而,内存可被扩充到2k字节。此类NFC标签的通信速度为106 kbit/s。此类标签简洁,故成本效益较好,适用于许多NFC应用[5]

2类标签(Tag 2 Type):此类标签也是基于ISO14443A,具有可读、重新写入的能力,用户可将其配置为只读。其基本内存大小为48字节,但可被扩充到2k字节。通信速度也是106 kbit/s。  

3类标签(Tag 3 Type):此类标签基于Sony FeliCa体系。目前具有2k字节内存容量,数据通讯速度为212 kbit/s。故此类标签较为适合较复杂的应用,尽管成本较高。 

4类标签(Tag 4 Type):此类标签被定义为与ISO14443AB标准兼容。制造时被预先设定为可读/可重写、或者只读。内存容量可达32k字节,通信速度介于106 kbit/s424 kbit/s之间。  

从上述不同标签类型的定义可以看出,前两类与后两类在内存容量、构成方面大不相同。故它们的应用不太可能有很多重叠。第1与第2类标签是双态的,可为读/写或只读。第3与第4类则是只读,数据在生产时写入或者通过特殊的标签写入器来写入。

NDEFNFC Data Exchange Format

为实现标签和NFC设备,及NFC设备之间的交互通信,NFC论坛(NFC FROUM)定义了称为NFC数据交换格式(NDEF)的通用数据格式。

NDEF是轻量级的紧凑的二进制格式,可带有URLvCardNFC定义的各种数据类型。

NDEF使得NFC的各种功能能容易的中使用各种支持的标签类型传输数据,因为NDEF封装了标签的种类细节信息,使得应用不用关心与何种标签在通信。

NDEF交换的信息由一系列记录组成。每条记录包含一个有效载荷。内容可以似乎URLMIME媒质,或NFC定义的数据类型。使用NFC定义的数据类型,载荷内容必须被定义在一个NFC记录类型定义(RTD)文件中。

记录中数据的类型和大小由记录载荷的头部注明。

头部包含,类型域用来指定载荷的类型。载荷的长度数的单位是字节(octet)。可选的指定载荷是否带有一个NDEF记录。

 

3,Android NFC API

Android.nfc package包含顶层类用来与本地NFC适配器交互. 这些类可以表示被检测到的tags和用NDEF数据格式。
























ClassDescription
NfcManager一个NFC adapter的管理器,可以列出所有此android设备支持的NFC adapter.只不过大部分android 设备只有一个NFC adapter,所以你大部分情况下可以直接用静态方法getDefaultAdapter(context)来取适配器。
NfcAdapter表示本设备的NFC adapter,可以定义Intent来请求将系统检测到tags的提醒发送到你的Activity.并提供方法去注册前台tag提醒发布和前台NDEF推送。 前台NDEF推送是当前android版本唯一支持的p2p NFC通信方式。
NdefMessageandNdefRecordNDEF是NFC论坛定义的数据结构,用来有效的存数据到NFC tags.比如文本,URL,和其他MIME类型。一个NdefMessage扮演一个容器,这个容器存哪些发送和读到的数据。一个NdefMessage对象包含0或多个NdefRecord,每个NDEF record有一个类型,比如文本,URL,智慧型海报/广告,或其他MIME数据。在NDEFMessage里的第一个NfcRecord的类型用来发送tag到一个android设备上的activity.
Tag标示一个被动的NFC目标,比如tag,card,钥匙挂扣,甚至是一个电话模拟的的NFC卡.当一个tag被检测到,一个tag对象将被创建并且封装到一个Intent里,然后NFC 发布系统将这个Intent用startActivity发送到注册了接受这种Intent的activity里。你可以用getTechList()方法来得到这个tag支持的技术细节和创建一个android.nfc.tech提供的相应的TagTechnology对象。

android.nfc.tech package 包含那些对tag查询属性和进行I/O操作的类。这些类分别标示一个tag支持的不同的NFC技术标准。















































ClassDescription
TagTechnology这个接口是下面所有tag technology类必须实现的。
NfcA支持ISO 14443-3A 标准的操作。Provides access to NFC-A (ISO 14443-3A) properties and I/O operations.
NfcBProvides access to NFC-B (ISO 14443-3B) properties and I/O operations.
NfcFProvides access to NFC-F (JIS 6319-4) properties and I/O operations.
NfcVProvides access to NFC-V (ISO 15693) properties and I/O operations.
IsoDepProvides access to ISO-DEP (ISO 14443-4) properties and I/O operations.
Ndef提供对那些被格式化为NDEF的tag的数据的访问和其他操作。Provides access to NDEF data and operations on NFC tags that have been formatted as NDEF.
NdefFormatable对那些可以被格式化成NDEF格式的tag提供一个格式化的操作
MifareClassic如果android设备支持MIFARE,提供对MIFARE Classic目标的属性和I/O操作。
MifareUltralight如果android设备支持MIFARE,提供对MIFARE Ultralight目标的属性和I/O操作。

4,Android NFC

系统操作流程

 

 

 

 

 

 

 

 

 

 

应用程序处理流程

 

 

5,Android NFC应用开发步骤:

5.1 声明Android Manifest.xml的元素

在你能访问一个设备的NFC硬件和正确的处理NFC的Intent之前,需要在AndroidManifest.xml中先声明下面的项:

1. NFC使用 <uses-permission> 元素来访问NFC硬件:

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

2. 最小SDK版本需要设置正确,API level 9只包含有限的tag支持,包括:
.通过ACTION_TAG_DISCOVERED来发布Tag信息
.只有通过EXTRA_NDEF_MESSAGES扩展来访问NDEF消息
.其他的tag属性和I/O操作都不支持
所以你可能想要用API level 10来实现对tag的广泛的读写支持。

<uses-sdk android:minSdkVersion=”10”/>

3. uses-feature 元素定义:你的程序可以再android市场里显示有NFC硬件。

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

4. NFC intent filter告诉android系统你的activity能处理NFC数据,可以定义1个或多个intent filter:

<intent-filter>
_ <action android:name=”android.nfc.action.NDEFDISCOVERED”/>
<data android:mimeType=”mime/type” />
</intent-filter>

<intent-filter>
_ <action android:name=”android.nfc.action.TECHDISCOVERED”/>
_ <meta-data android:name=”android.nfc.action.TECHDISCOVERED”
_ android:resource=”@xml/nfc_techfilter.xml” />
</intent-filter>

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

上边3个intent filters 有优先级,更多信息可以看下面的Tag发布系统

也可以看NFCDemo例子的 AndroidManifest.xml来有个更深的理解。

5.2使用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 的 NDEFDISCOVERED intent ,你要按照如下定义AndroidManifest.xml file: <intent-filter> <action android:name=”android.nfc.action.NDEFDISCOVERED”/>
    <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.TECHDISCOVERED”/>
</intent-filter>

_<meta-data android:name=”android.nfc.action.TECHDISCOVERED”
_ android:resource=”@xml/nfc_techfilter” />

</activity>

 

 

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

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

1. 添加下列代码到Activity的onCreate() 方法里

a. 创建一个 PendingIntent 对象, 这样Android系统就能在一个tag被检测到时定位到这个对象

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

b. 在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_NDEFDISCOVERED);
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,
};

c. 设置一个你程序要处理的Tag technologies的列表,调用Object.class.getName() 方法来获得你想要支持处理的technology类。
techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
2. 覆盖下面的方法来打开或关闭前台发布系统。比如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.EXTRATAG);
//do something with tagFromIntent
}

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

 

5.4 使用NFC tag上的数据

NFC tag上的数据是以字节存放,所以你可以将其转换成其他你想要的格式。当往tag写东西时,你必须以字节格式来写。Android提供API来帮助写符合NDEF标准的信息。使用此标准能保证你的数据在往tag写时能被所有Android NFC设备支持。然而,很多tag使用他们自己的标准来存储数据,这些标准也被Android支持。但你必须自己实现协议栈来读写这些tag。你可以在android.nfc.tech里找到所有支持的technologies,并且可以在TagTechnology接口里对technology有个了解。这一段是简单介绍在android系统里怎样使用NDEF 消息。这不意味着是一个完整的NDEF功能的介绍。但标出了主要需要注意和使用的东西。

为了方便使用NDEF消息,android提供NdefRecordNdefMessage来包装原始字节数据为NDEF消息。一个NdefMessage是保存0个或多个NdefRecords的容器。每个NdefRecord有自己的唯一类型名字格式,记录类型和ID来与其他记录区分开。你可以存储不同类型的记录,不同的长度到同一个 NdefMessage。NFC tag容量的限制决定你的NdefMessage的大小。
那些支持Ndef和NdefFormatable技术的tag可以返回和接受NdefMessage对象为参数来进行读写操作。你需要创建你自己的逻辑来为其他在android.nfc.tech的tag技术实现读写字节的操作。

你可以从NFC Forum(http://www.nfc-forum.org/specs/)下载NDEF消息标准的技术文档,比如纯文本和智慧型海报. NFCDemo例子里声明了纯文本和智慧型海报的NDef 消息。

5.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_TAGDISCOVERED.equals(action)) {
_ Parcelable[] rawMsgs =intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEFMESSAGES);
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.TNFUNKNOWN, 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消息,比如纯文本和智慧型海报。

5.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.USASCII);
String text = “Tag, you’re it!”;
_final byte[] textBytes = text.getBytes(Charsets.UTF8);
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.RTDTEXT, newbyte[0], data);
try {
NdefRecord[] records = {text};
NdefMessage message = new NdefMessage(records);
tag.connect();
tag.format(message);
}
catch (Exception e){
//do error handling
}

5.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来了解点对点数据交换。

 

 

 

以上内容参考网上的好Android的标准API文档,部分内容需自我实践才可以使用;

6,总结:

首先在AndroidManifest.xml文件中注册NFC的读写权限和注册Activity操作何种卡片数据类型的intent

其次在Activity中设置自己的前台发布系统,防止系统吊起其他应用程序处理NFCTag信息

再次在onresume或者自己的onNewIntent函数中处理自己的Intent的信息,来读卡的UID,以及读写卡片;

注意写卡的操作时和自己的卡片类型相关的,Android支持的卡片在写卡前最好执行一下NdefFormatable

要了解自己卡片的类型,一般TapeA卡用NdefFormatable后直接用Ndef写卡问题不大;

 

我一直好奇上海的公交卡在刷卡的过程中到底实时联网吗?最后看了中卡的介绍认定并不是联网的,我们的钱是写在卡里的。最后还有4篇文章就2013了!