Android NFC读卡应用配置:禁止关闭自启、实现正常前台读卡
问题根因
- AndroidManifest中给scanActivity配置了全局
NDEF_DISCOVERED意图过滤器,这是应用关闭时碰标签自动启动、扫标签时重复创建应用实例的核心原因:全局意图过滤器属于系统级匹配规则,只要扫到对应类型标签,无论应用是否在前台都会尝试唤起匹配的Activity。 - 代码存在两处逻辑错误:一是
onNewIntent回调中传入的是旧的getIntent()而非回调返回的新标签Intent,永远读不到新扫描的内容;二是前台调度的IntentFilter配置错误,ACTION_TAG_DISCOVERED是最低优先级的兜底动作,和Manifest中配置的NDEF_DISCOVERED动作不匹配,导致前台拦截失效,系统直接走全局唤起逻辑。 - Android 12及以上版本中,NFC前台调度使用的
PendingIntent需要添加FLAG_MUTABLE标识,否则会抛出异常无法接收标签数据。 - 原生NDEF文本记录的Payload首字节为状态位,包含编码格式、语言码长度信息,直接转字符串会出现乱码、多余字符问题。
修复方案
第一步:修改AndroidManifest.xml配置
删除scanActivity节点下所有NFC相关的intent-filter配置,仅保留NFC权限声明,从根源上避免应用未运行时被标签自动唤起。
权限部分仅保留如下代码:
<uses-permission android:name="android.permission.NFC"/>
同时给scanActivity配置singleTop启动模式,避免重复创建实例打乱原有Activity栈:
<activity android:name=".scanActivity" android:launchMode="singleTop" android:exported="true"> <!-- 不要在此处添加任何NFC相关intent-filter --> </activity>
第二步:修复scanActivity读卡逻辑
修复后逻辑仅在scanActivity处于前台时拦截NFC标签事件,不会触发其他Activity启动,也不会在应用后台/关闭时响应标签。完整修复代码如下:
public class scanActivity extends AppCompatActivity { private TextView scanView; private NfcAdapter nfcAdapter; private PendingIntent pendingIntent; private IntentFilter[] readFilters; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scan); scanView = findViewById(R.id.scanView); nfcAdapter = NfcAdapter.getDefaultAdapter(this); // 配置PendingIntent,适配Android12+版本规则 Intent intent = new Intent(this, getClass()); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); int pendingFlag = PendingIntent.FLAG_UPDATE_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { pendingFlag |= PendingIntent.FLAG_MUTABLE; } pendingIntent = PendingIntent.getActivity(this, 0, intent, pendingFlag); // 配置前台调度过滤器,仅拦截纯文本类型NDEF标签 try { IntentFilter ndefTextFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); ndefTextFilter.addDataType("text/plain"); readFilters = new IntentFilter[]{ndefTextFilter}; } catch (IntentFilter.MalformedMimeTypeException e) { e.printStackTrace(); } readTag(getIntent()); } private void enableForegroundRead() { if (nfcAdapter != null) { nfcAdapter.enableForegroundDispatch(this, pendingIntent, readFilters, null); } } private void disableForegroundRead() { if (nfcAdapter != null) { nfcAdapter.disableForegroundDispatch(this); } } @Override protected void onResume() { super.onResume(); enableForegroundRead(); } @Override protected void onPause() { super.onPause(); disableForegroundRead(); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); // 替换当前Activity持有的Intent为新的标签Intent,再执行读取 setIntent(intent); readTag(intent); } private void readTag(Intent intent) { // 过滤非NDEF标签动作,避免空数据异常 if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) { return; } Parcelable[] messages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); scanView.setText(""); if (messages != null) { for (Parcelable message : messages) { NdefMessage ndefMessage = (NdefMessage) message; for (NdefRecord record : ndefMessage.getRecords()) { if (record.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)) { scanView.append("扫描成功,标签内容:"); byte[] payload = record.getPayload(); // 正确解析NDEF文本编码,跳过状态位和语言码 String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; int languageCodeLength = payload[0] & 0x3F; try { String tagContent = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); scanView.append(tagContent); // 可在此处添加数据库写入、进度更新等业务逻辑 } catch (UnsupportedEncodingException e) { e.printStackTrace(); scanView.append("标签解析失败"); } scanView.append("\n"); } } } } } }
最终效果
- 应用在scanActivity前台时碰标签,不会重启应用、不会跳转无关页面,直接在当前页读取展示标签内容,原有Activity栈完全保留
- 应用在其他页面、后台、完全关闭状态下碰标签,系统不会自动唤起应用,不会产生误触发
- 不会重复创建Activity实例,符合单页面读卡的最低要求
内容的提问来源于stack exchange,提问作者Leonardo Colli
相关产品推荐
相关产品推荐

