Android Kotlin开发:NFC标签URL如何在应用内打开而非跳转Chrome
核心问题定位
- 你虽然构建了前台调度所需的
pendingIntent、intentFiltersArray、techListsArray参数,但从未调用nfcAdapter.enableForegroundDispatch()向系统注册事件优先处理权限。App在前台时没有NFC事件的拦截优先级,系统会直接把NFC标签携带的URL事件分发给默认处理URL的Chrome浏览器。 - 现有IntentFilter配置无法匹配URL类型的NDEF标签:你仅通过
addDataType("*/*")配置了MIME类型NDEF事件的捕获规则,但URL类NDEF标签触发的是携带URI数据的ACTION_NDEF_DISCOVERED事件,该规则对这类事件完全无效。 processIntent方法仅获取了原始NDEF消息数组,没有完成URL解析、应用内打开的核心逻辑,就算成功捕获事件也无法触发预期行为。
具体修复步骤
- 补全前台调度的注册/反注册逻辑
在onResume生命周期中调用enableForegroundDispatch,把提前构建的PendingIntent、IntentFilter数组、TechList数组注册给系统;在onPause中调用disableForegroundDispatch反注册,避免页面不可见时抢占NFC事件、引发内存泄漏。 - 修正IntentFilter的匹配规则
针对常用的http/https类型URL NFC标签,为ACTION_NDEF_DISCOVERED增加对应data scheme的匹配规则,替换原来仅匹配MIME类型的无效配置。 - 适配Android高版本的PendingIntent要求
构建PendingIntent时必须添加FLAG_IMMUTABLE(无特殊修改需求的场景),否则Android 12及以上版本会直接抛出异常。 - 补全NDEF解析与应用内打开逻辑
从NDEF消息中提取URI记录的URL地址,跳转到应用内置的WebView页面加载该地址,而非发送系统级的ACTION_VIEW意图。
修正后的参考代码
class MainActivity : AppCompatActivity() { private lateinit var nfcAdapter: NfcAdapter private lateinit var pendingIntent: PendingIntent private lateinit var intentFiltersArray: Array<IntentFilter> private lateinit var techListsArray: Array<Array<String>> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化NfcAdapter,无NFC硬件时直接返回 nfcAdapter = NfcAdapter.getDefaultAdapter(this) ?: run { Log.d("NFC supported", "false") return } Log.d("NFC supported", "true") Log.d("NFC enabled", nfcAdapter.isEnabled.toString()) // 构建前台调度用的PendingIntent,适配Android 12+的Flag要求 val intent = Intent(this, javaClass).apply { addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) } pendingIntent = PendingIntent.getActivity( this, 0, intent, PendingIntent.FLAG_IMMUTABLE ) // 配置IntentFilter:同时匹配MIME类型NDEF、http/https URI类型NDEF val ndefMimeFilter = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply { try { addDataType("*/*") } catch (e: IntentFilter.MalformedMimeTypeException) { throw RuntimeException("MIME类型配置错误", e) } } val ndefUriFilter = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply { addDataScheme("http") addDataScheme("https") } intentFiltersArray = arrayOf(ndefMimeFilter, ndefUriFilter) // 配置TechList,覆盖常见NFC标签支持的技术类型 techListsArray = arrayOf( arrayOf(NfcA::class.java.name), arrayOf(NfcB::class.java.name), arrayOf(NfcF::class.java.name), arrayOf(NfcV::class.java.name), arrayOf(IsoDep::class.java.name), arrayOf(Ndef::class.java.name), arrayOf(NdefFormatable::class.java.name) ) // 处理App被NFC事件冷启动的场景 intent?.let { processIntent(it) } } override fun onResume() { super.onResume() // 前台页面可见时,注册优先NFC调度权限 nfcAdapter.enableForegroundDispatch( this, pendingIntent, intentFiltersArray, techListsArray ) } override fun onPause() { super.onPause() // 页面不可见时,反注册NFC调度,避免影响其他应用正常使用 if (nfcAdapter.isEnabled) { nfcAdapter.disableForegroundDispatch(this) } } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) // 处理App在前台时收到的NFC事件 processIntent(intent) } private fun processIntent(checkIntent: Intent) { when (checkIntent.action) { NfcAdapter.ACTION_NDEF_DISCOVERED -> { val rawMessages = checkIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES) ?: return // 解析第一层NDEF记录 val ndefMessage = rawMessages[0] as NdefMessage val ndefRecord = ndefMessage.records[0] // 匹配URI类型的NDEF记录 if (ndefRecord.tnf == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.type, NdefRecord.RTD_URI)) { val uri = ndefRecord.toUri() // 跳转到应用内部的WebView页面加载URL,不触发外部浏览器 val webIntent = Intent(this, InternalWebActivity::class.java).apply { putExtra("target_url", uri.toString()) } startActivity(webIntent) } } } } }
注意:你需要自行实现
InternalWebActivity,在页面内部放置WebView组件加载传入的URL,完全避免跳出到Chrome等外部浏览器。
额外注意事项
- 不要在Manifest中为MainActivity配置
android.intent.action.VIEW的URL相关intent-filter,否则会导致每次打开App都弹出应用选择框,且无法保证前台调度的优先级。 - 如果你的NFC标签包含非http/https的自定义scheme,需要把对应scheme补充到
ndefUriFilter的addDataScheme规则中,否则无法匹配对应事件。 - 只要正确注册了前台调度,Android系统会优先把NFC事件传给前台应用,不会分发给其他注册了默认处理规则的应用。
内容的提问来源于stack exchange,提问作者Tomas Mota
相关产品推荐
相关产品推荐

