Статьи

Чтение тегов NFC с Android

Вам интересно, что такое NFC и как его можно интегрировать в ваши собственные приложения для Android? Этот урок быстро познакомит вас с этой темой, прежде чем погрузиться в нее и научит создавать простое приложение для чтения NFC!


NFC — это сокращение от Near Field Communication . Это международный стандарт для бесконтактного обмена данными. В отличие от широкого спектра других технологий, таких как беспроводная локальная сеть и Bluetooth, максимальное расстояние между двумя устройствами составляет 10 см. Разработка стандарта началась в 2002 году компаниями NXP Semiconductors и Sony. NFC Forum , консорциум из более чем 170 компаний и членов, в который входят Mastercard, NXP, Nokia, Samsung, Intel и Google, разрабатывает новые спецификации с 2004 года.

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

В этом уроке я хочу объяснить, как реализовать NFC с помощью Android SDK, какие подводные камни существуют и что нужно иметь в виду. Мы шаг за шагом создадим приложение, которое сможет читать содержимое тегов NFC, поддерживающих NDEF.


Существует множество тегов NFC, которые можно прочитать с помощью смартфона. Спектр варьируется от простых наклеек и брелоков до сложных карточек со встроенным криптографическим оборудованием. Тэги также отличаются по своей технологии чипов. Наиболее важным является NDEF, который поддерживается большинством тегов. Кроме того, следует упомянуть Mifare, поскольку это самая распространенная технология бесконтактных чипов в мире. Некоторые теги могут быть прочитаны и записаны, а другие — только для чтения или зашифрованы.

В этом руководстве обсуждается только формат обмена данными NFC (NDEF).

girogo-карты

Мы начинаем с нового проекта и пустой деятельности. Важно выбрать минимальную версию SDK уровня 10, потому что NFC поддерживается только после Android 2.3.3. Не забудьте выбрать собственное имя пакета. Я выбрал net.vrallev.android.nfc.demo , потому что vrallev.net является доменом моего сайта, а другая часть относится к теме этого приложения.

1
2
3
<uses-sdk
       android:minSdkVersion=»10″
       android:targetSdkVersion=»17″ />

Макет по умолчанию, сгенерированный Eclipse, для нас почти достаточен. Я только добавил идентификатор в TextView и изменил текст.

1
2
3
4
5
<TextView
       android:id=»@+id/textView_explanation»
       android:layout_width=»wrap_content»
       android:layout_height=»wrap_content»
       android:text=»@string/explanation» />

Чтобы получить доступ к оборудованию NFC, вы должны обратиться за разрешением в манифест. Если приложение не будет работать без NFC, вы можете указать условие с помощью тега метки использования. Если требуется NFC, приложение не может быть установлено на устройства без него, и Google Play будет отображать ваше приложение только пользователям, которые владеют устройством NFC.

1
2
3
4
5
<uses-permission android:name=»android.permission.NFC» />
 
   <uses-feature
       android:name=»android.hardware.nfc»
       android:required=»true» />

MainActivity должен состоять только из метода onCreate (). Вы можете взаимодействовать с оборудованием через класс NfcAdapter. Важно выяснить, является ли NfcAdapter нулевым. В этом случае устройство Android не поддерживает NFC.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package net.vrallev.android.nfc.demo;
 
   import android.app.Activity;
   import android.nfc.NfcAdapter;
   import android.os.Bundle;
   import android.widget.TextView;
   import android.widget.Toast;
    
   /**
    * Activity for reading data from an NDEF Tag.
    *
    * @author Ralf Wondratschek
    *
    */
   public class MainActivity extends Activity {
    
       public static final String TAG = «NfcDemo»;
    
       private TextView mTextView;
       private NfcAdapter mNfcAdapter;
    
       @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);
    
           mTextView = (TextView) findViewById(R.id.textView_explanation);
    
           mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    
           if (mNfcAdapter == null) {
               // Stop here, we definitely need NFC
               Toast.makeText(this, «This device doesn’t support NFC.», Toast.LENGTH_LONG).show();
               finish();
               return;
    
           }
        
           if (!mNfcAdapter.isEnabled()) {
               mTextView.setText(«NFC is disabled.»);
           } else {
               mTextView.setText(R.string.explanation);
           }
            
           handleIntent(getIntent());
       }
        
       private void handleIntent(Intent intent) {
           // TODO: handle Intent
       }
   }

Если мы запустим наше приложение сейчас, мы увидим текст, включен ли NFC или нет.

У нас есть пример приложения и мы хотим получать уведомление от системы, когда мы прикрепляем NFC-тег к устройству. Как обычно, Android использует систему Intent для доставки тегов в приложения. Если несколько приложений могут обрабатывать намерение, на экране отобразится средство выбора активности, и пользователь может решить, какое приложение будет открыто. Открытие URL или обмен информацией обрабатываются одинаково.


Есть три разных фильтра для тегов:

  1. ACTION_NDEF_DISCOVERED
  2. ACTION_TECH_DISCOVERED
  3. ACTION_TAG_DISCOVERED

Список отсортирован от самого высокого до самого низкого приоритета.

Что происходит, когда к смартфону прикрепляется метка? Если система обнаруживает тег с поддержкой NDEF, запускается намерение. Намерение ACTION_TECH_DISCOVERED запускается, если ни одно действие из какого-либо приложения не зарегистрировано для намерения NDEF или если тег не поддерживает NDEF. Если снова не найдено приложение для Intent или технология чипа не может быть обнаружена, то запускается намерение ACTION_TAG_DISCOVERED . На следующем рисунке показан процесс:

отправка тегов NFC

Таким образом, это означает, что каждое приложение должно фильтроваться после намерения с наивысшим приоритетом. В нашем случае это намерение NDEF. Сначала мы реализуем намерение ACTION_TECH_DISCOVERED, чтобы выделить разницу между приоритетами.


Мы должны указать интересующую нас технологию. Для этого мы создаем подпапку с именем xml в папке res . В этой папке мы создаем файл nfc_tech_filter.xml , в котором мы указываем технологии.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version=»1.0″ encoding=»utf-8″?>
   <resources xmlns:xliff=»urn:oasis:names:tc:xliff:document:1.2″>
       <tech-list>
           <tech>android.nfc.tech.Ndef</tech>
           <!— class name —>
       </tech-list>
   </resources>
    
   <!—
   <resources xmlns:xliff=»urn:oasis:names:tc:xliff:document:1.2″>
       <tech-list>
           <tech>android.nfc.tech.IsoDep</tech>
           <tech>android.nfc.tech.NfcA</tech>
           <tech>android.nfc.tech.NfcB</tech>
           <tech>android.nfc.tech.NfcF</tech>
           <tech>android.nfc.tech.NfcV</tech>
           <tech>android.nfc.tech.Ndef</tech>
           <tech>android.nfc.tech.NdefFormatable</tech>
           <tech>android.nfc.tech.MifareClassic</tech>
           <tech>android.nfc.tech.MifareUltralight</tech>
       </tech-list>
   </resources>
   —>

Теперь мы должны создать IntentFilter в манифесте, и приложение будет запущено, когда мы прикрепим тег.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<?xml version=»1.0″ encoding=»utf-8″?>
   <activity
       android:name=»net.vrallev.android.nfc.demo.MainActivity»
       android:label=»@string/app_name» >
       <intent-filter>
           <action android:name=»android.intent.action.MAIN» />
           <category android:name=»android.intent.category.LAUNCHER» />
       </intent-filter>
            
       <intent-filter>
           <action android:name=»android.nfc.action.TECH_DISCOVERED» />
       </intent-filter>
 
       <meta-data
           android:name=»android.nfc.action.TECH_DISCOVERED»
           android:resource=»@xml/nfc_tech_filter» />
   </activity>

Если никакое другое приложение не зарегистрировано для этого Намерения, наша Деятельность начнется немедленно. На моем устройстве, однако, установлены другие приложения, поэтому отображается выбор активности.

Скриншот

Как я упоминал ранее, у Обнаруженного Технического Намерения есть второй самый высокий приоритет. Однако, поскольку наше приложение будет поддерживать только NDEF, вместо этого мы можем использовать обнаруженное намерение NDEF, которое имеет более высокий приоритет. Мы можем снова удалить список технологий и заменить IntentFilter следующим.

1
2
3
4
5
6
7
<intent-filter>
       <action android:name=»android.nfc.action.NDEF_DISCOVERED» />
 
       <category android:name=»android.intent.category.DEFAULT» />
 
       <data android:mimeType=»text/plain» />
   </intent-filter>

Когда мы сейчас прикрепим тег, приложение будет запущено, как и раньше. Однако для меня есть разница. Средство выбора действий не отображается, и приложение запускается немедленно, поскольку у намерения NDEF более высокий приоритет, а у других приложений зарегистрирован только более низкий приоритет. Это именно то, что мы хотим.


Обратите внимание, что остается одна проблема. Когда наше приложение уже открыто и мы снова прикрепляем тег, приложение открывается второй раз вместо прямой доставки тега. Это не наше предполагаемое поведение. Вы можете обойти проблему с помощью Foreground Dispatch.

Вместо того, чтобы система распространила Намерение, вы можете зарегистрировать свою активность, чтобы получить метку напрямую. Это важно для определенного рабочего процесса, где нет смысла открывать другое приложение.

Я вставил объяснения в соответствующие места в коде.

1
package net.vrallev.android.nfc.demo;

Теперь, когда вы прикрепляете тег и наше приложение уже открыто, вызывается onNewIntent, и новая активность не создается.


Последний шаг — прочитать данные из тега. Объяснения вставляются в соответствующие места в коде еще раз. NdefReaderTask — это закрытый внутренний класс.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package net.vrallev.android.nfc.demo;
 
   import java.io.UnsupportedEncodingException;
   import java.util.Arrays;
    
   import android.app.Activity;
   import android.app.PendingIntent;
   import android.content.Intent;
   import android.content.IntentFilter;
   import android.content.IntentFilter.MalformedMimeTypeException;
   import android.nfc.NdefMessage;
   import android.nfc.NdefRecord;
   import android.nfc.NfcAdapter;
   import android.nfc.Tag;
   import android.nfc.tech.Ndef;
   import android.os.AsyncTask;
   import android.os.Bundle;
   import android.util.Log;
   import android.widget.TextView;
   import android.widget.Toast;
    
   /*
    * … other code parts
    */
    
   private void handleIntent(Intent intent) {
       String action = intent.getAction();
       if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
            
           String type = intent.getType();
           if (MIME_TEXT_PLAIN.equals(type)) {
 
               Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
               new NdefReaderTask().execute(tag);
                
           } else {
               Log.d(TAG, «Wrong mime type: » + type);
           }
       } else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
            
           // In case we would still use the Tech Discovered Intent
           Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
           String[] techList = tag.getTechList();
           String searchedTech = Ndef.class.getName();
            
           for (String tech : techList) {
               if (searchedTech.equals(tech)) {
                   new NdefReaderTask().execute(tag);
                   break;
               }
           }
       }
   }
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
    * Background task for reading the data.
    *
    * @author Ralf Wondratschek
    *
    */
   private class NdefReaderTask extends AsyncTask<Tag, Void, String> {
 
       @Override
       protected String doInBackground(Tag… params) {
           Tag tag = params[0];
            
           Ndef ndef = Ndef.get(tag);
           if (ndef == null) {
               // NDEF is not supported by this Tag.
               return null;
           }
 
           NdefMessage ndefMessage = ndef.getCachedNdefMessage();
 
           NdefRecord[] records = ndefMessage.getRecords();
           for (NdefRecord ndefRecord : records) {
               if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
                   try {
                       return readText(ndefRecord);
                   } catch (UnsupportedEncodingException e) {
                       Log.e(TAG, «Unsupported Encoding», e);
                   }
               }
           }
 
           return null;
       }
        
       private String readText(NdefRecord record) throws UnsupportedEncodingException {
           /*
            * See NFC forum specification for «Text Record Type Definition» at 3.2.1
            *
            * http://www.nfc-forum.org/specs/
            *
            * bit_7 defines encoding
            * bit_6 reserved for future use, must be 0
            * bit_5..0 length of IANA language code
            */
 
           byte[] payload = record.getPayload();
 
           // Get the Text Encoding
           String textEncoding = ((payload[0] & 128) == 0) ?
 
           // Get the Language Code
           int languageCodeLength = payload[0] & 0063;
            
           // String languageCode = new String(payload, 1, languageCodeLength, «US-ASCII»);
           // eg «en»
            
           // Get the Text
           return new String(payload, languageCodeLength + 1, payload.length — languageCodeLength — 1, textEncoding);
       }
        
       @Override
       protected void onPostExecute(String result) {
           if (result != null) {
               mTextView.setText(«Read content: » + result);
           }
       }
   }

Приложение теперь успешно читает содержимое.

экран

Чтобы проверить, правильно ли считываются и записываются данные, мне лично нравится использовать следующие приложения:

  • NFC TagInfo от NFC Research Lab для чтения данных
  • TagInfo от NXP SEMICONDUCTORS для чтения данных
  • TagWriter от NXP SEMICONDUCTORS для записи данных

В этом уроке я показал вам, как можно извлечь данные из тега NDEF. Вы можете расширить пример на другие типы MIME и технологии чипов; функция записи данных также будет полезна. Первый шаг для работы с NFC был сделан. Тем не менее, Android SDK предлагает гораздо больше возможностей, таких как простой обмен данными (называемый Android Beam).

Если вы хотите продолжить разработку Android, ознакомьтесь с огромным набором полезных шаблонов приложений для Android на Envato Market. Или нанять разработчика Android на Envato Studio.


Помимо учебы, Ральф работает фрилансером в области мобильных компьютеров. В последние несколько лет он работал с Java, XML, HTML, JSP, JSF, Eclipse, Google App Engine и, конечно, с Android. На сегодняшний день он опубликовал два приложения для Android, которые можно найти здесь .

Вы можете узнать больше о работе автора на его домашней странице vrallev.net .


http://www.nfc-forum.org/home/n-mark.jpg

http://commons.wikimedia.org/wiki/File%3A%C3%9Cberlagert.jpg

http://developer.android.com/images/nfc_tag_dispatch.png