NFC(Near Field Communication,近场通信)作为一种短距离高频无线通信技术,在移动支付、身份识别、数据传输等场景中应用广泛。在 Android 设备上,NFC 功能可以实现与 IC 卡、标签、其他 NFC 设备的交互,其中 “读取 IC 卡” 是最常见的需求之一。本文将从技术原理到实际开发,全面讲解 Android NFC 技术及 IC 卡读取实现。
NFC 是一种基于 RFID(射频识别)技术的短距离通信标准,工作频率为 13.56MHz,通信距离通常在 10cm 以内。相比蓝牙、WiFi 等无线技术,NFC 具有无需配对、响应速度快、功耗低的特点,非常适合 “接触式” 交互场景(如刷公交卡、门禁卡)。
NFC 技术兼容三种主要标准:
Android 系统从 API 9(Android 2.3)开始支持 NFC 功能,核心工作模式分为三种:
在 Android 开发中,NFC 相关功能主要通过以下核心类实现:
IC 卡(集成电路卡)是通过芯片存储数据的卡片,按通信方式可分为接触式(如银行卡)和非接触式(如公交卡)。非接触式 IC 卡正是通过 NFC 技术实现数据交互的,因此 “读取 IC 卡” 本质上是 Android 设备在 “读卡器模式” 下与 IC 卡建立通信并获取数据。
不同 IC 卡遵循的协议不同,对应的 Android NFC 技术支持也不同,常见类型如下:
IC 卡类型 |
协议标准 |
适用场景 |
Android 支持情况 |
Mifare Classic |
ISO 14443A |
校园卡、门禁卡 |
需设备支持,Android 10 + 限制部分功能 |
Mifare Ultralight |
ISO 14443A |
电子标签、门票 |
大部分设备支持,数据读取无特殊限制 |
CPU 卡 |
ISO 14443A/B |
身份证、金融 IC 卡 |
需通过加密认证,支持 ISO_DEP 技术 |
Felica |
ISO 18092 |
日本交通卡(Suica) |
仅部分设备支持(如日系机型) |
其中,Mifare Classic(MF1 卡) 是最常见的民用 IC 卡(如小区门禁、校园一卡通),也是开发中最常接触的类型,但需注意:Android 10(API 29)及以上系统对 Mifare Classic 的读取做了限制(需设备厂商授权),部分设备可能无法读取。
读取 IC 卡的核心逻辑是:当 IC 卡靠近 Android 设备时,系统通过 NFC 检测到标签,应用接收标签数据并解析。以下是完整开发流程。
清单文件需完成三件事:声明权限、配置 NFC 过滤规则、指定启动模式(避免重复创建 Activity)。
其中nfc_tech_filter.xml(位于res/xml目录)用于指定支持的标签技术,例如支持 Mifare Classic 和 ISO 14443A:
android.nfc.tech.MifareClassic
android.nfc.tech.IsoDep
android.nfc.tech.NfcA
在 Activity 中,首先需要初始化NfcAdapter,并判断设备是否支持 NFC:
public class ICCardReaderActivity extends AppCompatActivity {
private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_iccard_reader);
// 初始化NFC适配器
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
Toast.makeText(this, "设备不支持NFC", Toast.LENGTH_SHORT).show();
finish();
return;
}
// 检查NFC是否开启
if (!mNfcAdapter.isEnabled()) {
Toast.makeText(this, "请开启NFC功能", Toast.LENGTH_SHORT).show();
// 可跳转到NFC设置页
startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
}
// 创建PendingIntent:当检测到标签时,系统通过此Intent启动当前Activity
mPendingIntent = PendingIntent.getActivity(
this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_IMMUTABLE
);
}
}
当 IC 卡靠近设备时,系统会通过PendingIntent触发 Activity 的onNewIntent方法,我们需要在此方法中获取标签数据并解析。
1.从 Intent 中获取Tag对象(标签实例);
2.根据标签支持的技术(如MifareClassic)创建对应的数据读取对象;
3.连接标签并读取数据;
4.解析数据(需根据 IC 卡数据格式处理)。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// 判断是否为NFC标签 Intent
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
// 获取标签对象
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) {
readICCardData(tag); // 读取IC卡数据
}
}
}
// 读取IC卡数据(以Mifare Classic为例)
private void readICCardData(Tag tag) {
// 获取Mifare Classic标签实例
MifareClassic mifare = MifareClassic.get(tag);
if (mifare == null) {
Toast.makeText(this, "不支持该类型IC卡", Toast.LENGTH_SHORT).show();
return;
}
try {
// 连接标签
mifare.connect();
// 获取卡片类型(如Mifare Classic 1K/4K)
String type = mifare.getType() == MifareClassic.TYPE_CLASSIC ? "Classic" :
mifare.getType() == MifareClassic.TYPE_PLUS ? "Plus" : "UltraLight";
// 获取扇区数和块数
int sectorCount = mifare.getSectorCount();
int blockCount = mifare.getBlockCount();
// 读取数据(以读取第0扇区第0块为例,需注意:扇区0的块0通常存储厂商信息,不可修改)
// 注意:读取扇区需先验证密钥(默认密钥可能为0xFFFFFFFFFFFF)
boolean auth = mifare.authenticateSectorWithKeyA(0, MifareClassic.KEY_DEFAULT);
if (auth) {
// 获取扇区内的块索引
int blockIndex = mifare.sectorToBlock(0);
// 读取块数据(16字节)
byte[] data = mifare.readBlock(blockIndex);
// 解析数据(根据实际格式转换,这里转为16进制字符串)
String hexData = bytesToHex(data);
Log.d("ICCard", "扇区0块0数据:" + hexData);
Toast.makeText(this, "读取成功:" + hexData, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "密钥验证失败,无法读取数据", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "读取失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
} finally {
try {
// 关闭连接
mifare.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 字节数组转16进制字符串(辅助方法)
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) sb.append("0");
sb.append(hex).append(" ");
}
return sb.toString();
}
为确保在 Activity 处于不同状态时(如前台、后台)都能接收 NFC 事件,需在onResume和onPause中开启 / 关闭 NFC 前台调度:
@Override
protected void onResume() {
super.onResume();
if (mNfcAdapter != null) {
// 开启前台调度:优先接收NFC事件
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
}
@Override
protected void onPause() {
super.onPause();
if (mNfcAdapter != null) {
// 关闭前台调度
mNfcAdapter.disableForegroundDispatch(this);
}
}
Android 10(API 29)及以上系统对MifareClassic类做了限制:非系统应用无法直接访问 Mifare Classic 卡的完整功能(如密钥验证、数据读写),会抛出SecurityException。
解决方案:
IC 卡的扇区通常有密钥保护(Key A 和 Key B),默认密钥(如0xFFFFFFFFFFFF)仅适用于未加密的卡片。若卡片已被自定义加密(如校园卡、企业门禁卡),需获取对应密钥才能读取。
提示:部分卡片的公共扇区(如厂商信息区)可能使用默认密钥,可优先尝试读取这些区域。
Android NFC 读取 IC 卡的核心流程可概括为:配置权限与过滤规则→初始化 NFC 适配器→监听标签事件→解析标签数据。实际开发中需注意不同 IC 卡的协议差异(如 Mifare、ISO 14443)和系统限制(如 Android 10 + 的 Mifare 限制)。
若需读取加密卡数据,需提前获取密钥或通过合法渠道获取卡片数据格式;对于普通应用,可优先实现对公开数据(如卡片 ID、厂商信息)的读取,满足基础需求。
通过本文的步骤,你可以快速搭建一个基础的 IC 卡读取应用,后续可根据实际场景扩展功能(如数据解析、历史记录存储等)。