27、权限与NFC:NFC权限、NFC读写权限、NFC前台分发
NFC 这个功能,说实话,在 Android 开发里一直是个「小众但关键」的存在。我做安全审计时,经常发现大家对 NFC 权限的理解停留在「加个 uses-feature 就完事」的阶段。但实际坑不少,尤其是 Android 10 之后,NFC 的权限模型和前台分发机制都变了。
今天我们就把它彻底讲透。
NFC 权限:你需要的其实不多
先看最基础的。NFC 权限声明很简单,就一个:
<uses-permission android:name="android.permission.NFC" />
嗯,就这一个。但注意,它属于 normal 级别权限,系统自动授予,不需要运行时申请。所以很多开发者觉得「那不就完了?」——其实不是。
真正容易出问题的是 <uses-feature> 声明。如果你在 Google Play 上架应用,这个声明会决定你的应用是否对没有 NFC 硬件的设备可见。
<uses-feature android:name="android.hardware.nfc" android:required="true" />
我个人习惯是:如果 NFC 只是辅助功能(比如扫码后顺便读个标签),我会设成 required="false"。这样没有 NFC 的手机也能下载,只是功能受限而已。
required="true",但代码里没有做运行时检测,用户在没有 NFC 的设备上安装后直接崩溃——这种问题我在项目里见过不止一次。
NFC 读写权限:别被名字骗了
很多人以为「读写权限」需要额外声明。其实 Android 并没有单独的 NFC 读权限或写权限。你只需要 android.permission.NFC 这一个就够了。
但为什么我还是要单独讲「读写权限」?因为真正的限制不在权限声明里,而在 Intent Filter 和前台分发 的配置上。
举个例子。你想让应用在后台也能读到 NFC 标签?对不起,Android 10 之后,后台 NFC 读取被严格限制。你必须让 Activity 处于前台,或者使用前台服务。
我曾经遇到一个项目:客户要求「手机锁屏时靠近 NFC 标签自动打卡」。我直接告诉他,这个需求在原生 Android 上做不到,除非用厂商定制接口。为什么?因为 NFC 的前台分发机制决定了——只有前台 Activity 才能处理 Tag 发现事件。
NFC 前台分发:核心机制
前台分发(Foreground Dispatch)是 NFC 开发里最重要的概念。说白了就是:让你的 Activity 优先处理 NFC 标签,而不是让系统去匹配 Intent Filter。
为什么要用前台分发?因为 Intent Filter 匹配太死板了。比如你只想处理特定类型的 NDEF 格式标签,但系统可能把其他标签也分发给你。前台分发让你可以动态控制。
实现步骤很简单,就三步:
- 创建
PendingIntent,用于系统回调 - 声明你要拦截的 Intent Filter 数组
- 在
onResume()中启用,在onPause()中禁用
看代码:
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
// 创建 PendingIntent,系统用它来启动当前 Activity
pendingIntent = PendingIntent.getActivity(
this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_IMMUTABLE
);
}
@Override
protected void onResume() {
super.onResume();
if (nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(
this, pendingIntent,
null, // IntentFilter 数组,传 null 表示接收所有 Tag
null // TechList 数组,传 null 表示接收所有技术类型
);
}
}
@Override
protected void onPause() {
super.onPause();
if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(this);
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// 处理 NFC 标签
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
// 读取标签数据...
}
}
onNewIntent() 里处理标签,而不是 onResume()。因为 onNewIntent() 只会在有新 Intent 时触发,而 onResume() 每次恢复都会执行,容易重复处理。
前台分发的优先级
这里有个关键点:前台分发的优先级高于 Intent Filter。也就是说,如果你的 Activity 启用了前台分发,那么所有 NFC 标签都会先经过它,而不是去匹配 AndroidManifest 里声明的 Intent Filter。
我画了一张图,帮你理解整个分发流程:
从图里可以清楚看到:前台分发是「截胡」机制。一旦启用,系统就不再走 Intent Filter 匹配流程了。这在你需要动态控制 NFC 行为时非常有用。
实际项目中的避坑指南
讲几个我踩过的坑:
- 不要忘记在 onPause 中禁用前台分发。否则你的 Activity 退到后台后,其他应用可能无法正常使用 NFC。我曾经因为这个被测试团队追着改了一个星期。
- Android 12 之后,PendingIntent 必须声明 FLAG_IMMUTABLE。否则会 crash。这是 Google 强制的安全要求。
- NFC 标签的读写操作必须在子线程执行。主线程操作 NFC 会导致 ANR。我习惯用
AsyncTask或者Coroutine来处理。 - 注意 NDEF 格式的兼容性。有些标签是 Mifare Classic,有些是 Type A/B。你的代码要能处理多种技术类型。
核心总结:
- NFC 权限只有一个
android.permission.NFC,属于 normal 级别 - 前台分发是处理 NFC 标签的首选方式,优先级高于 Intent Filter
- 前台分发必须在
onResume启用,onPause禁用 - Android 12+ 要求 PendingIntent 使用
FLAG_IMMUTABLE
好了,NFC 权限这块就这些。说实话,它比普通运行时权限简单得多,但细节不少。你只要记住「前台分发是核心」这句话,基本就能搞定大部分场景。