Android + NFC 建國科技大學 資管系 饒瑞佶 2017/3 v1
Android 2.3.3 (API Level 10)才有支援完整的NFC功能 只要NFC相容都讀的到(NFC或Mifare) 讀取Tag UUID Android 2.3.3 (API Level 10)才有支援完整的NFC功能 只要NFC相容都讀的到(NFC或Mifare)
建立新專案 修改AndroidManifest.xml 加入<intent-filter>,如果有NFC Tag進入感測範圍,本App也會變 成可處理的App選項之一
<intent-filter> <action android:name="android.nfc.action.TAG_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>
繼續修改AndroidManifest.xml 加入<uses-permission>與<uses-feature> <uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc" android:required="true" />
設計layout 使用預設的activity_main.xml,加入1個TextView,用來顯示UUID 資訊 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" tools:context=".MainActivity"> <TextView android:id="@+id/info" android:layout_height="wrap_content" /> </LinearLayout>
修改MainActivity.java onCreate中加入 檢查裝置是否支援 NFC NfcAdapter物件,用 以對應裝置上NFC
private NfcAdapter nfcAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); nfcAdapter = NfcAdapter.getDefaultAdapter(this); // 對應裝置上NFC if (nfcAdapter == null) { //透過是否對應到裝置NFC,檢查裝置是否支援NFC Toast.makeText(this, "本裝置不支援NFC!", Toast.LENGTH_LONG).show(); finish(); } else if (!nfcAdapter.isEnabled()) { "NFC功能尚未開啟", }
修改MainActivity.java 加入TextView對應
修改MainActivity.java 加入onResume事件 偵測是否有發生AndroidManifest.xml中設定的 Intent=ACTION_TAG_DISCOVERED? 如果有,可以透過 intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)取得UUID 等相關資訊
onResume事件 首先取得Intent發生的動作 @Override protected void onResume() { super.onResume(); // 取得Intent動作 Intent intent = getIntent(); String action = intent.getAction(); }
onResume事件 處理 ACTION_TAG_DIS COVERED
// 取得Intent動作 Intent intent = getIntent(); String action = intent.getAction(); // 如果發生ACTION_TAG_DISCOVERED if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { Toast.makeText(this, "發生 - ACTION_TAG_DISCOVERED", Toast.LENGTH_SHORT).show(); // 取得tag資訊 Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); if(tag == null){ nfcInfo.setText("沒有偵測到tag"); }else{ } // 非ACTION_TAG_DISCOVERED "發生動作 : " + action,
onResume事件 顯示TAG資訊
if (tag == null) { nfcInfo.setText("沒有偵測到tag"); } else { // 顯示TAG資料 String tagInfo = tag.toString() + "\n"; tagInfo += "\nTag UUID: \n"; byte[] tagId = tag.getId(); tagInfo += "長度 = " + tagId.length + "\n"; for (int i = 0; i < tagId.length; i++) { tagInfo += Integer.toHexString(tagId[i] & 0xFF) + " "; } tagInfo += "\n"; // 顯示TAG其他技術規範資料 String[] techList = tag.getTechList(); tagInfo += "\n技術規範資料\n"; tagInfo += "長度 = " + techList.length + "\n"; for (int i = 0; i < techList.length; i++) { tagInfo += techList[i] + "\n "; nfcInfo.setText(tagInfo);
執行 確認NFC有啟動
執行
Read & Write NDEF
修改AndroidManifest.xml 加入NDEF偵測之Intent 原Tag偵測
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter>
新增layout ndef.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/info" android:layout_height="wrap_content" android:text="請靠近標籤讀取資料"/> <Button android:id="@+id/write_tag" android:layout_width="wrap_content" android:text="寫入資料到標籤"></Button> <EditText android:id="@+id/note" android:layout_width="fill_parent" android:layout_height="388dp" android:gravity="top" android:text=""></EditText> </LinearLayout> 新增layout ndef.xml
新增NDEF.java 加入nfcAdapter與 必要的宣告 gResumed與gWriteMode決定寫入或讀取模式
public class NDEF extends AppCompatActivity { private NfcAdapter nfcAdapter; // 判斷目前是否在Resume狀態 private boolean gResumed = false; // 判斷目前是否在可寫入的模式 private boolean gWriteMode = false; // 對應layout輸入框 EditText gNote; // 處理Intent PendingIntent gNfcPendingIntent; IntentFilter[] gNdefExchangeFilters; IntentFilter[] gWriteTagFilters; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ndef); nfcAdapter = NfcAdapter.getDefaultAdapter(this); // 對應裝置上NFC if (nfcAdapter == null) { //透過是否對應到裝置NFC,檢查裝置是否支援NFC Toast.makeText(this, "本裝置不支援NFC!", Toast.LENGTH_LONG).show(); finish(); } else if (!nfcAdapter.isEnabled()) { "NFC功能尚未開啟", }
註冊物件與事件 當App在背景,偵測到Tag時會自動跳出到前景
// 取得EditText與Button,並註冊對應的事件 findViewById(R.id.write_tag).setOnClickListener(this.gTagWriter); gNote = (EditText) findViewById(R.id.note); gNote.addTextChangedListener(gTextWatcher); // 註冊本Activity負責處理所有接收到的NFC Intents gNfcPendingIntent = PendingIntent.getActivity(this, 0, // 指定本Activity為應用程式中的最上層Activity new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); // 建立要處理的Intent Filter負責處理來自Tag或p2p交換的資料 IntentFilter ndefDetected = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); try { ndefDetected.addDataType("text/plain"); } catch (IntentFilter.MalformedMimeTypeException e) { } gNdefExchangeFilters = new IntentFilter[]{ndefDetected};
加入按鈕事件
// 按鈕事件 private View.OnClickListener gTagWriter = new View.OnClickListener() { @Override public void onClick(View v) { // 先停止接收任何的Intent,準備寫入資料至tag disableNdefExchangeMode(); // 啟動寫入Tag模式,監測是否有Tag進入 enableTagWriteMode(); // 顯示對話框,告知將Tag或手機靠近本機的NFC感應區 new AlertDialog.Builder(NDEF.this) .setTitle("請將標籤靠近,以便寫入資料") .setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { // 在取消模式下,先關閉監偵有Tag準備寫入的模式,再啟動等待資料交換的模式 // 停止寫入Tag模式,代表已有Tag進入 disableTagWriteMode(); // 啟動資料交換 enableNdefExchangeMode(); } }).create().show(); };
加入輸入框change事件
// 輸入框輸入文字後change事件 private TextWatcher gTextWatcher = new TextWatcher() { @Override public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { public void afterTextChanged(Editable arg0) { // 如果是在Resume的狀態下,當編輯完後,啟動前景發佈訊息的功能 if (gResumed) { nfcAdapter.enableForegroundNdefPush(NDEF.this, getNoteAsNdef()); };
啟動與取消NDEF資料存取
// 啟動NDEF交換資料模式 private void enableNdefExchangeMode() { // 讓NfcAdatper啟動前景Push資料至Tag或應用程式 nfcAdapter.enableForegroundNdefPush(NDEF.this, getNoteAsNdef()); // 讓NfcAdapter啟動能夠在前景模式下進行intent filter的dispatch nfcAdapter.enableForegroundDispatch(this, gNfcPendingIntent, gNdefExchangeFilters, null); } // 取消NDEF交換資料模式 private void disableNdefExchangeMode() { nfcAdapter.disableForegroundNdefPush(this); nfcAdapter.disableForegroundDispatch(this);
啟動與取消Tag寫入模式
// 啟動Tag寫入模式,註冊對應的Intent Filter來前景模式監聽是否有Tag進入的訊息 private void enableTagWriteMode() { gWriteMode = true; IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); gWriteTagFilters = new IntentFilter[]{tagDetected}; nfcAdapter.enableForegroundDispatch(this, gNfcPendingIntent, gWriteTagFilters, null); } // 停止Tag寫入模式,取消前景模式的監測 private void disableTagWriteMode() { gWriteMode = false; nfcAdapter.disableForegroundDispatch(this);
將輸入的內容轉成NdefMessage // 將輸入的內容轉成NdefMessage private NdefMessage getNoteAsNdef() { byte[] textBytes = gNote.getText().toString().getBytes(); NdefRecord textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(), new byte[]{}, textBytes); return new NdefMessage(new NdefRecord[]{textRecord}); }
onResume & onPause @Override protected void onResume() { super.onResume(); gResumed = true; // 處理由Android系統送出的intent filter內容 if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { // 取得NdefMessage NdefMessage[] messages = getNdefMessages(getIntent()); // 取得實際的內容 byte[] payload = messages[0].getRecords()[0].getPayload(); //設定寫入的內容 setNoteBody(new String(payload)); // 往下送出該intent給其他的處理對象 setIntent(new Intent()); } // 啟動前景模式支持Nfc intent處理 enableNdefExchangeMode(); protected void onPause() { super.onPause(); gResumed = false; // 由於NfcAdapter啟動前景模式將相對花費更多的電力,要記得關閉。 nfcAdapter.disableForegroundNdefPush(this);
取得Intent中放入的NdefMessage NdefMessage[] getNdefMessages(Intent intent) { // 解析intent內容 NdefMessage[] msgs = null; String action = intent.getAction(); if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); if (rawMsgs != null) { msgs = new NdefMessage[rawMsgs.length]; for (int i = 0; i < rawMsgs.length; i++) { msgs[i] = (NdefMessage) rawMsgs[i]; } } else { // 未知的tag type byte[] empty = new byte[]{}; NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty); NdefMessage msg = new NdefMessage(new NdefRecord[]{record}); msgs = new NdefMessage[]{msg}; finish(); return msgs;
從輸入框取出要寫入Tag的內容 // 從輸入框取出要寫入Tag的內容 private void setNoteBody(String body) { Editable text = gNote.getText(); text.clear(); text.append(body); }
設定真正要處理動作的Intent // 真正要處理動作的Intent @Override protected void onNewIntent(Intent intent) { // NDEF exchange mode (讀取資料) if (!gWriteMode && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) { NdefMessage[] msgs = getNdefMessages(intent); promptForContent(msgs[0]); // 讀取資料 } // 監測到有指定ACTION進入,代表要寫入資料至Tag中。 // Tag writing mode if (gWriteMode && NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); writeTag(getNoteAsNdef(), detectedTag); // 寫入資料
讀取Tag內容 // 提示讀取出內容會覆蓋textbox目前內容 private void promptForContent(final NdefMessage msg) { new AlertDialog.Builder(this).setTitle("是否取代現在TextBox的內容?").setPositiveButton("是", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { String body = new String(msg.getRecords()[0].getPayload()); setNoteBody(body); } }).setNegativeButton("否", new DialogInterface.OnClickListener() { }).show();
寫入Tag // 寫入Tag boolean writeTag(NdefMessage message, Tag tag) { int size = message.toByteArray().length; try { Ndef ndef = Ndef.get(tag); if (ndef != null) { ndef.connect(); if (!ndef.isWritable()) { toast("這是唯讀標籤"); return false; } if (ndef.getMaxSize() < size) { toast("標籤容量為 " + ndef.getMaxSize() + " bytes, message大小是 " + size + " bytes."); ndef.writeNdefMessage(message); toast("資料成功寫入已格式化標籤"); return true; } else {
NdefFormatable format = NdefFormatable.get(tag); if (format != null) { try { format.connect(); format.format(message); toast("成功格式化標籤與寫入資料"); return true; } catch (IOException e) { toast("無法格式化標籤"); return false; } } else { toast("標籤不支援NDEF."); } catch (Exception e) { toast("寫入失敗"); private void toast(String text) { Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
執行 中文ok
兩個裝置透過NFC交換資料 http://android-er.blogspot.tw/2014/04/example-of-programming- android-nfc.html
其他 http://android-er.blogspot.tw/search?q=NFC