Android NFC-ndef.writeNdefMessage()引发IOException并擦除标签数据 [英] Android NFC - ndef.writeNdefMessage() throws IOException and erases tag data
问题描述
我的应用程序使用前台分派系统允许用户点击其NFC标签,以便对该标签执行读写操作.
如果用户正确点击标签(即,将其在手机上的正确位置点击并保持足够长的连接时间),效果很好,但是如果用户过早地物理移除标签,则会抛出ndef.writeNdefMessage(...)
IOException.
这意味着写操作失败,这很公平.但是真正的问题是,相同的失败操作也会从标记中删除整个ndef格式/消息!
我的代码围绕着高级NFC | Android开发人员页面(不幸的是,
我真的很想知道,在覆盖数据的过程中删除存储设备时,还会发生什么. 您编写的代码并不是真正的擦除"数据.它只是从标签存储器的开头就开始覆盖数据,当您中断写入时,标签将处于未定义状态. NFC标签仅支持一次存储一个NDEF消息.因此,当您开始编写新的NDEF消息时,旧的NDEF消息需要被覆盖.因此, 将从其第一块开始覆盖现有的NDEF消息.对于NTAG203,MIFARE Ultralight和MIFARE Ultralight C(顺便说一下,这是三种不同的标签类型),第一个块将位于块4周围. 如果写入过程被中断(例如,通过从阅读器字段中拉出标签),则仅会写入新消息的一部分(而旧消息的一部分可能会保留在标签上).由于旧消息和新消息都不完整,因此Android(就像其他NDEF阅读器一样)无法从标记中读取有效的NDEF消息,因此无法检测到任何NDEF消息.由于您还注册了 如果您的NDEF消息足够长,以至于用户在写入时实际上能够拉出标签,那么您就不能对拉出本身做很多事情(除非指示用户不要这样做). NFC标签也没有开箱即用的任何形式的保护. IE.当前没有标记可以可靠地存储旧的NDEF消息,直到完全写入新的NDEF消息为止. 您可能要做的是将旧的(或新的)NDEF消息(可能映射到标签ID)存储在应用程序中,并让用户在写入过程失败后重新开始写入过程.尽管如此,这仍需要用户的配合. 这可能是另一个选择:不要对关键数据使用NDEF,而应使用特定于应用程序的内存布局(或除NDEF之外). NTAG/MIFARE Ultralight在ISO 14443-3A(NFC-A)之上具有命令集,并且不支持ISO-DEP(ISO 14443-4).因此,您可以使用 其中 然后,您将使用类似的方法来读取和更新您的标签: 低级读写命令如下所示: 阅读: 写: My app uses the foreground dispatch system to allow a user to tap their NFC tag in order to perform a read-then-write operation on the tag. It works nicely if the user taps their tag properly (i.e., they tap it in the correct place on the phone and leave it connected for long enough), but if they physically remove the tag too early, then That means the write operation fails, which is fair enough. But the real problem is that the same failed operation also deletes the entire ndef formatting/message from the tag! My code is built around the snippets from the Advanced NFC | Android Developers page (as unfortunately the link to the ForegroundDispatch sample appears to be broken and there is no such sample project to import into Android Studio). Step 1. Here is the logcat/stacktrace output when the user first taps their NFC tag, but moves it away too soon: Step 2. Next, the same user taps the same tag again but it appears to no longer contain an ndef message (which I have confirmed by altering the code and checking that I am getting this issue with both devices I have tested with so far - a Samsung Galaxy Core Prime (a lower end phone) running Android 5.1.1 and a Samsung Galaxy A5 (a mid range phone) running Android 5.0.2. The NFC tags used by my app contain important information (i.e., inadvertently deleting the tag data is not an option!), so my questions are... Having done a lot of searching, I'm very surprised that this problem has not been discussed elsewhere, so if the problem isn't to do with my code, then could it be to do with the NFC tags I am using?... The tags I'm using are NXP MIFARE Ultralight (Ultralight C) - NTAG203 (Tag type: ISO 14443-3A). Some of these I bought from ebay, and some I bought from Rapid NFC (a reputable company), yet I seem to have this problem with all of them. Here is my complete code for the activity:
I really wonder what else you would expect to happen when you remove a storage device in the middle of overwriting its data. You code is not really "erasing" data. It simply starts overwriting the data from the beginning of the tag memory leaving the tag in an undefined state when you interrupt writing. An NFC tag only supports storing one NDEF message at a time. Consequently, when you start to write an new NDEF message, the old NDEF message needs to be overwritten. Thus, will overwrite the existing NDEF message starting at its first block. For NTAG203, MIFARE Ultralight and MIFARE Ultralight C (that's three different tag types by the way), this first block will be around block 4. If the write procedure is interrupted (e.g. by pulling the tag from the reader field), then only parts of the new message are written (and parts of the old message may remain on the tag). Since neither the old nor the new message are complete, Android (just as any other NDEF reader) cannot read a valid NDEF message from the tag and, therefore, does not detect any NDEF message. The tag is still detected by your app since you also registered for the If your NDEF message is that long that your users are actually able to pull the tag while writing there is not much you can do against the pulling itself (except for instructing the users not to do so). NFC tags also do not have any form of pulling protection out-of-the-box. I.e. there are currently no tags that will reliably store the old NDEF message until the new NDEF message was written completely. What you could possibly do is to store the old (or the new) NDEF message (possibly mapped to the tag ID) within your app and let the users restart the write procedure once it failed. Still, that would require user cooperation. That might be another option: Don't use NDEF for the critical data but use an application-specific memory layout instead (or in addition to NDEF). NTAG/MIFARE Ultralight have a command set on top of ISO 14443-3A (NFC-A) and do not support ISO-DEP (ISO 14443-4). Thus, you could use Where You would then use something like this to read and update your tag: Low-level read and write commands look like this: READ: WRITE: 这篇关于Android NFC-ndef.writeNdefMessage()引发IOException并擦除标签数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!为什么我的代码(见下文)会这样擦除标签数据?
ndef.writeNdefMessage(ndefMessageNew);
writeNdefMessage
然后将写入新的消息块,以用新数据替换旧数据. >
TECH_DISCOVERED
意向(不需要该标签包含有效的NDEF消息),因此您的应用仍然可以检测到该标签.我该如何解决根本问题,或者有可接受的解决方法?
尝试使用NfcA或IsoDep而不是Ndef值得吗?
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/MF Ultralight上有1个块) 4个字节).
x
中读取内容以找出哪个部分包含有效(最新)数据->部分s
.s
部分读取数据,并将其用作当前数据.s
= 1:第0部分;如果s
= 0:第1部分).x
.
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
});
ndef.writeNdefMessage(...)
throws an IOException.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()
returns 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)
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();
}
});
}
}
Why is my code (see below) erasing the tag data like this?
ndef.writeNdefMessage(ndefMessageNew);
writeNdefMessage
will then write the new message block for block replacing old data with new data.TECH_DISCOVERED
intent (which does not require the tag to contain a vaild NDEF message).How can I fix the underlying problem, or is there an acceptable workaround?
Would it be worth me trying to use NfcA or IsoDep rather than Ndef?
NfcA
(or MifareUltralight
) to directly read from/write to the tags using low-level commands. You could structure the tag memory in two sections that you use to store the old and the new data:
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
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).
x
to find out which section contains the vaild (newest) data -> section s
.s
and use it as current data.s
= 1: section 0; if s
= 0: section 1).x
with the new section number.
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
});