Пустой тег записывается, если запись NFC не удалась на Android

Я пытаюсь обновить URI, записанный на устройстве NFC. По сути, я добавляю некоторое значение из текста редактирования на экране приложения 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;
                    }          
                }
            }
        }
    }
}

0
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Есть несколько проблем с тем, как вы это делаете.

Главное, что у вас нет кода для решения этой ситуации.

Функция try/catch writeNdefMessage просто регистрирует ошибку и не пытается ее восстановить.

Подход, который я использовал для восстановления после чего-то подобного, заключается в сохранении идентификатора тега до тех пор, пока запись не завершится успешно. Хотя уникальность идентификатора тега не гарантируется в большинстве случаев, не критичных к безопасности, с реальным оборудованием тегов NFC, его вполне достаточно для повторной попытки записи.

например в writeNfcDataToDevice сделать

Сохраните идентификатор тега в глобальной переменной и сравните его с текущим идентификатором, и если они совпадают, перезапишите сохраненное сообщение 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 в основной теме, и в документации Android для writeNdefMessage говорится

Это операция ввода-вывода, и она будет заблокирована до завершения. Его нельзя вызывать из основного потока приложения.

Также у Реальных пользователей, использующих старый Intent(enableForegroundDispatch) метод работы с NFC, очень ненадежно при записи данных. Это связано с тем, что оно приостанавливает и возобновляет работу вашего приложения для доставки данных тега NFC, а система NFC также издает звук перед доставкой данных в ваше приложение.

Это заставляет пользователей полагать, что операция NFC завершена до того, как ваше приложение сможет что-либо сделать, и довольно часто они удаляют тег из диапазона, что приводит к сбоям записи.

Гораздо лучше использовать более новый enableReaderMode API, потому что он позволяет вам отключить звук платформы, а данные тега доставляются в новом потоке, поэтому вам не нужно выполнять какую-либо работу с потоком самостоятельно, и не нужно приостанавливать и возобновлять работу. ваше приложение для этого. После успешной записи вы можете создать свой собственный звук уведомления вместо звука платформы. Таким образом, использование enableReaderMode решает две другие проблемы вашего кода.

Пример EnableReaderMode

Спасибо за подробное объяснение! Я также перешел на использование enableReaderMode API. Запись NFC гораздо более надежна и имеет более высокий уровень успеха при использовании этого API по сравнению с API enableForegroundDispatch.

Umang Mathur 02.07.2024 08:34

Другие вопросы по теме