Android NFC - ndef.writeNdefMessage()抛出IOException并删除标记数据

时间:2016-03-28 20:06:20

标签: android tags nfc mifare ndef

我的应用程序使用前台调度系统允许用户点击他们的NFC标签,以便对标签执行读写操作。

如果用户正确点击他们的标签(即,他们在手机上的正确位置点击它并将其连接足够长时间),它可以很好地工作,但如果他们过早地物理删除标签,那么{{1}抛出IOException。

这意味着写操作失败,这是公平的。但真正的问题是同样失败的操作也从标签中删除整个ndef格式/消息!

我的代码是围绕Advanced NFC | Android Developers页面的代码段构建的(不幸的是,link to the ForegroundDispatch sample似乎已被破坏,并且没有可导入Android Studio的示例项目。)

步骤1。当用户首次点击他们的NFC标签时,这是logcat / stacktrace输出,但过早地将其移走:

ndef.writeNdefMessage(...)

第2步。接下来,同一个用户再次点击相同的标记,但它似乎不再包含ndef消息(我已通过更改代码并检查03-28 20:15:18.589 21278-21278/com.example.exampleapp E/NfcTestActivity: Tag error java.io.IOException at android.nfc.tech.Ndef.writeNdefMessage(Ndef.java:320) at com.example.exampleapp.NfcTestActivity.onNewIntent(NfcTestActivity.java:170) at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1224) at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:2946) at android.app.ActivityThread.performNewIntents(ActivityThread.java:2959) at android.app.ActivityThread.handleNewIntent(ActivityThread.java:2968) at android.app.ActivityThread.access$1700(ActivityThread.java:181) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1554) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6145) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194) 03-28 20:15:18.599 1481-17792/? E/SecNfcJni: nfaConnectionCallback: NFA_SELECT_RESULT_EVT error: status = 3 03-28 20:15:18.599 1481-1502/? E/SecNfcJni: reSelect: tag is not active 确认了该消息返回ndef.getCachedNdefMessage()):

null

到目前为止我使用的两款设备都遇到了这个问题 - 运行Android 5.1.1的三星Galaxy Core Prime (低端手机)和三星Galaxy A5 < / em>(中档手机)运行Android 5.0.2。

我的应用使用的NFC标签包含重要信息(即,无意中删除了标签数据不是一种选择!),所以我的问题是......

  1. 为什么我的代码(见下文)会像这样删除标签数据?
  2. 如何解决潜在问题,或者是否有可接受的解决方法?
  3. 我是否值得尝试使用NfcA或IsoDep而不是Ndef?
  4. 经过大量的搜索后,我很惊讶这个问题还没有在别处讨论过,所以如果问题与我的代码无关,那么可能与我正在使用的NFC标签有关?...

    我使用的标签是 NXP MIFARE Ultralight(Ultralight C) - NTAG203 (标签类型:ISO 14443-3A)。其中一些我从ebay购买,有些是我从Rapid NFC(一家声誉良好的公司)购买的,但我似乎对所有这些都有这个问题。

    以下是我完整的活动代码:

    03-28 20:15:27.499 21278-21278/com.example.exampleapp E/NfcTestActivity: Tag error
    java.lang.Exception: Tag was not ndef formatted: android.nfc.action.TECH_DISCOVERED
        at com.example.exampleapp.NfcTestActivity.onNewIntent(NfcTestActivity.java:124)
        at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1224)
        at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:2946)
        at android.app.ActivityThread.performNewIntents(ActivityThread.java:2959)
        at android.app.ActivityThread.handleNewIntent(ActivityThread.java:2968)
        at android.app.ActivityThread.access$1700(ActivityThread.java:181)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1554)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:145)
        at android.app.ActivityThread.main(ActivityThread.java:6145)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
    

1 个答案:

答案 0 :(得分:4)

我真的很想知道在覆盖数据的过程中删除存储设备时还会发生什么。

为什么我的代码(见下文)会像这样删除标签数据?

您的代码并非真正“删除”数据。它只是开始覆盖标签内存开头的数据,当你中断写入时,标签处于未定义状态。

NFC标签仅支持一次存储一条NDEF消息。因此,当您开始编写新的NDEF消息时,需要覆盖旧的NDEF消息。因此,

ndef.writeNdefMessage(ndefMessageNew);

将覆盖从第一个块开始的现有NDEF消息。对于NTAG203,MIFARE Ultralight和MIFARE Ultralight C(顺便说一下是三种不同的标签类型),第一个块将在块4附近。writeNdefMessage然后将写入新的消息块,用于用新数据替换旧数据。

如果写入过程被中断(例如,通过从读取器字段中拉出标签),则仅写入新消息的一部分(并且旧消息的一部分可以保留在标签上)。由于旧消息和新消息都不完整,因此Android(与任何其他NDEF读取器一样)无法从标记中读取有效的NDEF消息,因此不会检测到任何NDEF消息。您的应用仍会检测到该代码,因为您还注册了TECH_DISCOVERED意图(不要求代码包含虚拟NDEF消息)。

如何解决潜在问题,或者是否有可接受的解决方法?

如果您的NDEF消息很长,您的用户实际上可以在写入时拉动标记,那么您可以对拉动本身做很多事情(除非指示用户不这样做)。 NFC标签也没有开箱即用的任何形式的拉保护。即在完全写入新的NDEF消息之前,目前没有可靠地存储旧NDEF消息的标签。

您可能要做的是在应用程序中存储旧的(或新的)NDEF消息(可能映射到标记ID),并让用户在失败后重新启动写入过程。不过,这需要用户合作。

我是否值得尝试使用NfcA或IsoDep而不是Ndef?

这可能是另一种选择:不要将NDEF用于关键数据,而是使用特定于应用程序的内存布局(或者除了NDEF之外)。 NTAG / MIFARE Ultralight在ISO 14443-3A(NFC-A)之上设置了命令集,不支持ISO-DEP(ISO 14443-4)。因此,您可以使用NfcA(或MifareUltralight)使用低级命令直接读取/写入标记。您可以将标记内存分为两个部分,用于存储旧数据和新数据:

Block x: Flag indicating which section (1 or 2) contains the valid data
Block x+1: First block of section 1
Block x+2: Second block of section 1
[...]
Block x+m: Last block of section 1
Block x+m+1: First block of section 2
Block x+m+2: Second block of section 2
[...]
Block x+2*m: Last block of section 2

其中x是您的自定义内存结构的第一个块(您甚至可以在一些固定的NDEF消息之后启动该区域)并且m是块中每个部分的长度(NTAG上的1个块) / MF Ultralight有4个字节)。

然后,您可以使用类似的内容来阅读和更新您的代码:

  1. 从块x读取以找出哪个部分包含vaild(最新)数据 - &gt;第s节。
  2. 阅读s部分中的数据并将其用作当前数据。
  3. 将新数据写入其他部分(如果s = 1:第0部分;如果s = 0:部分1)。
  4. 如果数据已成功(并且完全)写入,请使用新的部分编号更新数据块x
  5. 低级读写命令如下所示:

    • READ:

      byte[] result = nfcA.transceive(new byte[] {
              (byte)0x30,  // READ
              (byte)(blockNumber & 0x0ff)
      });
      
    • WRITE:

      byte[] result = nfcA.transceive(new byte[] {
              (byte)0xA2,  // WRITE
              (byte)(blockNumber & 0x0ff),
              byte0, byte1, byte2, byte3
      });