我正在尝试更新写入 NFC 设备的 URI。我本质上是从 Android 应用程序屏幕上的编辑文本中附加一些值。
但是,每当我移动 NFC 设备太快时,之前的 URI 值就会被删除,使设备处于不可用状态。这种情况很少发生,但确实会发生,特别是在我在几秒钟内进行多次 NFC 刷卡的情况下(同一台 Android 手机和同一台 NFC 设备)。
我包含了下面的示例代码(我故意保持 URI 操作代码完整,因为我可能在短时间内做了太多事情,这可能部分导致问题更频繁地发生)。
MainActivity.java:
package my.package.name;
import static my.package.name.NfcData.URI_PATTERN;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
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.util.Log;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity {
private NfcAdapter nfcAdapter;
private TextView myTextView;
private final String LOG_TAG = MainActivity.class.getName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myTextView = findViewById(R.id.myTextView);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
}
@Override
protected void onResume() {
super.onResume();
if (nfcAdapter != null) {
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE);
IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
IntentFilter writeTagFilters[] = new IntentFilter[]{tagDetected};
nfcAdapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
}
}
@Override
protected void onPause() {
super.onPause();
if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(this);
}
}
private void writeNfcDataToDevice(Intent intent, String myString) {
try {
Tag nfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
NfcData nfcData = new NfcData(intent);
if (URI_PATTERN.matcher(String.valueOf(nfcData.getUri())).matches()) {
String preExistingUrlStr = nfcData.getUri().toString();
Uri preExistingUri = Uri.parse(preExistingUrlStr);
Uri newUri = preExistingUri.buildUpon().clearQuery().appendQueryParameter(NfcData.MY_PARAM, myString).build();
NdefMessage uriNdefMessage = createUriNdefMessage(newUri);
Ndef ndef = Ndef.get(nfcTag);
ndef.connect();
ndef.writeNdefMessage(uriNdefMessage);
NdefMessage ndefMessage = ndef.getNdefMessage();
ndef.close();
Log.d(LOG_TAG, "NFC write successful !");
} else {
Log.d(LOG_TAG, "nSomething Went wrong. URI pattern does not match:" + nfcData.getUri());
}
}catch (Exception exception) {
Log.e(LOG_TAG, "nSomething Went wrong: " + exception.toString());
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null && (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction()))) {
writeNfcDataToDevice(intent, myTextView.getText().toString().trim());
}
}
public NdefMessage createUriNdefMessage(Uri uri) {
NdefRecord record = NdefRecord.createUri(uri);
NdefMessage msg = new NdefMessage(new NdefRecord[]{record});
return msg;
}
}
NfcData.java:
package my.package.name;
import android.content.Intent;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.os.Parcelable;
import java.util.regex.Pattern;
public final class NfcData {
private final Uri mUri;
public static final String MY_PARAM = "xyz";
static Pattern URI_PATTERN = Pattern.compile("^https?://www.mywebsite.com/ax/([0-9A-Fa-f]{12})(?:\\?.*)?$");
// both http and https protocol accepted
// domain name www.mywebsite.com
// string "/ax/"
// 12 compulsory characters that may be any digit from 0 to 9 or A through F or a through f (used to denote bluetooth address)
// Any text/characters beyond the above are optional
public Uri getUri() {
return mUri;
}
public NfcData(Intent intent) {
Uri uri = null;
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs != null) {
for (Parcelable parcelable : rawMsgs) {
if (!(parcelable instanceof NdefMessage)) continue;
NdefMessage msg = (NdefMessage) parcelable;
for (NdefRecord record : msg.getRecords()) {
uri = record.toUri();
if(uri != null {
this.mUri = uri;
break;
}
}
}
}
}
}
您的做法存在多个问题。
主要是你没有代码来处理这种情况。
try/catch
writeNdefMessage
只是记录错误,并不尝试恢复。
我用来从类似情况中恢复的一种方法是存储标签 ID,直到写入成功。虽然在使用真实 NFC 标签硬件的大多数非安全关键实例中不能保证标签 ID 是唯一的,但它足以重试写入。
例如在
writeNfcDataToDevice
do
将标签 ID 存储在全局变量中,并与当前 ID 进行比较,如果它们相同,则重写存储的 Ndef 消息,例如下面的伪代码:-
byte[] localTagID = nfcTag.getID();
if (localTagID == globalTagID && globalNdefMessage != null) {
// Recover from failed write
currentNdefMessage = globalNdefMessage;
} else {
// This is a new Tag, so read existing NdefMessage in to currentNdefMessage and append data.
...
}
try {
Ndef ndef = Ndef.get(nfcTag);
ndef.connect();
ndef.writeNdefMessage(currentNdefMessage);
ndef.close();
// no exception so was successfully written
// forget the ID and message
globalTagID = null;
globalNdefMessage = null;
} catch (Exception e) {
// failed to write
// store Tag ID
globalTagID = localTagID;
// store the Ndef Message to re-write
globalNdefMessage = currentNdefMessage;
// Tell the user to represent the Tag, so another attempt at writing the stored NdefMessage can be done.
}
这会导致代码出现其他问题:-
您正在主线程上执行
writeNfcDataToDevice
,writeNdefMessage
的Android文档说
这是一个 I/O 操作,将阻塞直到完成。不得从主应用程序线程调用它。
对于使用旧的
Intent
(enableForegroundDispatch
) NFC 工作方法的真实用户来说,写入数据时也非常不可靠。这是因为它会暂停和恢复您的应用程序以传送 NFC 标签数据,并且 NFC 系统在将数据传送到您的应用程序之前还会发出声音。
这导致用户在您的应用程序有机会执行任何操作之前就相信 NFC 操作已完成,并且通常他们会将标签从范围中删除,从而导致写入失败。
使用较新的
enableReaderMode
API 更好,因为它允许您关闭平台声音,并且标签数据在新线程中传递,因此您不需要自己做任何线程工作,也不必暂停并恢复您的应用程序来执行此操作。一旦写入成功,您就可以制作自己的通知声音而不是平台声音。因此,使用 enableReaderMode
可以解决代码中的另外两个问题。
的示例