Оставайтесь в текущей активности после приема NFC

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

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

Я не совсем уверен, почему это так. Я бы хотел, чтобы он отклонял любые нажатия, если пользователь уже не участвует в этом действии, и, если они есть, оставаться в этом действии и обрабатывать намерение NFC.

Вот манифест:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".Timer" />
<activity android:name=".AddSlaves"
          android:label="Add Slave Devices"
          android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>
<activity android:name=".JoinSrv"
          android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>

Вот класс отправителя:

public class JoinSrv extends Activity implements NfcAdapter.OnNdefPushCompleteCallback, NfcAdapter.CreateNdefMessageCallback {
    //The array lists to hold our messages
    private ArrayList<String> messagesToSendArray = new ArrayList<>();
    private ArrayList<String> messagesReceivedArray = new ArrayList<>();

    //Text boxes to add and display our messages
    private NfcAdapter mNfcAdapter;

    //Save our Array Lists of Messages for if the user navigates away
    @Override
    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putStringArrayList("messagesToSend", messagesToSendArray);
        savedInstanceState.putStringArrayList("lastMessagesReceived", messagesReceivedArray);
    }

    //Load our Array Lists of Messages for when the user navigates back
    @Override
    public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        messagesToSendArray = savedInstanceState.getStringArrayList("messagesToSend");
        messagesReceivedArray = savedInstanceState.getStringArrayList("lastMessagesReceived");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_join_srv);


        //Check if NFC is available on device
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (mNfcAdapter != null) {
            //Handle some NFC initialization here
        } else {
            Toast.makeText(this, "NFC not available on this device",
                    Toast.LENGTH_SHORT).show();
        }

        //Check if NFC is available on device
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (mNfcAdapter != null) {
            //This will refer back to createNdefMessage for what it will send
            mNfcAdapter.setNdefPushMessageCallback(this, this);

            //This will be called if the message is sent successfully
            mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
        }
    }

    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        //This will be called when another NFC capable device is detected.
        //We'll write the createRecords() method in just a moment
        NdefRecord[] recordsToAttach = createRecords();
        //When creating an NdefMessage we need to provide an NdefRecord[]
        return new NdefMessage(recordsToAttach);
    }

    @Override
    public void onNdefPushComplete(NfcEvent event) {
        //This is called when the system detects that our NdefMessage was
        //Successfully sent.
        messagesToSendArray.clear();
    }

    public NdefRecord[] createRecords() {
        NdefRecord[] records = new NdefRecord[1];
        //To Create Messages Manually if API is less than
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {

            byte[] payload = "192.168.1.100".
                    getBytes(Charset.forName("UTF-8"));
            NdefRecord record = new NdefRecord(
                    NdefRecord.TNF_WELL_KNOWN,      //Our 3-bit Type name format
                    NdefRecord.RTD_TEXT,            //Description of our payload
                    new byte[0],                    //The optional id for our Record
                    payload);                       //Our payload for the Record

            records[1] = record;

        }
        //Api is high enough that we can use createMime, which is preferred.
        else {

                byte[] payload = "192.168.1.100".
                        getBytes(Charset.forName("UTF-8"));

                NdefRecord record = NdefRecord.createMime("text/plain",payload);
                records[1] = record;

        }
        records[messagesToSendArray.size()] =
                NdefRecord.createApplicationRecord(getPackageName());
        return records;
    }

    private void handleNfcIntent(Intent NfcIntent) {
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(NfcIntent.getAction())) {
            Parcelable[] receivedArray =
                    NfcIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

            if (receivedArray != null) {
                messagesReceivedArray.clear();
                NdefMessage receivedMessage = (NdefMessage) receivedArray[0];
                NdefRecord[] attachedRecords = receivedMessage.getRecords();

                for (NdefRecord record : attachedRecords) {
                    String string = new String(record.getPayload());
                    //Make sure we don't pass along our AAR (Android Application Record)
                    if (string.equals(getPackageName())) {
                        continue;
                    }
                    messagesReceivedArray.add(string);
                }
                Toast.makeText(this, "Received " + messagesReceivedArray.size() +
                        " Messages", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "Received Blank Parcel", Toast.LENGTH_LONG).show();
            }
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        handleNfcIntent(intent);
    }

    @Override
    public void onResume() {
        super.onResume();
        handleNfcIntent(getIntent());
    }
}

Вот класс приемника:

public class AddSlaves extends Activity implements NfcAdapter.OnNdefPushCompleteCallback, NfcAdapter.CreateNdefMessageCallback{
    //The array lists to hold our messages
    private ArrayList<String> messagesToSendArray = new ArrayList<>();
    private ArrayList<String> messagesReceivedArray = new ArrayList<>();

    //Text boxes to add and display our messages
    private EditText txtBoxAddMessage;
    private TextView txtReceivedMessages;
    private TextView txtMessagesToSend;
    private NfcAdapter mNfcAdapter;

    private  void updateTextViews() {
        txtReceivedMessages.setText("Messages Received:\n");
        //Populate our list of messages we have received
        if (messagesReceivedArray.size() > 0) {
            for (int i = 0; i < messagesReceivedArray.size(); i++) {
                txtReceivedMessages.append(messagesReceivedArray.get(i));
                txtReceivedMessages.append("\n");
            }
        }
    }

    //Save our Array Lists of Messages for if the user navigates away
    @Override
    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putStringArrayList("lastMessagesReceived",messagesReceivedArray);
    }

    //Load our Array Lists of Messages for when the user navigates back
    @Override
    public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        messagesReceivedArray = savedInstanceState.getStringArrayList("lastMessagesReceived");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_slaves);

        txtReceivedMessages = (TextView) findViewById(R.id.txtMessagesReceived);
        Button btnAddMessage = (Button) findViewById(R.id.buttonAddMessage);


        //Check if NFC is available on device
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if(mNfcAdapter != null) {
            //Handle some NFC initialization here
        }
        else {
            Toast.makeText(this, "NFC not available on this device",
                    Toast.LENGTH_SHORT).show();
        }

        //Check if NFC is available on device
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if(mNfcAdapter != null) {
            //This will refer back to createNdefMessage for what it will send
            mNfcAdapter.setNdefPushMessageCallback(this, this);

            //This will be called if the message is sent successfully
            mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
        }
    }

    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        //This will be called when another NFC capable device is detected.
        return null;

    }

    @Override
    public void onNdefPushComplete(NfcEvent event) {
        //This is called when the system detects that our NdefMessage was
        //Successfully sent.
        messagesToSendArray.clear();
    }


    private void handleNfcIntent(Intent NfcIntent) {
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(NfcIntent.getAction())) {
            Parcelable[] receivedArray =
                    NfcIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

            if(receivedArray != null) {
                messagesReceivedArray.clear();
                NdefMessage receivedMessage = (NdefMessage) receivedArray[0];
                NdefRecord[] attachedRecords = receivedMessage.getRecords();

                for (NdefRecord record:attachedRecords) {
                    String string = new String(record.getPayload());
                    //Make sure we don't pass along our AAR (Android Application Record)
                    if (string.equals(getPackageName())) { continue; }
                    messagesReceivedArray.add(string);
                }
                Toast.makeText(this, "Received " + messagesReceivedArray.size() +
                        " Messages", Toast.LENGTH_LONG).show();
                updateTextViews();
            }
            else {
                Toast.makeText(this, "Received Blank Parcel", Toast.LENGTH_LONG).show();
            }
        }
    }


    @Override
    public void onNewIntent(Intent intent) {
        handleNfcIntent(intent);
    }

    @Override
    public void onResume() {
        super.onResume();
        updateTextViews();
        handleNfcIntent(getIntent());
    }
}

person Alex Collette    schedule 09.08.2017    source источник
comment
Я предполагаю, что ваши проблемы связаны с singleTask режимом запуска. Почему вы это указали? Объясните, пожалуйста, навигацию по вашему приложению.   -  person David Wasser    schedule 11.08.2017
comment
Также у вас есть 2 действия, которые могут обрабатывать NDEF_DISCOVERED действие. Это означает, что Android не знает, какой из них запускать, когда вы сканируете тег NFC. Как ты с этим справишься?   -  person David Wasser    schedule 11.08.2017


Ответы (1)


У вас есть несколько проблем в вашем коде активности отправителя:

  1. Вы храните messagesToSendArray, но на самом деле никогда не заполняете этот список массивов данными (т.е. messagesToSendArray.size() всегда равен 0). Поскольку вы только что создаете сообщение NDEF всякий раз, когда вызывается createNdefMessage(), нет необходимости сохранять и восстанавливать messagesToSendArray.

  2. Вы написали, что хотите отправлять сообщения NDEF в одном действии, но хотите получать события NFC в другом действии. Однако вы зарегистрировали свою активность отправителя для получения событий NDEF_DISCOVERED в манифесте. Нет необходимости в фильтре намерений NDEF_DISCOVERED в манифесте, если вы не хотите получать и обрабатывать эти события.

  3. Более того, нет необходимости обрабатывать намерение NDEF_DISCOVERED в вашей активности отправителя (т.е. вы можете безопасно удалить методы onNewIntent() и handleNfcIntent()).

  4. On Android versions below Jelly Bean you create an NFC Forum Text record with an invalid structure. The Text RTD requres a payload that is encoded in the form (also see this post)

    +----------+---------------+--------------------------------------+
    | Status   | Language Code | Text                                 |
    | (1 byte) | (n bytes)     | (m bytes)                            |
    +----------+---------------+--------------------------------------+
    
    where Status equals to the length n of the Language Code if the Text is UTF-8 encoded and Language Code is an IANA language code (e.g. "en" for English). Consequently, the proper way to encode that record would be:

    public static NdefRecord createTextRecord(String language, String text) {
        byte[] languageBytes;
        byte[] textBytes;
        try {
            languageBytes = language.getBytes("US-ASCII");
            textBytes = text.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new AssertionError(e);
        }
    
        byte[] recordPayload = new byte[1 + (languageBytes.length & 0x03F) + textBytes.length];
    
        recordPayload[0] = (byte)(languageBytes.length & 0x03F);
        System.arraycopy(languageBytes, 0, recordPayload, 1, languageBytes.length & 0x03F);
        System.arraycopy(textBytes, 0, recordPayload, 1 + (languageBytes.length & 0x03F), textBytes.length);
    
        return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, null, recordPayload);
    }
    
  5. Мне непонятно, почему вы создаете запись NFC Forum Text в версиях Android ниже Jelly Bean, когда вы создаете запись типа MIME в Jelly Bean и выше. Вы должны быть последовательны и создавать один и тот же тип записи на всех платформах (см. Метод NdefRecord.createTextRecord (en, string), не работающий ниже уровня API 21):

    String text = "192.168.1.100";
    String language = "en";
    
    NdefRecord record;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        record = NdefRecord.createTextRecord(language, text);
    } else {
        record = createTextRecord(language, text);
    }
    
  6. Наконец, в createRecords() вы создаете массив records как

    NdefRecord[] records = new NdefRecord[1];
    

    Следовательно, в массиве есть один элемент, доступный по индексу 0. Однако вы попытаетесь получить доступ к элементу 1 позже:

    records[1] = record;
    

    Это приводит к IndexOutOfBounds исключению. Поскольку createRecords() вызывается Android через обратный вызов createNdefMessage(), обратный вызов не выполняется (из-за исключения времени выполнения), и Android не будет использовать ваше сообщение NDEF. Вместо этого Android будет использовать для вашего приложения сообщение NDEF по умолчанию. Это сообщение NDEF по умолчанию содержит запись приложения Android, которая вызовет ваше основное действие (поскольку никакие другие ваши действия не зарегистрированы для запуска для конкретного содержимого сообщения NDEF по умолчанию); см. Обнаружение тега NFC не вызывает onNewIntent, а запускается из основного действия. Следовательно, вам нужно изменить смещение в records, где вы храните свою вновь созданную запись NDEF, на 0:

    records[0] = record;
    

    Причем нужно убрать строчку

    records[messagesToSendArray.size()] =
        NdefRecord.createApplicationRecord(getPackageName());
    

    так как это затем перезапишет ранее сохраненную запись NDEF с индексом 0 (messagesToSendArray.size() = 0) записью приложения Android. Опять же, это приведет к запуску вашего основного действия, поскольку вы не зарегистрировались для этого конкретного типа записи в своем манифесте.

Наконец, если вы хотите отклонять push-уведомления, если пользователь не находится внутри активности получателя, вам следует рассмотреть возможность использования системы диспетчеризации переднего плана. В этом случае вы должны полностью удалить все фильтры намерений NDEF_DISCOVERED из своего манифеста и вместо этого зарегистрировать каждое действие (получатель, отправитель, и основное) в системе диспетчеризации переднего плана. В получателе вы будете получать сообщения NDEF через onNewIntent(). В отправителе и в основном действии вы просто игнорируете и отбрасываете любое полученное сообщение NDEF. См. Пример Android-приложение, которое включает NFC только для одного действия.

person Michael Roland    schedule 11.08.2017