gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 12:48:51 37 4
gpt4 key购买 nike

我的应用使用前台调度系统允许用户轻触他们的 NFC 标签,以便对标签执行先读后写操作。

如果用户正确点击他们的标签(即,他们在手机上的正确位置点击它并保持连接足够长的时间),它会很好地工作,但如果他们过早地移除标签,那么 ndef .writeNdefMessage(...) 抛出 IOException。

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

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

第 1 步。这是用户首次轻触他们的 NFC 标签但过早将其移开时的 logcat/stacktrace 输出:

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

第 2 步。 接下来,同一个用户再次点击同一个标签,但它似乎不再包含 ndef 消息(我已通过更改代码并检查 ndef. getCachedNdefMessage() 返回 null):

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)

到目前为止我测试过的两台设备都遇到了这个问题 - 运行 Android 5.1.1 的 Samsung Galaxy Core Prime(低端手机)和 Samsung Galaxy A5(中档手机)运行 Android 5.0.2。

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

  1. 为什么我的代码(见下文)会像这样删除标签数据?
  2. 如何解决根本问题,或者是否有可接受的解决方法?
  3. 我尝试使用 NfcA 或 IsoDep 而不是 Ndef 是否值得?

经过大量搜索后,我很惊讶这个问题没有在其他地方讨论过,所以如果问题不是与我的代码有关,那么是否与我使用的 NFC 标签有关?...

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

这是我的 Activity 的完整代码:

package com.example.exampleapp;

import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
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.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;

public class NfcTestActivity extends AppCompatActivity {

private static String LOG_TAG = NfcTestActivity.class.getSimpleName();
private static int SUCCESS_COUNT = 0;
private static int FAILURE_COUNT = 0;

private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
private IntentFilter[] intentFiltersArray;
private String[][] techListsArray;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc_test);
getSupportActionBar().setDisplayShowHomeEnabled(true);

nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {

makeToast("NFC not available!", Toast.LENGTH_LONG);
finish();

}
else {

//makeToast("NFC available");

pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*"); /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[]{
ndef
};

techListsArray = new String[][]{
new String[]{
Ndef.class.getName()
}
};
}

}

@Override
public void onPause() {
super.onPause();

if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(this);
}

}

@Override
public void onResume() {
super.onResume();

if (nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
}

}

public void onNewIntent(Intent intent) {

Ndef ndef = null;

try {

String action = intent.getAction();
//makeToast("action: " + action);

if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {

throw new Exception("Tag was not ndef formatted: " + action); // line #124

}
else {

Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent

ndef = Ndef.get(tag);
//makeToast("ndef: " + ndef);

if (ndef == null) {

throw new Exception("ndef == null!");

}
else {

// Connect
ndef.connect();

// Get cached message
NdefMessage ndefMessageOld = ndef.getCachedNdefMessage();

if (ndefMessageOld == null) {

throw new Exception("No ndef message on tag!");

}
else {

// Get old records
NdefRecord[] ndefRecordsOld = ndefMessageOld.getRecords();
int numRecords = (ndefRecordsOld == null) ? 0 : ndefRecordsOld.length;

// Create/copy 'new' records

NdefRecord[] ndefRecordsNew = new NdefRecord[numRecords];
for (int i = 0; i < numRecords; i++) {
ndefRecordsNew[i] = ndefRecordsOld[i];
}

// Create new message
NdefMessage ndefMessageNew = new NdefMessage(ndefRecordsNew);

// Write new message
ndef.writeNdefMessage(ndefMessageNew); // line #170

SUCCESS_COUNT++;

// Report success
String msg = "Read & wrote " + numRecords + " records.";
makeToast(msg);
Log.d(LOG_TAG, msg);

}

}

}

}
catch(Exception e) {

FAILURE_COUNT++;

Log.e(LOG_TAG, "Tag error", e);
makeToast("Tag error: " + e, Toast.LENGTH_LONG);

}
finally {

try {
if (ndef != null) {
ndef.close();
}
}
catch(Exception e) {
Log.e(LOG_TAG, "Error closing ndef", e);
makeToast("Error closing ndef: " + e, Toast.LENGTH_LONG);
}

makeToast("Successes: " + SUCCESS_COUNT + ". Failures: " + FAILURE_COUNT);

}

}

private void makeToast(final String msg) {

makeToast(msg, Toast.LENGTH_SHORT);

}

private void makeToast(final String msg, final int duration) {

new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {

Toast.makeText(NfcTestActivity.this, msg, duration).show();

}
});
}

}

最佳答案

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

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

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

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

ndef.writeNdefMessage(ndefMessageNew);

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

如果写入过程被中断(例如,通过从读取器字段中拉出标签),则只会写入部分新消息(而部分旧消息可能会保留在标签上)。由于旧消息和新消息都不完整,Android(就像任何其他 NDEF 阅读器一样)无法从标签中读取有效的 NDEF 消息,因此不会检测到任何 NDEF 消息。该标签仍会被您的应用检测到,因为您还注册了 TECH_DISCOVERED Intent (不需要标签包含有效的 NDEF 消息)。

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

如果您的 NDEF 消息太长以至于您的用户实际上能够在写入时拉取标签,那么您对拉取本身无能为力(除了指示用户不要这样做)。 NFC 标签也没有任何形式的开箱即用保护。 IE。目前没有标签可以可靠地存储旧的 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 dataBlock x+1: First block of section 1Block x+2: Second block of section 1[...]Block x+m: Last block of section 1Block x+m+1: First block of section 2Block x+m+2: Second block of section 2[...]Block x+2*m: Last block of section 2

Where x is the first block of your custom memory structure (you could even start that area after some fixed NDEF message) and m is the length of each section in blocks (1 block on NTAG/MF Ultralight has 4 bytes).

You would then use something like this to read and update your tag:

  1. Read from block x to find out which section contains the vaild (newest) data -> section s.
  2. Read the data from section s and use it as current data.
  3. Write the new data to the other section (if s = 1: section 0; if s = 0: section 1).
  4. If the data was written successfully (and completely), update block x with the new section number.

Low-level read and write commands look like this:

  • READ:

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

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

关于Android NFC - ndef.writeNdefMessage() 抛出 IOException 并删除标签数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36270238/

37 4 0