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

为何NFC适配PendingIntent.FLAG_MUTABLE而非FLAG_IMMUTABLE?

NFC应用PendingIntent标记位问题解答

问题核心原因

你的问题本质是NFC前台调度(Foreground Dispatch)对PendingIntent的特殊要求:

  • NFC系统检测到标签时,需要修改你创建的PendingIntent中的Intent对象,注入android.nfc.action.NDEF_DISCOVERED动作以及EXTRA_TAG等核心数据。
  • 当使用FLAG_IMMUTABLE时,该标记会让Intent变为不可修改状态,NFC服务无法向其中添加这些必要信息,导致onNewIntent中拿到的Intent缺少关键数据(表现为Action为null、TAG对象获取失败),最终引发崩溃。
  • 而FLAG_MUTABLE允许系统修改Intent,因此NFC服务能正常注入数据,功能可以正常运行。

关于文档推荐IMMUTABLE的说明

官方文档推荐使用FLAG_IMMUTABLE是通用安全原则——大多数PendingIntent场景不需要被系统修改,不可变的Intent能避免潜在的安全风险。但NFC前台调度属于必须让系统修改Intent内容的特殊场景,此时使用FLAG_MUTABLE是合理且必要的。

兼容处理建议

如果需要兼容Android 12以下版本,可以通过API级别判断来设置标记位:

import android.os.Build;
// ...
int pendingIntentFlags;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    pendingIntentFlags = PendingIntent.FLAG_MUTABLE;
} else {
    pendingIntentFlags = 0; // 低版本不需要设置这两个标记
}
pendingIntent = PendingIntent.getActivity(this, 0, 
    new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 
    pendingIntentFlags);

相关代码

MainActivity.java

import androidx.appcompat.app.AppCompatActivity;

import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity {
    private NfcAdapter nfcAdapter;
    private PendingIntent pendingIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        nfcAdapter = NfcAdapter.getDefaultAdapter(this);

        if (nfcAdapter == null) {
            Toast.makeText(this, "不支持NFC的设备", Toast.LENGTH_LONG).show();
        } else if (!nfcAdapter.isEnabled()) {
            Toast.makeText(this, "NFC已关闭", Toast.LENGTH_LONG).show();
            startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
        }

        pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (nfcAdapter != null) {
            nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (nfcAdapter != null) {
            nfcAdapter.disableForegroundDispatch(this);
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d("INTENT ACTION", intent.getAction());
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        if (tag != null) {
            Ndef ndef = Ndef.get(tag);
            if (ndef == null) {
                Toast.makeText(this, "空NFC标签", Toast.LENGTH_SHORT).show();
                return;
            }

            try {
                ndef.connect();

                NdefMessage ndefMessage = ndef.getNdefMessage();
                if (ndefMessage != null) {
                    NdefRecord[] records = ndefMessage.getRecords();
                    for (NdefRecord record : records) {
                        if (record.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)) {
                            String text = readText(record);
                            // TODO: 发送数据到Google Sheets
                            Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            } catch (IOException | FormatException e) {
                e.printStackTrace();
            } finally {
                try {
                    ndef.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } else {
            Toast.makeText(this, "损坏的NFC标签", Toast.LENGTH_LONG).show();
        }
    }

    private String readText(NdefRecord record) throws UnsupportedEncodingException {
        byte[] payload = record.getPayload();

        String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
        int languageCodeLength = payload[0] & 63;

        return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
    }
}

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.NFC"/>
    <uses-feature android:name="android.hardware.nfc" android:required="true"/>
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.APPNFC"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

内容的提问来源于stack exchange,提问作者M. Cahyna

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 04:55:59