为何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
相关产品推荐
相关产品推荐

