10.3 近场通信
近场通信(Near Field Communication, NFC),是一种超近距离的无线通信技术,被广泛地用于近距离、非接触式的识别和互联场景下,比如:通过NFC可将移动设备变成电子钱包,通过NFC来识别商品信息,等等。
NFC有三种工作模式。一种模式是作为信息的载体,被其他读卡设备用来识别和获取信息,在这种模式下,移动设备可以作为电子会员卡、银行卡等,在读卡设备上进行刷卡操作;另一种模式是作为信息识别工具,近距离扫描标识(Tag),识别标识中的信息,比如,扫描带有地址信息的NFC标识,快速了解商场或餐馆的信息;最后一种模式则是在设备间建立点对点(Peer-to-Peer)的连接,进行近距离地数据传输,它的传输速率与蓝牙不相上下,大约在106 Kbit/s至848 Kbit/s,但由于传输距离近不需要进行前续的配对过程,使得连接更为快速,只是能覆盖的有效距离很短(小于10厘米)。
10.3.1 基于NFC的识别和通信
在三种模式中,将Android设备用作NFC标识的扫描和读取者是最常见的工作模式,Android从2.3版本的SDK(api level 9)开始支持基于NFC通信。如图10-2所示,基于NFC的识别和通信可分为三个步骤。
首先,Android通过设备上NFC的相关硬件和驱动,发现周边的NFC设备或标识,并读取出其中包含的消息和数据。
然后,Android会发出请求事件,调用适合的界面组件来处理该NFC消息。和NFC相关的Intent-Filter共有三种。
1)android. nfc.action.NDEF_DISCOVERED。在该广播事件中,界面组件可以指明能够处理的NFC消息数据类型(NFC Data Exchange Format, NDEF)是一个Uri还是一段文本,亦或是一份多媒体数据,等等。示例如下:
//在配置文件中为组件注册NDEF_DISCOVERED事件,处理纯文本信息
<activity name="com.sample.Read_NFC_Text"…>
<intent-filter>
<action android:name=
"android.nfc.action.NDEF_DISCOVERED"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
2)android. nfc.action.TECH_DISCOVERED。在该Intent-Filter中,界面组件可以声明处理特定技术标准下的NFC消息(NFC TECH),比如:基于NfcA(ISO 14443-3A)技术的消息、基于NfcB(ISO 14443-3B)技术的消息、基于NfcF(JIS 6319-4)技术的消息,等等[1]。在配置文件中,可以通过meta-data元素来声明组件支持的NFC技术:
//在配置文件中为组件注册TECH_DISCOVERED事件,处理相关技术的消息
<activity name="com.sample.TECH_NFCA_DISCOVERED"…>
<intent-filter>
<action android:name=
"android.nfc.action.TECH_DISCOVERED"/>
<meta-data android:name=
"android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter.xml"/>
</intent-filter>
</activity>
//资源文件nfc_tech_filter.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-list>
</resources>
3)android. nfc.action.TAG_DISCOVERED。在该Intent-Filter中,可以处理其他未知的NFC消息。
在三种请求事件中,NDEF_DISCOVERED的优先级最高,TAG_DISCOVERED的优先级最低。在事件传播过程中,一旦有界面组件响应高优先级的请求,其余的请求事件将不再发出。由于基于NFC的识别和通信要求设备间的距离非常近,这就需要组件能够快速地响应请求。因此,Android会直接为NFC相关的事件直接选择一个适合的界面组件,而跳过用户选择最合适组件的环节。
因此,在实际开发中,开发者需要尽可能精确地配置Intent-Filter,避免捕获无法处理的NFC事件影响用户体验;或者采取热插拔Intent-Filter的策略,将用户选择处理组件的流程放在识别之前,从而实现精准选择NFC处理组件。相关示例如下:
//构造待触发的Intent
PendingIntent pendingIntent=PendingIntent.getActivity(
this,
0,
new Intent(this, getClass()).addFlags(
Intent.FLAG_ACTIVITY_SINGLE_TOP),
0);
//构造Intent-Filter来处理NFC消息
IntentFilter ndef=new IntentFilter(
NfcAdapter.ACTION_NDEF_DISCOVERED);
ndef.addDataType("text/plain");
intentFiltersArray=new IntentFilter[]{ndef};
techListsArray=new String[][]{
new String[]{NfcF.class.getName()}};
…
//当界面组件位于前台时,监听相关NFC事件
public void onResume(){
super.onResume();
NfcAdapter.getDefaultAdapter(this).enableForegroundDispatch(this,
pendingIntent, intentFiltersArray, techListsArray);
}
//当界面组件位于后台状态时,停止监听
public void onPause(){
super.onPause();
NfcAdapter.getDefaultAdapter(this)
.disableForegroundDispatch(this);
}
图 10-2 Android的NFC支持框架
最后,在寻找到合适的界面组件后,Android会构造并调用该界面组件,由它与NFC标识进行通信。组件与NFC标识交换的数据是通过android.nfc.NdefMessage对象来封装的。每一个NdefMessage中,包含了1个或多个android.nfc.NdefRecord对象。NdefRecord是表示NDEF格式数据的基本单元,它包含类型信息、唯一的标识信息以及需要传输的数据。界面组件可以从NFC标识中读取或写入NdefMessage对象:
//从传入的Intent消息中读取数据
NdefMessage[]msgs=null;
Parcelable[]rawMsgs=intent.getParcelableArrayExtra(
NfcAdapter.EXTRA_NDEF_MESSAGES);
if(rawMsgs!=null){//将数据流转换成Ndef消息
msgs=new NdefMessage[rawMsgs.length];
for(int i=0;i<rawMsgs.length;i++){
msgs[i]=(NdefMessage)rawMsgs[i];
}
}else{//如果是无法识别的数据流,将其整体作为一条Ndef消息
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};
}
//将信息写入到NFC标识中
byte[]msgData=text.getBytes(Charsets.UTF_8);
NdefRecord record=NdefRecord(NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_TEXT, new byte[0],msgData);
try{
//将需要传输的数据封装成消息
NdefMessage message=new NdefMessage(
new NdefRecode{record});
//连接标识并传输
NdefFormatable tag=NdefFormatable.get(t);
tag.connect();
tag.format(message);
}catch(Exception e){
//处理错误
}
[1]Android支持的NFC协议列表,请参见:http://developer.android.com/reference/android/nfc/tech/TagTechnology.html。