如果 Android 上 NFC 写入失败,则写入空标签

问题描述 投票:0回答:1

我正在尝试更新写入 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;
                    }          
                }
            }
        }
    }
}

android nfc
1个回答
0
投票

您的做法存在多个问题。

主要是你没有代码来处理这种情况。

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
可以解决代码中的另外两个问题。

enableReaderMode

的示例
© www.soinside.com 2019 - 2024. All rights reserved.