基于android的NFC技术与开发实例

近距离无线通信技术(Near Field Communication,NFC),是由飞利浦公司和索尼公司共同开发的一种非接触式识别和互联技术,可以在移动设备、消费类电子产品、PC和智能设备间进行近距离无线通信。NFC提供了一种简单的、非触控式的解决方案,可以让消费者简单直观地交换信息、访问内容与服务。NFC整合了非接触式读卡器、非接触式智能卡和点对点(Peer-to—Peer)通信功能,为消费者开创了全新的便捷生活方式。手机和NFC技术的结合,将会给消费者提供极大的生活便利,例如移动支付、位置服务信息、身份识别、公共交通卡等应用,在医疗保健、优惠券、智能海报等许多领域有也有巨大的应用潜力。Android是由googh公司设计的开源智能操作系统,在手机和平板电脑等移动设备领域有着普遍的应用。在Android上配备了LCD、触摸屏、GPS、WiFi、蓝牙、重力传感器、感光器等实用外设,同时支持3G语音和数据业务。Android不仅提供基于Java开发的SDK(Software De.velopment Kit,SDK),而且源代码是开放的。大量的应用程序支持,使得Android成为最为流行的智能手机操作系统闭。Android系统的开放性以及强大的通信功能,使得NFC的各种应用能在Android上便捷地实现,同时也可以进一步促进Android的普及和发展,丰富其应用功能。

NFC终端有3种工作模式:

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

对于Android 4.0 SDK中提供的Beam例子,对于NFC开发来说的确是一个不错的模板。对于了解NFC的NDEF消息处理过程不妨看下面的代码。

public class Beam extends Activity implements CreateNdefMessageCallback, 
         OnNdefPushCompleteCallback { 
     NfcAdapter mNfcAdapter; 
     TextView mInfoText; 
     private static final int MESSAGE_SENT = 1; 
    @Override 
     public void onCreate(Bundle savedInstanceState) { 
         super.onCreate(savedInstanceState); 
         setContentView(R.layout.main); 
        mInfoText = (TextView) findViewById(R.id.textView); 
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);  //实例化NFC设备 
         if (mNfcAdapter == null) { 
             mInfoText = (TextView) findViewById(R.id.textView); 
             mInfoText.setText("NFC is not available on this device."); 
         } 
        mNfcAdapter.setNdefPushMessageCallback(this, this); //注册NDEF回调消息 
         mNfcAdapter.setOnNdefPushCompleteCallback(this, this); 
     } 
    @Override 
     public NdefMessage createNdefMessage(NfcEvent event) { 
         Time time = new Time(); 
         time.setToNow(); 
         String text = ("Beam me up!\\n\\n" + 
                 "Beam Time: " + time.format("%H:%M:%S")); 
         NdefMessage msg = new NdefMessage( 
                 new NdefRecord\[\] { createMimeRecord( 
                         "application/com.example.android.beam", text.getBytes()) 
         }); 
         return msg; 
     } 
    @Override 
     public void onNdefPushComplete(NfcEvent arg0) { 
         // A handler is needed to send messages to the activity when this 
         // callback occurs, because it happens from a binder thread 
         mHandler.obtainMessage(MESSAGE_SENT).sendToTarget(); 
     } 
    private final Handler mHandler = new Handler() { 
         @Override 
         public void handleMessage(Message msg) { 
             switch (msg.what) { 
             case MESSAGE_SENT: 
                 Toast.makeText(getApplicationContext(), "Message sent!", Toast.LENGTH_LONG).show(); 
                 break; 
             } 
         } 
     }; 
    @Override 
     public void onResume() { 
         super.onResume(); 
         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) { 
         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 
         mInfoText.setText(new String(msg.getRecords()\[0\].getPayload())); 
     } 
    /** 
      * Creates a custom MIME type encapsulated in an NDEF record 
      * 
      * @param mimeType 
      */
     public NdefRecord createMimeRecord(String mimeType, byte\[\] payload) { 
         byte\[\] mimeBytes = mimeType.getBytes(Charset.forName("US-ASCII")); 
         NdefRecord mimeRecord = new NdefRecord( 
                 NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte\[0\], payload); 
         return mimeRecord; 
     } 
    @Override 
     public boolean onCreateOptionsMenu(Menu menu) { 
         // If NFC is not available, we won't be needing this menu 
         if (mNfcAdapter == null) { 
             return super.onCreateOptionsMenu(menu); 
         } 
         MenuInflater inflater = getMenuInflater(); 
         inflater.inflate(R.menu.options, menu); 
         return true; 
     } 
    @Override 
     public boolean onOptionsItemSelected(MenuItem item) { 
         switch (item.getItemId()) { 
             case R.id.menu_settings: 
                 Intent intent = new Intent(Settings.ACTION_NFCSHARING_SETTINGS); 
                 startActivity(intent); 
                 return true; 
             default: 
                 return super.onOptionsItemSelected(item); 
         } 
     } 
 } 

一个带有NFC支持的android设备通常是一个发起者。也可以作为NFC的读写设备。他将检测NFC tags并且打开一个Activity来处理. Android 2.3.3还有支持有限的P2P。

Tags分很多种,其中简单的只提供读写段,有的只能读。复杂的tags可以支持一些运算,加密来控制对tags里数据段的读写。甚至一些tags上有简单的操作系统,允许一些复杂的交互和可以执行一些代码。

API概览

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

class

Description

NfcManager

一个NFC adapter的管理器,可以列出所有此android设备支持的NFC adapter.只不过大部分android 设备只有一个NFC adapter,所以你大部分情况下可以直接用静态方法 getDefaultAdapter(context)来取适配器。

NfcAdapter

表示本设备的NFC adapter,可以定义Intent来请求将系统检测到tags的提醒发送到你的Activity.并提供方法去注册前台tag提醒发布和前台NDEF推送。 前台NDEF推送是当前android版本唯一支持的p2p NFC通信方式。

NdefMessageandNdefRecord

NDEF是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.techpackage 包含那些对tag查询属性和进行I/O操作的类。这些类分别标示一个tag支持的不同的NFC技术标准。

Class

Description

TagTechnology

这个接口是下面所有tag technology类必须实现的。

NfcA

支持ISO 14443-3A 标准的操作。Provides access to NFC-A (ISO 14443-3A) properties and I/O operations.

NfcB

Provides access to NFC-B (ISO 14443-3B) properties and I/O operations.

NfcF

Provides access to NFC-F (JIS 6319-4) properties and I/O operations.

NfcV

Provides access to NFC-V (ISO 15693) properties and I/O operations.

IsoDep

Provides 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操作。

声明Android Manifest.xml的元素

1. NFC使用 元素来访问NFC硬件:

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

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

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

android:resource="@xml/nfc_tech_filter.xml" />

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

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

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对应的元素,这样当扫描到的tag传过来的数据类型与你定义的相匹配时,你的Activity就会被调用。例如想处理一个包含plain text 的 NDEF_DISCOVERED intent ,你要按照如下定义AndroidManifest.xml file:

如果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_DISCOVERED intents或者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保存到/res/xml文件夹.

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

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

android.nfc.tech.NfcA
android.nfc.tech.Ndef

android.nfc.tech.NfcB
android.nfc.tech.Ndef

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

...

android:resource="@xml/nfc_tech_filter" />
...

使用前台发布系统****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_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,  
};

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

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

2. 覆盖下面的方法来打开或关闭前台发布系统。比如onPause()和onResume()方法。必须在主线程里调用[enableForegroundDispatch(Activity, PendingIntent, IntentFilter[], String[][])](http://developer.android.com/reference/android/nfc/NfcAdapter.html#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], java.lang.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 ForegroundDispatchsample from API Demos for the complete sample.

使用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 消息。

读一个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消息,比如纯文本和智慧型海报。

写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, new byte[0], data);
try {
NdefRecord[] records = {text};
NdefMessage message = new NdefMessage(records);
tag.connect();
tag.format(message);
}
catch (Exception e){
//do error handling
}

点对点的数据交换

前台推送技术支持简单点对点的数据交换,你可以用[enableForegroundNdefPush(Activity, NdefMessage)](http://developer.android.com/reference/android/nfc/NfcAdapter.html#enableForegroundNdefPush(android.app.Activity, android.nfc.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)](http://developer.android.com/reference/android/nfc/NfcAdapter.html#enableForegroundNdefPush(android.app.Activity, android.nfc.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来了解点对点数据交换。