NFC-Tags mit Android lesen
German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)
Sind Sie neugierig, was NFC ist und wie es in Ihre eigenen Android-Anwendungen integriert werden kann? Dieses Tutorial führt Sie schnell in das Thema ein, bevor Sie tauchen und Ihnen beibringen, wie Sie eine einfache NFC-Reader-App erstellen können!
Was ist NFC?
NFC ist die Abkürzung für Near Field Communication. Es ist der internationale Standard für den kontaktlosen Austausch von Daten. Im Gegensatz zu vielen anderen Technologien wie Wireless LAN und Bluetooth beträgt der maximale Abstand zweier Geräte 10 cm. Die Entwicklung des Standards begann 2002 bei NXP Semiconductors und Sony. Das NFC Forum, ein Konsortium aus über 170 Unternehmen und Mitgliedern, darunter Mastercard, NXP, Nokia, Samsung, Intel und Google, entwickelt seit 2004 neue Spezifikationen.
Es gibt verschiedene Möglichkeiten für die NFC-Nutzung mit mobilen Geräten; zum Beispiel papierlose Tickets, Zugangskontrollen, bargeldlose Zahlungen und Autoschlüssel. Mit Hilfe von NFC-Tags können Sie Ihr Telefon steuern und Einstellungen ändern. Daten können einfach ausgetauscht werden, indem zwei Geräte nebeneinander gehalten werden.
In diesem Tutorial möchte ich erklären, wie NFC mit dem Android SDK implementiert wird, welche Fallstricke bestehen und was zu beachten ist. Wir werden Schritt für Schritt eine App erstellen, die den Inhalt von NFC-Tags lesen kann, die NDEF unterstützen.
NFC-Technologien
Es gibt eine Vielzahl von NFC-Tags, die mit einem Smartphone gelesen werden können. Das Spektrum reicht von einfachen Stickern und Schlüsselringen bis hin zu komplexen Karten mit integrierter kryptografischer Hardware. Tags unterscheiden sich auch in ihrer Chip-Technologie. Das wichtigste ist NDEF, das von den meisten Tags unterstützt wird. Darüber hinaus sollte Mifare erwähnt werden, da es sich hierbei um die am häufigsten verwendete kontaktlose Chiptechnologie weltweit handelt. Einige Tags können gelesen und geschrieben werden, während andere schreibgeschützt oder verschlüsselt sind.
In diesem Lernprogramm wird nur das NFC- atenaustauschformat (NDEF) behandelt.



NFC-Unterstützung in einer App hinzufügen
Wir beginnen mit einem neuen Projekt und einer leeren Aktivität. Es ist wichtig, eine SDK-Mindestversion von Level 10 auszuwählen, da NFC nur nach Android 2.3.3 unterstützt wird. Denken Sie daran, Ihren eigenen Paketnamen zu wählen. Ich habe net.vrallev.android.nfc.demo gewählt, weil vrallev.net die Domäne meiner Website ist und der andere Teil sich auf das Thema dieser Anwendung bezieht.
1 |
|
2 |
<uses-sdk |
3 |
android:minSdkVersion="10" |
4 |
android:targetSdkVersion="17" /> |
Das von Eclipse generierte Standardlayout ist für uns fast ausreichend. Ich habe der TextView nur eine ID hinzugefügt und den Text geändert.
1 |
|
2 |
<TextView |
3 |
android:id="@+id/textView_explanation" |
4 |
android:layout_width="wrap_content" |
5 |
android:layout_height="wrap_content" |
6 |
android:text="@string/explanation" /> |
Um Zugriff auf die NFC-Hardware zu erhalten, müssen Sie im Manifest eine Berechtigung beantragen. Wenn die App ohne NFC nicht funktioniert, können Sie die Bedingung mit dem Tag uses-feature angeben. Wenn NFC erforderlich ist, kann die App nicht auf Geräten ohne diese App installiert werden. Google Play zeigt Ihre App nur für Nutzer an, die Inhaber eines NFC-Geräts sind.
1 |
|
2 |
<uses-permission android:name="android.permission.NFC" /> |
3 |
|
4 |
<uses-feature |
5 |
android:name="android.hardware.nfc" |
6 |
android:required="true" /> |
Die MainActivity sollte nur aus der onCreate () -Methode bestehen. Sie können über die NfcAdapter-Klasse mit der Hardware interagieren. Es ist wichtig herauszufinden, ob der NfcAdapter null ist. In diesem Fall unterstützt das Android-Gerät NFC nicht.
1 |
|
2 |
package net.vrallev.android.nfc.demo; |
3 |
|
4 |
import android.app.Activity; |
5 |
import android.nfc.NfcAdapter; |
6 |
import android.os.Bundle; |
7 |
import android.widget.TextView; |
8 |
import android.widget.Toast; |
9 |
|
10 |
/**
|
11 |
* Activity for reading data from an NDEF Tag.
|
12 |
*
|
13 |
* @author Ralf Wondratschek
|
14 |
*
|
15 |
*/
|
16 |
public class MainActivity extends Activity { |
17 |
|
18 |
public static final String TAG = "NfcDemo"; |
19 |
|
20 |
private TextView mTextView; |
21 |
private NfcAdapter mNfcAdapter; |
22 |
|
23 |
@Override |
24 |
protected void onCreate(Bundle savedInstanceState) { |
25 |
super.onCreate(savedInstanceState); |
26 |
setContentView(R.layout.activity_main); |
27 |
|
28 |
mTextView = (TextView) findViewById(R.id.textView_explanation); |
29 |
|
30 |
mNfcAdapter = NfcAdapter.getDefaultAdapter(this); |
31 |
|
32 |
if (mNfcAdapter == null) { |
33 |
// Stop here, we definitely need NFC
|
34 |
Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show(); |
35 |
finish(); |
36 |
return; |
37 |
|
38 |
}
|
39 |
|
40 |
if (!mNfcAdapter.isEnabled()) { |
41 |
mTextView.setText("NFC is disabled."); |
42 |
} else { |
43 |
mTextView.setText(R.string.explanation); |
44 |
}
|
45 |
|
46 |
handleIntent(getIntent()); |
47 |
}
|
48 |
|
49 |
private void handleIntent(Intent intent) { |
50 |
// TODO: handle Intent
|
51 |
}
|
52 |
}
|
Wenn wir jetzt unsere App starten, können wir den Text sehen, ob NFC aktiviert oder deaktiviert ist.
So filtern Sie nach NFC-Tags
Wir haben unsere Beispielanwendung und möchten eine Benachrichtigung vom System erhalten, wenn wir ein NFC-Tag an das Gerät anhängen. Wie üblich verwendet Android sein Intent-System, um Tags an die Apps zu liefern. Wenn mehrere Apps mit der Absicht umgehen können, wird der Aktivitätswähler angezeigt und der Benutzer kann entscheiden, welche App geöffnet werden soll. Das Öffnen von URLs oder das Freigeben von Informationen erfolgt auf die gleiche Weise.
NFC-Intent-Filter
Es gibt drei verschiedene Filter für Tags:
- ACTION_NDEF_DISCOVERED
- ACTION_TECH_DISCOVERED
- ACTION_TAG_DISCOVERED
Die Liste wird von der höchsten zur niedrigsten Priorität sortiert.
Was passiert nun, wenn ein Tag an das Smartphone angehängt ist? Wenn das System ein Tag mit NDEF-Unterstützung erkennt, wird eine Absicht ausgelöst. Eine ACTION_TECH_DISCOVERED-Absicht wird ausgelöst, wenn keine Aktivität von einer App für die NDEF-Intent registriert ist oder wenn das Tag keine NDEF unterstützt. Wenn wieder keine App für den Intent gefunden wird oder die Chiptechnologie nicht erkannt wurde, wird eine ACTION_TAG_DISCOVERED Intent ausgelöst. Die folgende Grafik zeigt den Ablauf:



Zusammenfassend bedeutet dies, dass jede App nach der Absicht mit der höchsten Priorität filtern muss. In unserem Fall ist dies die NDEF-Absicht. Wir implementieren zuerst die ACTION_TECH_DISCOVERED Intent, um den Unterschied zwischen den Prioritäten hervorzuheben.
Tech Entdeckte Absicht
Wir müssen die Technologie angeben, an der wir interessiert sind. Zu diesem Zweck erstellen wir im Ordner res einen Unterordner namens xml. In diesem Ordner erstellen wir die Datei nfc_tech_filter.xml, in der wir die Technologien angeben.
1 |
|
2 |
<?xml version="1.0" encoding="utf-8"?>
|
3 |
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> |
4 |
<tech-list>
|
5 |
<tech>android.nfc.tech.Ndef</tech> |
6 |
<!-- class name -->
|
7 |
</tech-list>
|
8 |
</resources>
|
9 |
|
10 |
<!--
|
11 |
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
12 |
<tech-list>
|
13 |
<tech>android.nfc.tech.IsoDep</tech>
|
14 |
<tech>android.nfc.tech.NfcA</tech>
|
15 |
<tech>android.nfc.tech.NfcB</tech>
|
16 |
<tech>android.nfc.tech.NfcF</tech>
|
17 |
<tech>android.nfc.tech.NfcV</tech>
|
18 |
<tech>android.nfc.tech.Ndef</tech>
|
19 |
<tech>android.nfc.tech.NdefFormatable</tech>
|
20 |
<tech>android.nfc.tech.MifareClassic</tech>
|
21 |
<tech>android.nfc.tech.MifareUltralight</tech>
|
22 |
</tech-list>
|
23 |
</resources>
|
24 |
-->
|
Jetzt müssen wir einen IntentFilter im Manifest erstellen, und die App wird gestartet, wenn wir einen Tag anhängen.
1 |
|
2 |
<?xml version="1.0" encoding="utf-8"?>
|
3 |
<activity
|
4 |
android:name="net.vrallev.android.nfc.demo.MainActivity" |
5 |
android:label="@string/app_name" > |
6 |
<intent-filter>
|
7 |
<action android:name="android.intent.action.MAIN" /> |
8 |
<category android:name="android.intent.category.LAUNCHER" /> |
9 |
</intent-filter>
|
10 |
|
11 |
<intent-filter>
|
12 |
<action android:name="android.nfc.action.TECH_DISCOVERED" /> |
13 |
</intent-filter>
|
14 |
|
15 |
<meta-data
|
16 |
android:name="android.nfc.action.TECH_DISCOVERED" |
17 |
android:resource="@xml/nfc_tech_filter" /> |
18 |
</activity>
|
Wenn keine andere App für diese Absicht registriert ist, beginnt unsere Aktivität sofort. Auf meinem Gerät sind jedoch andere Apps installiert, sodass der Aktivitätswähler angezeigt wird.



NDEF entdeckt Absicht
Wie bereits erwähnt, hat der Tech Discovered Intent die zweithöchste Priorität. Da unsere App jedoch nur NDEF unterstützt, können wir stattdessen den NDEF Discovered Intent verwenden, der eine höhere Priorität hat. Wir können die Technologieliste wieder löschen und den IntentFilter durch den folgenden ersetzen.
1 |
|
2 |
<intent-filter> |
3 |
<action android:name="android.nfc.action.NDEF_DISCOVERED" /> |
4 |
|
5 |
<category android:name="android.intent.category.DEFAULT" /> |
6 |
|
7 |
<data android:mimeType="text/plain" /> |
8 |
</intent-filter> |
Wenn wir das Tag jetzt anhängen, wird die App wie zuvor gestartet. Es gibt jedoch einen Unterschied für mich. Der Aktivitätswähler erscheint nicht und die App startet sofort, da der NDEF-Intent eine höhere Priorität hat und die anderen Apps nur für die niedrigeren Prioritäten registriert sind. Genau das wollen wir.
Vordergrund Versand
Beachten Sie, dass ein Problem bleibt. Wenn unsere App bereits geöffnet ist und wir das Tag erneut anhängen, wird die App ein zweites Mal geöffnet, anstatt das Tag direkt zu übermitteln. Dies ist nicht unser beabsichtigtes Verhalten. Sie können das Problem umgehen, indem Sie einen Foreground-Versand verwenden.
Anstatt dass das System den Intent verteilt hat, können Sie Ihre Aktivität registrieren, um das Tag direkt zu erhalten. Dies ist wichtig für einen bestimmten Workflow, bei dem es keinen Sinn macht, eine andere App zu öffnen.
Ich habe die Erklärungen an den entsprechenden Stellen im Code eingefügt.
1 |
|
2 |
package net.vrallev.android.nfc.demo; |
3 |
|
4 |
import android.app.Activity; |
5 |
import android.app.PendingIntent; |
6 |
import android.content.Intent; |
7 |
import android.content.IntentFilter; |
8 |
import android.content.IntentFilter.MalformedMimeTypeException; |
9 |
import android.nfc.NfcAdapter; |
10 |
import android.os.Bundle; |
11 |
import android.widget.TextView; |
12 |
import android.widget.Toast; |
13 |
|
14 |
/**
|
15 |
* Activity for reading data from an NDEF Tag.
|
16 |
*
|
17 |
* @author Ralf Wondratschek
|
18 |
*
|
19 |
*/
|
20 |
public class MainActivity extends Activity { |
21 |
|
22 |
public static final String MIME_TEXT_PLAIN = "text/plain"; |
23 |
public static final String TAG = "NfcDemo"; |
24 |
|
25 |
private TextView mTextView; |
26 |
private NfcAdapter mNfcAdapter; |
27 |
|
28 |
@Override |
29 |
protected void onCreate(Bundle savedInstanceState) { |
30 |
super.onCreate(savedInstanceState); |
31 |
setContentView(R.layout.activity_main); |
32 |
|
33 |
mTextView = (TextView) findViewById(R.id.textView_explanation); |
34 |
|
35 |
mNfcAdapter = NfcAdapter.getDefaultAdapter(this); |
36 |
|
37 |
if (mNfcAdapter == null) { |
38 |
// Stop here, we definitely need NFC
|
39 |
Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show(); |
40 |
finish(); |
41 |
return; |
42 |
|
43 |
}
|
44 |
|
45 |
if (!mNfcAdapter.isEnabled()) { |
46 |
mTextView.setText("NFC is disabled."); |
47 |
} else { |
48 |
mTextView.setText(R.string.explanation); |
49 |
}
|
50 |
|
51 |
handleIntent(getIntent()); |
52 |
}
|
53 |
|
54 |
@Override |
55 |
protected void onResume() { |
56 |
super.onResume(); |
57 |
|
58 |
/**
|
59 |
* It's important, that the activity is in the foreground (resumed). Otherwise
|
60 |
* an IllegalStateException is thrown.
|
61 |
*/
|
62 |
setupForegroundDispatch(this, mNfcAdapter); |
63 |
}
|
64 |
|
65 |
@Override |
66 |
protected void onPause() { |
67 |
/**
|
68 |
* Call this before onPause, otherwise an IllegalArgumentException is thrown as well.
|
69 |
*/
|
70 |
stopForegroundDispatch(this, mNfcAdapter); |
71 |
|
72 |
super.onPause(); |
73 |
}
|
74 |
|
75 |
@Override |
76 |
protected void onNewIntent(Intent intent) { |
77 |
/**
|
78 |
* This method gets called, when a new Intent gets associated with the current activity instance.
|
79 |
* Instead of creating a new activity, onNewIntent will be called. For more information have a look
|
80 |
* at the documentation.
|
81 |
*
|
82 |
* In our case this method gets called, when the user attaches a Tag to the device.
|
83 |
*/
|
84 |
handleIntent(intent); |
85 |
}
|
86 |
|
87 |
private void handleIntent(Intent intent) { |
88 |
// TODO: handle Intent
|
89 |
}
|
90 |
|
91 |
/**
|
92 |
* @param activity The corresponding {@link Activity} requesting the foreground dispatch.
|
93 |
* @param adapter The {@link NfcAdapter} used for the foreground dispatch.
|
94 |
*/
|
95 |
public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) { |
96 |
final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass()); |
97 |
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); |
98 |
|
99 |
final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0); |
100 |
|
101 |
IntentFilter[] filters = new IntentFilter[1]; |
102 |
String[][] techList = new String[][]{}; |
103 |
|
104 |
// Notice that this is the same filter as in our manifest.
|
105 |
filters[0] = new IntentFilter(); |
106 |
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED); |
107 |
filters[0].addCategory(Intent.CATEGORY_DEFAULT); |
108 |
try { |
109 |
filters[0].addDataType(MIME_TEXT_PLAIN); |
110 |
} catch (MalformedMimeTypeException e) { |
111 |
throw new RuntimeException("Check your mime type."); |
112 |
}
|
113 |
|
114 |
adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList); |
115 |
}
|
116 |
|
117 |
/**
|
118 |
* @param activity The corresponding {@link BaseActivity} requesting to stop the foreground dispatch.
|
119 |
* @param adapter The {@link NfcAdapter} used for the foreground dispatch.
|
120 |
*/
|
121 |
public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) { |
122 |
adapter.disableForegroundDispatch(activity); |
123 |
}
|
124 |
}
|
Wenn Sie jetzt ein Tag anhängen und unsere App bereits geöffnet ist, wird onNewIntent aufgerufen, und es wird keine neue Aktivität erstellt.
Daten von einem NDEF-Tag lesen
Der letzte Schritt besteht darin, die Daten vom Tag zu lesen. Die Erklärungen werden an den entsprechenden Stellen im Code noch einmal eingefügt. Die NdefReaderTask ist eine private innere Klasse.
1 |
|
2 |
package net.vrallev.android.nfc.demo; |
3 |
|
4 |
import java.io.UnsupportedEncodingException; |
5 |
import java.util.Arrays; |
6 |
|
7 |
import android.app.Activity; |
8 |
import android.app.PendingIntent; |
9 |
import android.content.Intent; |
10 |
import android.content.IntentFilter; |
11 |
import android.content.IntentFilter.MalformedMimeTypeException; |
12 |
import android.nfc.NdefMessage; |
13 |
import android.nfc.NdefRecord; |
14 |
import android.nfc.NfcAdapter; |
15 |
import android.nfc.Tag; |
16 |
import android.nfc.tech.Ndef; |
17 |
import android.os.AsyncTask; |
18 |
import android.os.Bundle; |
19 |
import android.util.Log; |
20 |
import android.widget.TextView; |
21 |
import android.widget.Toast; |
22 |
|
23 |
/*
|
24 |
* ... other code parts
|
25 |
*/
|
26 |
|
27 |
private void handleIntent(Intent intent) { |
28 |
String action = intent.getAction(); |
29 |
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { |
30 |
|
31 |
String type = intent.getType(); |
32 |
if (MIME_TEXT_PLAIN.equals(type)) { |
33 |
|
34 |
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); |
35 |
new NdefReaderTask().execute(tag); |
36 |
|
37 |
} else { |
38 |
Log.d(TAG, "Wrong mime type: " + type); |
39 |
}
|
40 |
} else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) { |
41 |
|
42 |
// In case we would still use the Tech Discovered Intent
|
43 |
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); |
44 |
String[] techList = tag.getTechList(); |
45 |
String searchedTech = Ndef.class.getName(); |
46 |
|
47 |
for (String tech : techList) { |
48 |
if (searchedTech.equals(tech)) { |
49 |
new NdefReaderTask().execute(tag); |
50 |
break; |
51 |
}
|
52 |
}
|
53 |
}
|
54 |
}
|
1 |
|
2 |
/**
|
3 |
* Background task for reading the data. Do not block the UI thread while reading.
|
4 |
*
|
5 |
* @author Ralf Wondratschek
|
6 |
*
|
7 |
*/
|
8 |
private class NdefReaderTask extends AsyncTask<Tag, Void, String> { |
9 |
|
10 |
@Override |
11 |
protected String doInBackground(Tag... params) { |
12 |
Tag tag = params[0]; |
13 |
|
14 |
Ndef ndef = Ndef.get(tag); |
15 |
if (ndef == null) { |
16 |
// NDEF is not supported by this Tag.
|
17 |
return null; |
18 |
}
|
19 |
|
20 |
NdefMessage ndefMessage = ndef.getCachedNdefMessage(); |
21 |
|
22 |
NdefRecord[] records = ndefMessage.getRecords(); |
23 |
for (NdefRecord ndefRecord : records) { |
24 |
if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) { |
25 |
try { |
26 |
return readText(ndefRecord); |
27 |
} catch (UnsupportedEncodingException e) { |
28 |
Log.e(TAG, "Unsupported Encoding", e); |
29 |
}
|
30 |
}
|
31 |
}
|
32 |
|
33 |
return null; |
34 |
}
|
35 |
|
36 |
private String readText(NdefRecord record) throws UnsupportedEncodingException { |
37 |
/*
|
38 |
* See NFC forum specification for "Text Record Type Definition" at 3.2.1
|
39 |
*
|
40 |
* http://www.nfc-forum.org/specs/
|
41 |
*
|
42 |
* bit_7 defines encoding
|
43 |
* bit_6 reserved for future use, must be 0
|
44 |
* bit_5..0 length of IANA language code
|
45 |
*/
|
46 |
|
47 |
byte[] payload = record.getPayload(); |
48 |
|
49 |
// Get the Text Encoding
|
50 |
String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; |
51 |
|
52 |
// Get the Language Code
|
53 |
int languageCodeLength = payload[0] & 0063; |
54 |
|
55 |
// String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
|
56 |
// e.g. "en"
|
57 |
|
58 |
// Get the Text
|
59 |
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); |
60 |
}
|
61 |
|
62 |
@Override |
63 |
protected void onPostExecute(String result) { |
64 |
if (result != null) { |
65 |
mTextView.setText("Read content: " + result); |
66 |
}
|
67 |
}
|
68 |
}
|
Die App liest nun erfolgreich den Inhalt.



Nützliche Apps
Um zu prüfen, ob Daten korrekt gelesen und geschrieben werden, verwende ich gerne folgende Apps:
- NFC TagInfo von NFC Research Lab zum Lesen von Daten
- TagInfo von NXP SEMICONDUCTORS zum Lesen von Daten
- TagWriter von NXP SEMICONDUCTORS zum Schreiben von Daten
Fazit
In diesem Tutorial habe ich Ihnen gezeigt, wie die Daten eines NDEF-Tags extrahiert werden können. Sie könnten das Beispiel auf andere Mime-Typen und Chip-Technologien erweitern; eine Funktion zum Schreiben von Daten wäre ebenfalls nützlich. Der erste Schritt zur Arbeit mit NFC wurde gemacht. Das Android SDK bietet jedoch viel mehr Möglichkeiten, wie beispielsweise einen einfachen Austausch von Daten (Android Beam genannt).
Über den Autor
Ralf Wondratschek ist Informatikstudent aus Deutschland. Neben seinen Studien arbeitet Ralf als Freelancer im Bereich Mobile Computing. In den letzten Jahren hat er mit Java, XML, HTML, JSP, JSF, Eclipse, Google App Engine und natürlich Android gearbeitet. Er hat bisher zwei Android-Apps veröffentlicht, die hier zu finden sind.
Auf seiner Homepage vrallev.net können Sie mehr über die Arbeit des Autors erfahren.
Quellen
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



