You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android NFC读卡应用配置:禁止关闭自启、实现正常前台读卡

问题根因
  1. AndroidManifest中给scanActivity配置了全局NDEF_DISCOVERED意图过滤器,这是应用关闭时碰标签自动启动、扫标签时重复创建应用实例的核心原因:全局意图过滤器属于系统级匹配规则,只要扫到对应类型标签,无论应用是否在前台都会尝试唤起匹配的Activity。
  2. 代码存在两处逻辑错误:一是onNewIntent回调中传入的是旧的getIntent()而非回调返回的新标签Intent,永远读不到新扫描的内容;二是前台调度的IntentFilter配置错误,ACTION_TAG_DISCOVERED是最低优先级的兜底动作,和Manifest中配置的NDEF_DISCOVERED动作不匹配,导致前台拦截失效,系统直接走全局唤起逻辑。
  3. Android 12及以上版本中,NFC前台调度使用的PendingIntent需要添加FLAG_MUTABLE标识,否则会抛出异常无法接收标签数据。
  4. 原生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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 06:03:19