如何在 Android 中读取 NFC“TAG”

How to read an NFC 'TAG' in Android

提问人:Antonio 提问时间:11/2/2023 更新时间:11/2/2023 访问量:57

问:

查看那里的少量文档,包括相当稀疏的文档和我能找到的几个代码示例,我实现了以下 Activity,该活动应该从另一台安装了允许通过 NFC 发短信的应用程序的 Android 设备上获取一条简单的短信。

const val MIME_TEXT_PLAIN = "text/plain"

class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.simpleName
    private lateinit var binding: ActivityMainBinding
    private var nfcAdapter: NfcAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Inizializzazione del NfcAdapter
        nfcAdapter = NfcAdapter.getDefaultAdapter(this)


        // Verifica se il dispositivo supporta l'NFC
        if (nfcAdapter == null) {
            Log.w(TAG, "NFC non supportato!")
            // Il dispositivo non supporta l'NFC
            binding.nfcStatusText.text = "No NFC on this device."
        } else {
            // Imposta il click listener per il pulsante NFC
            binding.nfcButton.setOnClickListener {
                // Avvia l'attivazione dell'NFC
                enableNfc()
            }
        }
    }

    private fun enableNfc() {
        Log.w(TAG, "Abilito l'NFC...", )
        // Controlla se l'NFC è abilitato sul dispositivo
        if (!nfcAdapter?.isEnabled!!) {
            // Se l'NFC è disabilitato, mostra un messaggio all'utente per attivarlo
            binding.nfcStatusText.text = "Turn On NFC"
        } else {
            // L'NFC è già abilitato, attende il rilevamento del dispositivo NFC
            binding.nfcStatusText.text = "Searching..."
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)

        // also reading NFC message from here in case this activity is already started in order
        // not to start another instance of this activity
        receiveMessageFromDevice(intent)
    }

    override fun onResume() {
        super.onResume()

        // foreground dispatch should be enabled here, as onResume is the guaranteed place where app
        // is in the foreground
        enableForegroundDispatch(this, this.nfcAdapter)
        receiveMessageFromDevice(intent)
    }

    override fun onPause() {
        super.onPause()
        disableForegroundDispatch(this, this.nfcAdapter)
    }

    private fun receiveMessageFromDevice(intent: Intent) {
        val action = intent.action
//        if (NfcAdapter.ACTION_NDEF_DISCOVERED == action) {
        if (NfcAdapter.ACTION_TAG_DISCOVERED == action || NfcAdapter.ACTION_TECH_DISCOVERED == action || NfcAdapter.ACTION_NDEF_DISCOVERED == action) {

            val ndefMessage = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
            Log.d(TAG, "Parcelables data: $ndefMessage")

            if (!ndefMessage.isNullOrEmpty()) {
                val ndefRecord = (ndefMessage[0] as NdefMessage).records[0]
                val message = ndefRecord.payload.decodeToString()
                binding.nfcStatusText.text = "Message from tag NFC: $message"
            } else {
                binding.nfcStatusText.text = "No message on tag NFC"
            }
        }
    }


    // Foreground dispatch holds the highest priority for capturing NFC intents
    // then go activities with these intent filters:
    // 1) ACTION_NDEF_DISCOVERED
    // 2) ACTION_TECH_DISCOVERED
    // 3) ACTION_TAG_DISCOVERED

    // always try to match the one with the highest priority, cause ACTION_TAG_DISCOVERED is the most
    // general case and might be intercepted by some other apps installed on your device as well

    // When several apps can match the same intent Android OS will bring up an app chooser dialog
    // which is undesirable, because user will most likely have to move his device from the tag or another
    // NFC device thus breaking a connection, as it's a short range

    private fun enableForegroundDispatch(activity: AppCompatActivity, adapter: NfcAdapter?) {

        // here we are setting up receiving activity for a foreground dispatch
        // thus if activity is already started it will take precedence over any other activity or app
        // with the same intent filters

        val intent = Intent(this, javaClass)
        intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP

        val pendingIntent = PendingIntent.getActivity(
            this, 0, intent,
            PendingIntent.FLAG_IMMUTABLE
        )

        val filters = arrayOfNulls<IntentFilter>(1)
        val techList = arrayOf<Array<String>>()

        filters[0] = IntentFilter()
        with(filters[0]) {
            this?.addAction(NfcAdapter.ACTION_TAG_DISCOVERED)//ACTION_NDEF_DISCOVERED
            this?.addCategory(Intent.CATEGORY_DEFAULT)
            try {
                this?.addDataType(MIME_TEXT_PLAIN)
            } catch (ex: IntentFilter.MalformedMimeTypeException) {
                throw RuntimeException("Check your MIME type")
            }
        }

        adapter?.enableForegroundDispatch(activity, pendingIntent, filters, techList)
    }

    private fun disableForegroundDispatch(activity: AppCompatActivity, adapter: NfcAdapter?) {
        adapter?.disableForegroundDispatch(activity)
    }
}

问题是,我在第二台Android设备上安装的应用程序写入此设备时,会给我一个错误,并且不允许我写入该设备。

我不明白我的代码有什么问题,我也在清单中放置了以下权限并声明了以下意图过滤器:

<uses-permission android:name="android.permission.NFC" />

 <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain" />
 </intent-filter>
安卓 Kotlin NFC

评论


答:

1赞 Andrew 11/2/2023 #1

问题是你不了解NFC是如何工作的,NFC硬件有多种工作模式,你正试图让两种不兼容的模式相互通信。

更多细节在这里

基本上,当一台设备需要处于读/写模式,而另一台设备需要处于主机卡仿真模式时,两台设备都处于读/写模式。

我会 https://github.com/underwindfall/NFCAndroid 结帐,因为这是一个示例应用程序,用于将其中一个设备切换到 HCE 模式。