有幾天沒有更新博客了,不過本篇卻準備了許久,但願能帶給每一位開發者最簡單高效的學習方式。廢話到此爲止,下面開始正文。
NFC(Near Field Communication,近場通訊)是一種數據傳輸技術。與Wi-Fi、藍牙、紅外線等數據傳輸技術的一個主要差別就是有效距離通常不能超過4釐米。可是NFC傳輸速度要比紅外快。目前NFC已經出現了一些應用,例如電子標籤識別、刷手機、點對點付款、身份識別、信息記錄等,本篇文章的目的是爲你們揭開NFC標籤的面紗。java
下面咱們先從NFC的工做模式開始闡述NFC,開發NFC必先了解NFC。android
NFC支持以下3種工做模式:讀卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、點對點模式(P2P mode)。數組
下來分別看一下這三種模式:安全
數據在NFC芯片中,能夠簡單理解成「刷標籤」。本質上就是經過支持NFC的手機或其它電子設備從帶有NFC芯片的標籤、貼紙、名片等媒介中讀寫信息。一般NFC標籤是不須要外部供電的。當支持NFC的外設向NFC讀寫數據時,它會發送某種磁場,而這個磁場會自動的向NFC標籤供電。網絡
數據在支持NFC的手機或其它電子設備中,能夠簡單理解成「刷手機」。本質上就是將支持NFC的手機或其它電子設備當成借記卡、公交卡、門禁卡等IC卡使用。基本原理是將相應IC卡中的信息憑證封裝成數據包存儲在支持NFC的外設中 。
在使用時還須要一個NFC射頻器(至關於刷卡器)。將手機靠近NFC射頻器,手機就會接收到NFC射頻器發過來的信號,在經過一系列複雜的驗證後,將IC卡的相應信息傳入NFC射頻器,最後這些IC卡數據會傳入NFC射頻器鏈接的電腦,並進行相應的處理(如電子轉賬、開門等操做)。tcp
該模式與藍牙、紅外差很少,用於不一樣NFC設備之間進行數據交換,不過這個模式已經沒有有「刷」的感受了。其有效距離通常不能超過4釐米,但傳輸創建速度要比紅外和藍牙技術快不少,傳輸速度比紅外塊得多,如過雙方都使用Android4.2,NFC會直接利用藍牙傳輸。這種技術被稱爲Android Beam。因此使用Android Beam傳輸數據的兩部設備再也不限於4釐米以內。
點對點模式的典型應用是兩部支持NFC的手機或平板電腦實現數據的點對點傳輸,例如,交換圖片或同步設備聯繫人。所以,經過NFC,多個設備如數字相機,計算機,手機之間,均可以快速鏈接,並交換資料或者服務。ide
下面看一下NFC、藍牙和紅外之間的差別:學習
對比項 | NFC | 藍牙 | 紅外 |
---|---|---|---|
網絡類型 | 點對點 | 單點對多點 | 點對點 |
有效距離 | <=0.1m | <=10m,最新的藍牙4.0有效距離可達100m | 通常在1m之內,熱技術鏈接,不穩定 |
傳輸速度 | 最大424kbps | 最大24Mbps | 慢速115.2kbps,快速4Mbps |
創建時間 | <0.1s | 6s | 0.5s |
安全性 | 安全,硬件實現 | 安全,軟件實現 | 不安全,使用IRFM時除外 |
通訊模式 | 主動-主動/被動 | 主動-主動 | 主動-主動 |
成本 | 低 | 中 | 低 |
不一樣的NFC標籤之間差別很大,有的只支持簡單的讀寫操做,有時還會採用支持一次性寫入的芯片,將NFC標籤設計成只讀的。固然,也存在一些複雜的NFC標籤,例如,有一些NFC標籤能夠經過硬件加密的方式限制對某一區域的訪問。還有一些標籤自帶操做環境,容許NFC設備與這些標籤進行更復雜的交互。這些標籤中的數據也會採用不一樣的格式。但Android SDK API主要支持NFC論壇標準(Forum Standard),這種標準被稱爲NDEF(NFC Data Exchange Format,NFC數據交換格式)。ui
NDEF格式其實就相似於硬盤的NTFS,下面咱們看一下NDEF數據:this
Android SDK API支持以下3種NDEF數據的操做:
1)從NFC標籤讀取NDEF格式的數據。
2)向NFC標籤寫入NDEF格式的數據。
3)經過Android Beam技術將NDEF數據發送到另外一部NFC設備。
用於描述NDEF格式數據的兩個類:
1)NdefMessage:描述NDEF格式的信息,實際上咱們寫入NFC標籤的就是NdefMessage對象。
2)NdefRecord:描述NDEF信息的一個信息段,一個NdefMessage可能包含一個或者多個NdefRecord。
NdefMessage和NdefRecord是Android NFC技術的核心類,不管讀寫NDEF格式的NFC標籤,仍是經過Android Beam技術傳遞Ndef格式的數據,都須要這兩個類。
對於某些特殊需求,可能要存任意的數據,對於這些數據,咱們就須要自定義格式。這些數據格式實際上就是普通的字節流,至於字節流中的數據表明什麼,就由開發人員本身定義了。
1)設置權限,限制Android版本、安裝的設備:
<uses-sdk android:minSdkVersion="14"/> <uses-permission android:name="android.permission.NFC" /> <!-- 要求當前設備必需要有NFC芯片 --> <uses-feature android:name="android.hardware.nfc" android:required="true" />
2)定義可接收Tag的Activity
Activity清單須要配置一下launchMode屬性:
<activity android:name=".TagTextActivity" android:launchMode="singleTop"/>
而Activity中,咱們也抽取了一個通用的BaseNfcActivity,以下(後面的Activity實現都繼承於BaseNfcActivity):
/** * 1.子類須要在onCreate方法中作Activity初始化。 * 2.子類須要在onNewIntent方法中進行NFC標籤相關操做。 * 當launchMode設置爲singleTop時,第一次運行調用onCreate方法, * 第二次運行將不會建立新的Activity實例,將調用onNewIntent方法 * 因此咱們獲取intent傳遞過來的Tag數據操做放在onNewIntent方法中執行 * 若是在棧中已經有該Activity的實例,就重用該實例(會調用實例的onNewIntent()) * 只要NFC標籤靠近就執行 */ public class BaseNfcActivity extends AppCompatActivity { private NfcAdapter mNfcAdapter; private PendingIntent mPendingIntent; /** * 啓動Activity,界面可見時 */ @Override protected void onStart() { super.onStart(); mNfcAdapter = NfcAdapter.getDefaultAdapter(this); //一旦截獲NFC消息,就會經過PendingIntent調用窗口 mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); } /** * 得到焦點,按鈕能夠點擊 */ @Override public void onResume() { super.onResume(); //設置處理優於全部其餘NFC的處理 if (mNfcAdapter != null) mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); } /** * 暫停Activity,界面獲取焦點,按鈕能夠點擊 */ @Override public void onPause() { super.onPause(); //恢復默認狀態 if (mNfcAdapter != null) mNfcAdapter.disableForegroundDispatch(this); } }
注意:一般來講,全部處理NFC的Activity都要設置launchMode屬性爲singleTop或者singleTask,保證了不管NFC標籤靠近手機多少次,Activity實例只有一個。
接下來看幾個具體的NFC標籤應用實例,經過情景學習快速掌握NFC技術:
場景是這樣的:現將應用程序的包寫到NFC程序上,而後咱們將NFC標籤靠近Android手機,手機就會自動運行包所對應的程序,這個是NFC比較基本的一個應用。下面以貼近標籤自動運行Android自帶的「短信」爲例。
向NFC標籤寫入數據通常分爲三步:
1)獲取Tag對象
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
2)判斷NFC標籤的數據類型(經過Ndef.get方法)
Ndef ndef = Ndef.get(tag);
3)寫入數據
ndef.writeNdefMessage(ndefMessage);
詳細實現代碼以下:
public class RunAppActivity extends BaseNfcActivity{ private String mPackageName = "com.android.mms";//短信 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onNewIntent(Intent intent) { if (mPackageName == null) return; //1.獲取Tag對象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); writeNFCTag(detectedTag); } /** * 往標籤寫數據的方法 * * @param tag */ public void writeNFCTag(Tag tag) { if (tag == null) { return; } NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createApplicationRecord(mPackageName)}); //轉換成字節得到大小 int size = ndefMessage.toByteArray().length; try { //2.判斷NFC標籤的數據類型(經過Ndef.get方法) Ndef ndef = Ndef.get(tag); //判斷是否爲NDEF標籤 if (ndef != null) { ndef.connect(); //判斷是否支持可寫 if (!ndef.isWritable()) { return; } //判斷標籤的容量是否夠用 if (ndef.getMaxSize() < size) { return; } //3.寫入數據 ndef.writeNdefMessage(ndefMessage); Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show(); } else { //當咱們買回來的NFC標籤是沒有格式化的,或者沒有分區的執行此步 //Ndef格式類 NdefFormatable format = NdefFormatable.get(tag); //判斷是否得到了NdefFormatable對象,有一些標籤是隻讀的或者不容許格式化的 if (format != null) { //鏈接 format.connect(); //格式化並將信息寫入標籤 format.format(ndefMessage); Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "寫入失敗", Toast.LENGTH_SHORT).show(); } } } catch (Exception e) { } } }
注意:設置 RunAppActivity 的 launchMode 屬性爲 singleTop。
如今看一下效果圖:
將NFC標籤貼近手機背面,自動寫入數據,此時退出全部程序,返回桌面,而後再將NFC標籤貼近手機背面,將會看到自動打開了「短信」。
下來再看一個有趣的例子:
如何讓NFC標籤貼近手機,手機能夠自動打開一個網頁呢?
首先咱們建立一個NdefRecord,Android已經爲咱們提供好了這樣的方法:
//直接接受一個Uri public NdefRecord createUri(String uriString); //接受一個Uri的對象 public NdefRecord createUri(Uri uri);
實現代碼對比「3.利用NFC標籤讓Android自動運行程序」部分只是修改了writeNFCTag方法中
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createApplicationRecord(mPackageName)});
爲
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createUri(Uri.parse("http://www.baidu.com"))});
其他不變。
上面這個功能仍是比較有用的,例如咱們往某些商品上貼上NFC標籤,裏面寫入該商品的詳細介紹網頁Uri,當用戶貼近商品時,就會自動打開該商品的詳情介紹。
經過上面這兩個案例的學習相信不少人已經對NFC感起了興趣,那麼下來滲透式的分析一下NDEF文本格式,看看NDEF究竟是個什麼東西。
獲取NFC標籤中的數據要經過 NdefRecord.getPayload 方法完成。固然,在處理這些數據以前,最好判斷一下NdefRecord對象中存儲的是否是NDEF文本格式數據。
(1)判斷數據是否爲NDEF格式
1)TNF(類型名格式,Type Name Format)必須是NdefRecord.TNF_WELL_KNOWN。
2)可變的長度類型必須是NdefRecord.RTD_TEXT。
若是這兩個標準同時知足,那麼就爲NDEF格式。
(2)NDEF文本格式規範
無論什麼格式的數據本質上都是由一些字節組成的。對於NDEF文本格式來講,這些數據的第1個字節描述了數據的狀態,而後若干個字節描述文本的語言編碼,最後剩餘字節表示文本數據。這些數據格式由NFC Forum的相關規範定義,能夠經過 http://members.nfc-forum.org/specs/spec_dashboard 下載相關的規範。
下面這兩張表是規範中 3.2節 相對重要的翻譯部分:
NDEF文本數據格式:
偏移量 | 長度(bytes) | 描述 |
---|---|---|
0 | 1 | 狀態字節,見下表(狀態字節編碼格式) |
1 | n | ISO/IANA語言編碼。例如"en-US","fr-CA"。編碼格式是US-ASCII,長度(n)由狀態字節的後6位指定。 |
n+1 | m | 文本數據。編碼格式是UTF-8或UTF-16。編碼格式由狀態字節的前3位指定。 |
狀態字節編碼格式:
字節位(0是最低位,7是最高位) | 含義 |
---|---|
7 | 0:文本編碼爲UTF-8,1:文本編碼爲UTF-16 |
6 | 必須設爲0 |
5..0 | 語言編碼的長度(佔用的字節個數) |
下面咱們動手實現NFC標籤中的文本數據的讀寫操做:
public class ReadTextActivity extends BaseNfcActivity { private TextView mNfcText; private String mTagText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_read_text); mNfcText = (TextView) findViewById(R.id.tv_nfctext); } @Override public void onNewIntent(Intent intent) { //1.獲取Tag對象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //2.獲取Ndef的實例 Ndef ndef = Ndef.get(detectedTag); mTagText = ndef.getType() + "\nmaxsize:" + ndef.getMaxSize() + "bytes\n\n"; readNfcTag(intent); mNfcText.setText(mTagText); } /** * 讀取NFC標籤文本數據 */ private void readNfcTag(Intent intent) { if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) { Parcelable[] rawMsgs = intent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES); NdefMessage msgs[] = null; int contentSize = 0; if (rawMsgs != null) { msgs = new NdefMessage[rawMsgs.length]; for (int i = 0; i < rawMsgs.length; i++) { msgs[i] = (NdefMessage) rawMsgs[i]; contentSize += msgs[i].toByteArray().length; } } try { if (msgs != null) { NdefRecord record = msgs[0].getRecords()[0]; String textRecord = parseTextRecord(record); mTagText += textRecord + "\n\ntext\n" + contentSize + " bytes"; } } catch (Exception e) { } } } /** * 解析NDEF文本數據,從第三個字節開始,後面的文本數據 * @param ndefRecord * @return */ public static String parseTextRecord(NdefRecord ndefRecord) { /** * 判斷數據是否爲NDEF格式 */ //判斷TNF if (ndefRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) { return null; } //判斷可變的長度的類型 if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) { return null; } try { //得到字節數組,而後進行分析 byte[] payload = ndefRecord.getPayload(); //下面開始NDEF文本數據第一個字節,狀態字節 //判斷文本是基於UTF-8仍是UTF-16的,取第一個字節"位與"上16進制的80,16進制的80也就是最高位是1, //其餘位都是0,因此進行"位與"運算後就會保留最高位 String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8" : "UTF-16"; //3f最高兩位是0,第六位是1,因此進行"位與"運算後得到第六位 int languageCodeLength = payload[0] & 0x3f; //下面開始NDEF文本數據第二個字節,語言編碼 //得到語言編碼 String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII"); //下面開始NDEF文本數據後面的字節,解析出文本 String textRecord = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); return textRecord; } catch (Exception e) { throw new IllegalArgumentException(); } } }
注意:Activity清單須要配置一下launchMode屬性(後面同樣要注意):
<activity android:name=".ReadTextActivity" android:launchMode="singleTop"/>
public class WriteTextActivity extends BaseNfcActivity { private String mText = "NFC-NewText-123"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_write_text); } @Override public void onNewIntent(Intent intent) { if (mText == null) return; //獲取Tag對象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); NdefMessage ndefMessage = new NdefMessage( new NdefRecord[] { createTextRecord(mText) }); boolean result = writeTag(ndefMessage, detectedTag); if (result){ Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "寫入失敗", Toast.LENGTH_SHORT).show(); } } /** * 建立NDEF文本數據 * @param text * @return */ public static NdefRecord createTextRecord(String text) { byte[] langBytes = Locale.CHINA.getLanguage().getBytes(Charset.forName("US-ASCII")); Charset utfEncoding = Charset.forName("UTF-8"); //將文本轉換爲UTF-8格式 byte[] textBytes = text.getBytes(utfEncoding); //設置狀態字節編碼最高位數爲0 int utfBit = 0; //定義狀態字節 char status = (char) (utfBit + langBytes.length); byte[] data = new byte[1 + langBytes.length + textBytes.length]; //設置第一個狀態字節,先將狀態碼轉換成字節 data[0] = (byte) status; //設置語言編碼,使用數組拷貝方法,從0開始拷貝到data中,拷貝到data的1到langBytes.length的位置 System.arraycopy(langBytes, 0, data, 1, langBytes.length); //設置文本字節,使用數組拷貝方法,從0開始拷貝到data中,拷貝到data的1 + langBytes.length //到textBytes.length的位置 System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length); //經過字節傳入NdefRecord對象 //NdefRecord.RTD_TEXT:傳入類型 讀寫 NdefRecord ndefRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); return ndefRecord; } /** * 寫數據 * @param ndefMessage 建立好的NDEF文本數據 * @param tag 標籤 * @return */ public static boolean writeTag(NdefMessage ndefMessage, Tag tag) { try { Ndef ndef = Ndef.get(tag); ndef.connect(); ndef.writeNdefMessage(ndefMessage); return true; } catch (Exception e) { } return false; } }
咱們將手機貼近NFC標籤,當寫入成功會彈出「寫入成功」的吐司。下面咱們再驗證一下是否成功寫入:
咱們看到,數據已經寫入成功了,說明到此咱們已經成功的讀寫NFC標籤中的文本數據了。
與NDEF文本格式同樣,存儲在NFC標籤中的Uri也有必定的格式,http://members.nfc-forum.org/specs/spec_dashboard
(1)Uri的格式規範要比文本格式簡單一些:
Name | 偏移 | 大小 | 值 | 描述 |
---|---|---|---|---|
識別碼 | 0 | 1byte | Uri識別碼 | 用於存儲已知Uri的前綴 |
Uri字段 | 1 | N | UTF-8類型字符串 | 用於存儲剩餘字符串 |
(2)Uri的前綴以下(都是十六進制的一個數):
十進制 | 十六進制 | 協議 | 十進制 | 十六進制 | 協議 |
---|---|---|---|---|---|
0 | 0x00 | N/A | 1 | 0x01 | http://www. |
2 | 0x02 | https://www. | 3 | 0x03 | http:// |
4 | 0x04 | https:// | 5 | 0x05 | tel: |
6 | 0x06 | mailto: | 7 | 0x07 | ftp://anonymous:anonymous@ |
8 | 0x08 | ftp://ftp. | 9 | 0x09 | ftps:// |
10 | 0x0A | sftp:// | 11 | 0x0B | smb:// |
12 | 0x0C | nfs:// | 13 | 0x0D | ftp:// |
14 | 0x0E | dav:// | 15 | 0x0F | news: |
16 | 0x10 | telnet:// | 17 | 0x11 | imap: |
18 | 0x12 | rtsp:// | 19 | 0x13 | urn: |
20 | 0x14 | pop: | 21 | 0x15 | sip: |
22 | 0x16 | sips: | 23 | 0x17 | tftp: |
24 | 0x18 | btspp:// | 25 | 0x19 | btl2cap:// |
26 | 0x1A | btgoep:// | 27 | 0x1B | tcpobex:// |
28 | 0x1C | irdaobex:// | 29 | 0x1D | file:// |
30 | 0x1E | urn:epc🆔 | 31 | 0x1F | urn:epc:tag: |
32 | 0x20 | urn:epc:pat: | 33 | 0x21 | urn:epc:raw: |
34 | 0x22 | urn:epc: | 35 | 0x23 | urn:nfc: |
每個協議,都是用十六進制來存儲於識別碼位置(佔1byte)。
是否是相對簡單了些,那麼下來咱們來解析一個Uri。
(3)預先定義已知Uri前綴
這裏咱們定義一個UriPrefix類,以便方便的獲取Uri前綴:
public class UriPrefix { public static final Map<Byte, String> URI_PREFIX_MAP = new HashMap<Byte, String>(); // 預先定義已知Uri前綴 static { URI_PREFIX_MAP.put((byte) 0x00, ""); URI_PREFIX_MAP.put((byte) 0x01, "http://www."); URI_PREFIX_MAP.put((byte) 0x02, "https://www."); URI_PREFIX_MAP.put((byte) 0x03, "http://"); URI_PREFIX_MAP.put((byte) 0x04, "https://"); URI_PREFIX_MAP.put((byte) 0x05, "tel:"); URI_PREFIX_MAP.put((byte) 0x06, "mailto:"); URI_PREFIX_MAP.put((byte) 0x07, "ftp://anonymous:anonymous@"); URI_PREFIX_MAP.put((byte) 0x08, "ftp://ftp."); URI_PREFIX_MAP.put((byte) 0x09, "ftps://"); URI_PREFIX_MAP.put((byte) 0x0A, "sftp://"); URI_PREFIX_MAP.put((byte) 0x0B, "smb://"); URI_PREFIX_MAP.put((byte) 0x0C, "nfs://"); URI_PREFIX_MAP.put((byte) 0x0D, "ftp://"); URI_PREFIX_MAP.put((byte) 0x0E, "dav://"); URI_PREFIX_MAP.put((byte) 0x0F, "news:"); URI_PREFIX_MAP.put((byte) 0x10, "telnet://"); URI_PREFIX_MAP.put((byte) 0x11, "imap:"); URI_PREFIX_MAP.put((byte) 0x12, "rtsp://"); URI_PREFIX_MAP.put((byte) 0x13, "urn:"); URI_PREFIX_MAP.put((byte) 0x14, "pop:"); URI_PREFIX_MAP.put((byte) 0x15, "sip:"); URI_PREFIX_MAP.put((byte) 0x16, "sips:"); URI_PREFIX_MAP.put((byte) 0x17, "tftp:"); URI_PREFIX_MAP.put((byte) 0x18, "btspp://"); URI_PREFIX_MAP.put((byte) 0x19, "btl2cap://"); URI_PREFIX_MAP.put((byte) 0x1A, "btgoep://"); URI_PREFIX_MAP.put((byte) 0x1B, "tcpobex://"); URI_PREFIX_MAP.put((byte) 0x1C, "irdaobex://"); URI_PREFIX_MAP.put((byte) 0x1D, "file://"); URI_PREFIX_MAP.put((byte) 0x1E, "urn:epc:id:"); URI_PREFIX_MAP.put((byte) 0x1F, "urn:epc:tag:"); URI_PREFIX_MAP.put((byte) 0x20, "urn:epc:pat:"); URI_PREFIX_MAP.put((byte) 0x21, "urn:epc:raw:"); URI_PREFIX_MAP.put((byte) 0x22, "urn:epc:"); URI_PREFIX_MAP.put((byte) 0x23, "urn:nfc:"); } }
而後咱們來看一下清單文件中Activity的相關配置:
<activity android:name=".ReadWriteUriActivity" android:label="讀寫NFC標籤的Uri" android:launchMode="singleTop" > <intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> <!-- 攔截NFC標籤中存儲有如下Uri前綴的 --> <data android:scheme="http" /> <data android:scheme="https" /> <data android:scheme="ftp" /> </intent-filter> <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>
好了,接下來就能夠進行讀寫NFC標籤中的Uri數據了:
public class ReadUriActivity extends BaseNfcActivity { private TextView mNfcText; private String mTagText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_read_uri); mNfcText = (TextView) findViewById(R.id.tv_nfctext); } @Override public void onNewIntent(Intent intent) { //獲取Tag對象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //獲取Ndef的實例 Ndef ndef = Ndef.get(detectedTag); mTagText = ndef.getType() + "\n max size:" + ndef.getMaxSize() + " bytes\n\n"; readNfcTag(intent); mNfcText.setText(mTagText); } /** * 讀取NFC標籤Uri */ private void readNfcTag(Intent intent) { if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) { Parcelable[] rawMsgs = intent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES); NdefMessage ndefMessage = null; int contentSize = 0; if (rawMsgs != null) { if (rawMsgs.length > 0) { ndefMessage = (NdefMessage) rawMsgs[0]; contentSize = ndefMessage.toByteArray().length; } else { return; } } try { NdefRecord ndefRecord = ndefMessage.getRecords()[0]; Log.i("JAVA",ndefRecord.toString()); Uri uri = parse(ndefRecord); Log.i("JAVA","uri:"+uri.toString()); mTagText += uri.toString() + "\n\nUri\n" + contentSize + " bytes"; } catch (Exception e) { } } } /** * 解析NdefRecord中Uri數據 * @param record * @return */ public static Uri parse(NdefRecord record) { short tnf = record.getTnf(); if (tnf == NdefRecord.TNF_WELL_KNOWN) { return parseWellKnown(record); } else if (tnf == NdefRecord.TNF_ABSOLUTE_URI) { return parseAbsolute(record); } throw new IllegalArgumentException("Unknown TNF " + tnf); } /** * 處理絕對的Uri * 沒有Uri識別碼,也就是沒有Uri前綴,存儲的所有是字符串 * @param ndefRecord 描述NDEF信息的一個信息段,一個NdefMessage可能包含一個或者多個NdefRecord * @return */ private static Uri parseAbsolute(NdefRecord ndefRecord) { //獲取全部的字節數據 byte[] payload = ndefRecord.getPayload(); Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8"))); return uri; } /** * 處理已知類型的Uri * @param ndefRecord * @return */ private static Uri parseWellKnown(NdefRecord ndefRecord) { //判斷數據是不是Uri類型的 if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_URI)) return null; //獲取全部的字節數據 byte[] payload = ndefRecord.getPayload(); String prefix = UriPrefix.URI_PREFIX_MAP.get(payload[0]); byte[] prefixBytes = prefix.getBytes(Charset.forName("UTF-8")); byte[] fullUri = new byte[prefixBytes.length + payload.length - 1]; System.arraycopy(prefixBytes, 0, fullUri, 0, prefixBytes.length); System.arraycopy(payload, 1, fullUri, prefixBytes.length, payload.length - 1); Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8"))); return uri; } }
public class WriteUriActivity extends BaseNfcActivity { private String mUri = "http://www.baidu.com"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_write_uri); } public void onNewIntent(Intent intent) { Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createUriRecord(mUri)}); boolean result = writeTag(ndefMessage, detectedTag); if (result){ Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "寫入失敗", Toast.LENGTH_SHORT).show(); } } /** * 將Uri轉成NdefRecord * @param uriStr * @return */ public static NdefRecord createUriRecord(String uriStr) { byte prefix = 0; for (Byte b : UriPrefix.URI_PREFIX_MAP.keySet()) { String prefixStr = UriPrefix.URI_PREFIX_MAP.get(b).toLowerCase(); if ("".equals(prefixStr)) continue; if (uriStr.toLowerCase().startsWith(prefixStr)) { prefix = b; uriStr = uriStr.substring(prefixStr.length()); break; } } byte[] data = new byte[1 + uriStr.length()]; data[0] = prefix; System.arraycopy(uriStr.getBytes(), 0, data, 1, uriStr.length()); NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], data); return record; } /** * 寫入標籤 * @param message * @param tag * @return */ public static 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()) { return false; } if (ndef.getMaxSize() < size) { return false; } ndef.writeNdefMessage(message); return true; } } catch (Exception e) { } return false; } }
咱們將手機貼近NFC標籤,寫入成功後驗證一下是否成功寫入:
咱們看到,數據已經寫入成功了,說明到此咱們已經成功的讀寫NFC標籤中的Uri數據了。
到這裏,NDEF格式就大體說完了,那麼接下來看一下非NDEF格式的數據。
將NFC標籤的存儲區域分爲16個頁,每個頁能夠存儲4個字節,一個可存儲64個字節(512位)。頁碼從0開始(0至15)。前4頁(0至3)存儲了NFC標籤相關的信息(如NFC標籤的序列號、控制位等)。從第5頁開始存儲實際的數據(4至15頁)。
使用MifareUltralight.get方法獲取MifareUltralight對象,而後調用MifareUltralight.connect方法進行鏈接,並使用MifareUltralight.writePage方法每次寫入1頁(4個字節)。也可使用MifareUltralight.readPages方法每次連續讀取4頁。若是讀取的頁的序號超過15,則從頭開始讀。例如,從第15頁(序號爲14)開始讀。readPages方法會讀取1四、1五、0、1頁的數據。
public class ReadMUActivity extends BaseNfcActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_read_mu); } @Override public void onNewIntent(Intent intent) { Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); String[] techList = tag.getTechList(); boolean haveMifareUltralight = false; for (String tech : techList) { if (tech.indexOf("MifareUltralight") >= 0) { haveMifareUltralight = true; break; } } if (!haveMifareUltralight) { Toast.makeText(this, "不支持MifareUltralight數據格式", Toast.LENGTH_SHORT).show(); return; } String data = readTag(tag); if (data != null) Toast.makeText(this, data, Toast.LENGTH_SHORT).show(); } public String readTag(Tag tag) { MifareUltralight ultralight = MifareUltralight.get(tag); try { ultralight.connect(); byte[] data = ultralight.readPages(4); return new String(data, Charset.forName("GB2312")); } catch (Exception e) { } finally { try { ultralight.close(); } catch (Exception e) { } } return null; } }
public class WriteMUActivity extends BaseNfcActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_write_mu); } @Override public void onNewIntent(Intent intent) { Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); String[] techList = tag.getTechList(); boolean haveMifareUltralight = false; for (String tech : techList) { if (tech.indexOf("MifareUltralight") >= 0) { haveMifareUltralight = true; break; } } if (!haveMifareUltralight) { Toast.makeText(this, "不支持MifareUltralight數據格式", Toast.LENGTH_SHORT).show(); return; } writeTag(tag); } public void writeTag(Tag tag) { MifareUltralight ultralight = MifareUltralight.get(tag); try { ultralight.connect(); //寫入八個漢字,從第五頁開始寫,中文須要轉換成GB2312格式 ultralight.writePage(4, "北京".getBytes(Charset.forName("GB2312"))); ultralight.writePage(5, "上海".getBytes(Charset.forName("GB2312"))); ultralight.writePage(6, "廣州".getBytes(Charset.forName("GB2312"))); ultralight.writePage(7, "天津".getBytes(Charset.forName("GB2312"))); Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show(); } catch (Exception e) { } finally { try { ultralight.close(); } catch (Exception e) { } } } }
咱們將手機貼近NFC標籤,寫入成功後驗證一下是否成功寫入:
咱們看到,彈出了「北京上海廣州天津」,說明數據已經寫入成功了,說明到此咱們已經成功的讀寫NFC非NDEF格式的數據了。