NFC基本概念了解
一.NFC(近场通信)原理及通讯方式
NFC(Near Field Communication,近场通信)是一种短距离无线通信技术,基于RFID(射频识别)技术发展而来。其工作原理主要包括磁场耦合和射频信号调制与解调:
磁场耦合:NFC模块通过产生高频磁场,与附近的NFC设备或标签进行通信。当两个设备靠近时(通常在10厘米以内),它们通过电磁感应实现能量传输和数据交换。
射频信号调制与解调:NFC模块通过调制射频信号来传输数据,接收端则通过解调还原数据。这种通信方式保证了数据传输的快速性和安全性
-
卡模式(Card Emulation): 在卡模式中,NFC设备模拟成一张传统的非接触式智能卡(如信用卡、门禁卡等)。当NFC设备(例如智能手机)靠近一个NFC读卡器时,它可以接收读卡器发出的射频信号,并通过调制其天线上的负载来发送信息。这种方式常用于移动支付、身份验证等场合。
- 被动卡模式:NFC设备不主动产生射频场,而是由外部读卡器提供能量和数据交换。
- 主动卡模式:NFC设备可以主动产生射频场,与读卡器进行通信。
-
读卡器模式(Reader/Writer Mode): 在读卡器模式中,NFC设备充当读卡器,它可以主动发送射频信号来唤醒并通信附近的NFC标签(或卡片)。这种方式常用于读取NFC标签上的信息,如产品信息、广告、网址等。
- 被动读卡器模式:NFC设备提供射频场,NFC标签利用这个射频场发送数据。
- 主动读卡器模式:NFC设备可以主动发送命令,并接收NFC标签的响应。
-
点对点模式(Peer-to-Peer Mode): 在点对点模式中,两个NFC设备可以相互通信,交换数据。这种方式不需要外部读卡器或标签,适用于设备之间的直接数据传输,如交换联系人信息、照片、文件等。
- 主动点对点模式:两个NFC设备轮流发送和接收数据,一个设备在发送数据时提供射频场。
- 被动点对点模式:一个设备提供射频场,另一个设备在这个射频场上发送数据。
二.NFC(近场通信)标签类型
- 根据存储能力、数据传输速度、读写特性以及是否需要电源等因素进行分类
- NFC Forum Type 1:通常具有约48字节到1.5千字节的存储容量。
- NFC Forum Type 2:存储容量通常在48字节到8千字节之间。
- NFC Forum Type 3:这种类型的标签通常具有较大一点的存储容量,从1.5千字节到32千字节不等。
- NFC Forum Type 4:这些标签的存储容量可以更大,从几千字节到数兆字节(MB)不等
三.NFC(近场通信)写入格式
基于程序开发需要对NFC标签的格式需要有一点的了解,基础只考虑常见的NDEF模式,其他模式后续学习。选择类型通常取决于应用场景和所需的功能。
-
NDEF (NFC Data Exchange Format):
这是NFC论坛定义的标准格式,用于在NFC设备之间交换数据。
NDEF消息由一个或多个NDEF记录组成,每个记录包含一个记录类型定义(RTD)或者一个URI(统一资源标识符)。
常见的NDEF记录类型包括:- URI记录:用于存储网址、电话号码等。
- TEXT记录:用于存储文本信息,可以指定语言编码。
- MIME媒体记录:用于存储多媒体内容或其他类型的数据。
- ACTION记录:用于触发设备上的特定操作,如打开应用程序。
-
Mifare Classic格式:
Mifare Classic是NXP Semiconductors的一种专有格式,常用于门禁控制、公共交通票务等。
数据以扇区(Sector)和块(Block)的形式组织,每个扇区可以包含多个块,其中一些块用于存储数据,另一些用于安全控制(如密钥)。 -
Mifare Ultralight格式:
适用于简单的应用,如产品标签、广告等。
数据以页(Page)的形式组织,每页通常包含4字节。 -
ISO/IEC 7816格式:
这是智能卡的国际标准,也适用于一些NFC标签。
数据组织类似于Mifare Classic,但包含了更复杂的安全特性。 -
自定义格式:
开发者可以根据自己的需求定义数据格式。
这种格式通常用于特定的应用场景,可能不兼容其他设备和应用程序。
四.NFC(近场通信)可实践方向
网址链接:可以将网址链接写入NFC标签,用户触碰标签后可以直接打开浏览器访问对应的网页。
文本信息:可以存储简单的文本信息,如联系人信息、笔记、产品描述等。
电话号码:可以写入电话号码,触碰NFC标签后手机会提示是否拨打该号码。
电子邮件地址:可以存储电子邮件地址,触碰后可快速创建新邮件。
地理位置信息:可以存储一个地理位置的经纬度信息,触碰后可以在地图应用中显示该位置。
Wi-Fi配置信息:可以写入Wi-Fi网络的SSID、密码和其他配置信息,使设备能够快速连接到Wi-Fi网络。
蓝牙配对信息:可以存储蓝牙设备的配对信息,简化蓝牙设备的配对过程。
VCard(电子名片):可以存储联系人的VCard信息,触碰后可以直接添加到手机的联系人列表。
日历事件:可以写入日历事件信息,如会议、提醒等,触碰后可以添加到手机的日历中。
音视频文件:可以存储音视频文件的链接或者小型的音视频文件,触碰后可以直接播放。
社交媒体链接:可以写入社交媒体的链接,如Twitter、Facebook等,触碰后可以打开相应的社交媒体页面。
支付信息:NFC还可以用于移动支付,如Google Wallet、Apple Pay等,这些服务在NFC标签或设备上存储支付信息。
门禁控制:在安全系统中,NFC可以用于门禁控制,存储访问权限信息。
身份验证:可以用于身份验证,如无钥匙进入汽车、酒店房间等。
五.NFC响应顺序
NFC Intent 调度的优先级顺序
系统会从最高优先级(最具体)到最低优先级(最通用)依次检查,一旦有应用匹配成功,就会立即启动该应用,并停止后续的检查。
前台调度系统(最高)
这是最特殊、优先级最高的情况。它允许一个当前正在前台运行的应用临时性地捕获所有 NFC 事件,从而“抢占”系统默认的分发行为。
结论:如果有一个应用正在使用前台调度,那么它总是获胜。NDEF_DISCOVERED(高)
如果没有任何应用使用前台调度,系统会检查扫描到的标签是否包含 NDEF (NFC Data Exchange Format) 格式的数据。这是目前最常见、最标准化的 NFC 标签格式。
如果标签是 NDEF 格式,并且只有一个应用精确匹配,则直接启动该应用。TECH_DISCOVERED(中)
如果标签是 NDEF 格式,但没有任何应用通过 NDEF_DISCOVERED 匹配成功,或者标签根本不是 NDEF 格式。系统不再关心标签上的数据内容,而是关心标签的技术类型。常见的技术类型包括:NfcA, NfcB, NfcF, NfcV, IsoDep, MifareClassic, Ndef 等。
结论:如果标签的技术类型被某个应用声明支持,则启动该应用。
如果有多个应用匹配同一优先级怎么办?
如果在 NDEF_DISCOVERED 或 TECH_DISCOVERED 级别,有多个应用都匹配成功,系统不会随机选择一个。相反,它会弹出一个 “应用选择器” (Activity Chooser Dialog),向用户展示所有能够处理该标签的应用列表,让用户手动选择启动哪一个。
六.实战示例
基础类,主要用于捕获nfc信息响应
/**
* 1.子类需要在onCreate方法中做Activity初始化。
* 2.子类需要在onNewIntent方法中进行NFC标签相关操作。
* 当launchMode设置为singleTop时,第一次运行调用onCreate方法,
* 第二次运行将不会创建新的Activity实例,将调用onNewIntent方法
* 所以我们获取intent传递过来的Tag数据操作放在onNewIntent方法中执行
* 如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())
* 只要NFC标签靠近就执行
*/
public class BaseNfcActivity extends AppCompatActivity {
//用于管理设备的 NFC 硬件功能。它允许应用程序与 NFC 标签或其他支持 NFC 的设备进行交互,
//包括读取标签数据、写入数据以及点对点通信等
private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
/**
* 启动Activity,界面可见时
*/
@Override
protected void onStart() {
super.onStart();
//用于获取当前设备的 NFC 适配器实例
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
//一旦截获NFC消息,就会通过PendingIntent调用窗口
mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), PendingIntent.FLAG_MUTABLE);
}
/**
* 获得焦点,按钮可以点击
*/
@Override
public void onResume() {
super.onResume();
//设置处理优于所有其他NFC的处理
if (mNfcAdapter != null)
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
/**
* 暂停Activity,界面获取焦点,按钮可以点击
*/
@Override
public void onPause() {
super.onPause();
//恢复默认状态
if (mNfcAdapter != null)
mNfcAdapter.disableForegroundDispatch(this);
}
}
操作类,具体的一些读写方法及类型判断等方法类
public class NFCHelper {
private String mPackageName = "com.****.****";//app地址、
private String mPackageNames = "com.hypergryph.arknights";//方舟地址、
private String checkInData = "{\"Time\":\"\",\"Number\":\"0\"}}"; // 默认文本数据
/**
* 读取NFC标签文本数据
*/
public String readNfcTag(Intent intent,Context context) {
String mTagText="";
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage msgs[] = null;
int contentSize = 0;
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
contentSize += msgs[i].toByteArray().length;
}
}
try {
if (msgs != null) {
NdefRecord record = msgs[0].getRecords()[0];
String textRecord = processNdefRecord(record,context);
mTagText += record.getTnf() +"\n"+ textRecord + "\n\ntext\n" + contentSize + " bytes";
}
} catch (Exception e) {
}
}
return mTagText;
}
public String processNdefRecord(NdefRecord ndefRecord, Context context) {
// 检查 TNF 是否为 TNF_EXTERNAL_TYPE
if (ndefRecord.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE||ndefRecord.getTnf() == NdefRecord.TNF_MIME_MEDIA) {
// 获取类型字段
byte[] type = ndefRecord.getType();
// 将类型字节数组转换为字符串,这里假设类型是字符串形式
String typeString = new String(type);
// 获取记录中的数据
byte[] data = ndefRecord.getPayload();
// 注意:实际数据可能需要根据实际情况解析
String dataString = new String(data, 0, data.length, Charset.forName("UTF-8"));
String textRecord="\n\nType: " + typeString+"\n\nData: " + dataString;
return textRecord;
// 输出类型和数据
}
return "失败";
}
/**
* 往标签写数据的方法
*
* @param tag
*/
public void writeNFCTag(Tag tag,Context context,String Dateinfo) {
if (tag == null) {
return;
}
if(Dateinfo.equals("")){
Dateinfo=checkInData;
}
// 创建一个包含签到数据的NdefRecord
NdefRecord checkInRecord = new NdefRecord(
NdefRecord.TNF_MIME_MEDIA,
"application/vnd.*****.checkin".getBytes(Charset.forName("US-ASCII")),
new byte[0], // 可以省略,如果不需要ID
Dateinfo.getBytes(Charset.forName("US-ASCII"))
);
// 创建一个表示应用程序记录的NdefRecord
NdefRecord appRecord = NdefRecord.createApplicationRecord(mPackageName);
NdefMessage ndefMessage = new NdefMessage(
new NdefRecord[]{checkInRecord, appRecord});
//转换成字节获得大小
int size = ndefMessage.toByteArray().length;
try {
//2.判断NFC标签的数据类型(通过Ndef.get方法)
Ndef ndef = Ndef.get(tag);
//判断是否为NDEF标签
if (ndef != null) {
ndef.connect();
//判断是否支持可写
if (!ndef.isWritable()) {
Toast.makeText(context, "不支持写入", Toast.LENGTH_SHORT).show();
return;
}
//判断标签的容量是否够用
if (ndef.getMaxSize() < size) {
Toast.makeText(context, "数据量不足", Toast.LENGTH_SHORT).show();
return;
}
//3.写入数据
ndef.writeNdefMessage(ndefMessage);
Toast.makeText(context, "写入成功", Toast.LENGTH_SHORT).show();
} else { //当我们买回来的NFC标签是没有格式化的,或者没有分区的执行此步
//Ndef格式类
NdefFormatable format = NdefFormatable.get(tag);
//判断是否获得了NdefFormatable对象,有一些标签是只读的或者不允许格式化的
if (format != null) {
//连接
format.connect();
//格式化并将信息写入标签
format.format(ndefMessage);
Toast.makeText(context, "写入成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "写入失败,标签类型不允许", Toast.LENGTH_SHORT).show();
}
}
} catch (Exception e) {
Toast.makeText(context, "错误数据"+e.toString(), Toast.LENGTH_SHORT).show();
}
}
}