Kitkat 是否支持 NFC NDEF?

Is NFC NDEF supported on Kitkat?

我正在尝试在 KitKat 上使用 MobiPrint 设备 运行 读取 NFC NDEF 消息。

我按照 Docs 中的说明进行操作,但我似乎无法在 Kitkat 上使用它。

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.pos">

    <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:name=".App"
        android:allowBackup="true"
        android:fullBackupContent="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Pos">
        <activity
            android:name=".ui.views.main.MainActivity"
            android:theme="@style/Theme.Pos.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
   
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
        </activity>
    </application>
</manifest>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private var adapter: NfcAdapter? = null
    private var pendingIntent: PendingIntent? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        adapter = NfcAdapter.getDefaultAdapter(this)

        pendingIntent = PendingIntent.getActivity(this, 0,
            Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
    }

    override fun onResume() {
        super.onResume()
        adapter?.enableForegroundDispatch(this, pendingIntent, null, null)
    }

    override fun onPause() {
        super.onPause()
        adapter?.disableForegroundDispatch(this)
    }

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

        val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
        Log.d("NFC_NDEF_MESSAGES", rawMessages.toString())
        Log.d("NFC_NDEF_ACTION", intent.action.toString())
    }
}

Logcat 消息

带有 Kitkat 的 MobiPrint 设备:

NFC_NDEF_MESSAGES: null NFC_NDEF_ACTION: android.nfc.action.TAG_DISCOVERED

S7 Active with Marshmallow:

NFC_NDEF_MESSAGES: [Landroid.os.Parcelable;@6405e69 NFC_NDEF_ACTION: android.nfc.action.NDEF_DISCOVERED

Kitkat 不支持 NDEF 吗?

NXP MIFARE Classic 系列 NFC 卡不符合 NFC 论坛标准(规范早于 NFC 标准)

因此某些硬件不支持 NXP MIFARE Classic 系列 NFC 卡,如果您查看 Android 文档 https://developer.android.com/reference/android/nfc/tech/MifareClassic

Implementation of this class on a Android NFC device is optional. If it is not implemented, then MifareClassic will never be enumerated in Tag#getTechList. If it is enumerated, then all MifareClassic I/O operations will be supported, and Ndef#MIFARE_CLASSIC NDEF tags will also be supported. In either case, NfcA will also be enumerated on the tag, because all MIFARE Classic tags are also NfcA.

所以对这种类型的卡的支持在 Android 中是可选的,正如它所说的,如果它被支持,那么更高级别的 NDEF 协议将在它之上被支持(但在你的情况下它不是在 MifareClassic 级别受支持,那么 NDEF 将不会在顶部受支持)

这可能是您收到 android.nfc.action.TAG_DISCOVERED 消息的原因,因为它来自低级别 NfcA,这是一个非常低的协议,MifareClassic 与其他 NFC 规范卡共有。

让 MobiPrint 设备读取 NFC 标签的解决方案是使用购买不同的 NFC 标签,不幸的是,这与 Android 版本无关(甚至某些后来的 Android 硬件也无法读取他们),我建议使用 NFC 论坛规范类型标签。 接近匹配的是 NXP TAG 216 卡(NFC 论坛类型 2)或 NXP Desfire(NFC 论坛类型 4)

出于某种原因,它将其检测为 TAG_DISCOVERED(通常暗示缺少 NDEF 格式)。即使检测不到MifareUltralight,也可以用NfcA,CC在第3页,data-area从第4页开始(无论是格式化还是读取格式).当标签在更高版本的 Android 上可读时,这些绝对应该算作“可读”。

它只需要一个 KitKat 实现,它处理 MifareUltralightNfcA

可以在不检查 API 级别的情况下回退:

when (intent.action.toString()) {
    android.nfc.action.NDEF_DISCOVERED -> {
        /* handle NDEF messages (business as usual). */
    }
    android.nfc.action.TAG_DISCOVERED -> {

        /* Check if NDEF format is present and read it. */
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
        MifareUltralight mifare = MifareUltralight.get(tag)
        mifare.connect()
        ...
        mifare.close()

        /* When MifareUltralight is null, try NfcA: */
        // NfcA nfcA = NfcA.get(tag)
    }
    else -> {
        print("unknown intent action")
    }
}

MifareUltralight 知道页面,NfcA 相当低级,只知道 ByteArray 有效负载。
而不是建议不同的标签(可能会触发相同的标签) Intent,只需要相应地处理),可以改为提高 minApiLevel 并完全停止支持 KitKat(Intent 仍然可能为空标签触发)。我有 NTAG216 和至少一个物理 API 22 设备 - 看看哪个 Intent 它会 实际上 触发,但没有时间。需要物理 API 19 设备才能正确重现这一点。