android nfc常用標(biāo)簽讀取總結(jié)
有幾天沒(méi)有更新博客了,不過(guò)本篇卻準(zhǔn)備了許久,希望能帶給每一位開(kāi)發(fā)者最簡(jiǎn)單高效的學(xué)習(xí)方式。廢話到此為止,下面開(kāi)始正文。
NFC(Near Field Communication,近場(chǎng)通信)是一種數(shù)據(jù)傳輸技術(shù)。與Wi-Fi、藍(lán)牙、紅外線等數(shù)據(jù)傳輸技術(shù)的一個(gè)主要差異就是有效距離一般不能超過(guò)4厘米。但是NFC傳輸速度要比紅外快。目前NFC已經(jīng)出現(xiàn)了一些應(yīng)用,例如電子標(biāo)簽識(shí)別、刷手機(jī)、點(diǎn)對(duì)點(diǎn)付款、身份識(shí)別、信息記錄等,本篇文章的目的是為大家揭開(kāi)NFC標(biāo)簽的面紗。
下面我們先從NFC的工作模式開(kāi)始闡述NFC,開(kāi)發(fā)NFC必先了解NFC。
1.NFC的工作模式
NFC支持如下3種工作模式:讀卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、點(diǎn)對(duì)點(diǎn)模式(P2P mode)。
下來(lái)分別看一下這三種模式:
(1)讀卡器模式
數(shù)據(jù)在NFC芯片中,可以簡(jiǎn)單理解成“刷標(biāo)簽”。本質(zhì)上就是通過(guò)支持NFC的手機(jī)或其它電子設(shè)備從帶有NFC芯片的標(biāo)簽、貼紙、名片等媒介中讀寫(xiě)信息。通常NFC標(biāo)簽是不需要外部供電的。當(dāng)支持NFC的外設(shè)向NFC讀寫(xiě)數(shù)據(jù)時(shí),它會(huì)發(fā)送某種磁場(chǎng),而這個(gè)磁場(chǎng)會(huì)自動(dòng)的向NFC標(biāo)簽供電。
(2)仿真卡模式
數(shù)據(jù)在支持NFC的手機(jī)或其它電子設(shè)備中,可以簡(jiǎn)單理解成“刷手機(jī)”。本質(zhì)上就是將支持NFC的手機(jī)或其它電子設(shè)備當(dāng)成借記卡、公交卡、門(mén)禁卡等IC卡使用?;驹硎菍⑾鄳?yīng)IC卡中的信息憑證封裝成數(shù)據(jù)包存儲(chǔ)在支持NFC的外設(shè)中 。
在使用時(shí)還需要一個(gè)NFC射頻器(相當(dāng)于刷卡器)。將手機(jī)靠近NFC射頻器,手機(jī)就會(huì)接收到NFC射頻器發(fā)過(guò)來(lái)的信號(hào),在通過(guò)一系列復(fù)雜的驗(yàn)證后,將IC卡的相應(yīng)信息傳入NFC射頻器,最后這些IC卡數(shù)據(jù)會(huì)傳入NFC射頻器連接的電腦,并進(jìn)行相應(yīng)的處理(如電子轉(zhuǎn)帳、開(kāi)門(mén)等操作)。
(3)點(diǎn)對(duì)點(diǎn)模式
該模式與藍(lán)牙、紅外差不多,用于不同NFC設(shè)備之間進(jìn)行數(shù)據(jù)交換,不過(guò)這個(gè)模式已經(jīng)沒(méi)有有“刷”的感覺(jué)了。其有效距離一般不能超過(guò)4厘米,但傳輸建立速度要比紅外和藍(lán)牙技術(shù)快很多,傳輸速度比紅外塊得多,如過(guò)雙方都使用Android4.2,NFC會(huì)直接利用藍(lán)牙傳輸。這種技術(shù)被稱為Android Beam。所以使用Android Beam傳輸數(shù)據(jù)的兩部設(shè)備不再限于4厘米之內(nèi)。
點(diǎn)對(duì)點(diǎn)模式的典型應(yīng)用是兩部支持NFC的手機(jī)或平板電腦實(shí)現(xiàn)數(shù)據(jù)的點(diǎn)對(duì)點(diǎn)傳輸,例如,交換圖片或同步設(shè)備聯(lián)系人。因此,通過(guò)NFC,多個(gè)設(shè)備如數(shù)字相機(jī),計(jì)算機(jī),手機(jī)之間,都可以快速連接,并交換資料或者服務(wù)。
下面看一下NFC、藍(lán)牙和紅外之間的差異:
對(duì)比項(xiàng) | NFC | 藍(lán)牙 | 紅外 |
---|---|---|---|
網(wǎng)絡(luò)類(lèi)型 | 點(diǎn)對(duì)點(diǎn) | 單點(diǎn)對(duì)多點(diǎn) | 點(diǎn)對(duì)點(diǎn) |
有效距離 | <=0.1m | <=10m,最新的藍(lán)牙4.0有效距離可達(dá)100m | 一般在1m以內(nèi),熱技術(shù)連接,不穩(wěn)定 |
傳輸速度 | 最大424kbps | 最大24Mbps | 慢速115.2kbps,快速4Mbps |
建立時(shí)間 | <0.1s | 6s | 0.5s |
安全性 | 安全,硬件實(shí)現(xiàn) | 安全,軟件實(shí)現(xiàn) | 不安全,使用IRFM時(shí)除外 |
通信模式 | 主動(dòng)-主動(dòng)/被動(dòng) | 主動(dòng)-主動(dòng) | 主動(dòng)-主動(dòng) |
成本 | 低 | 中 | 低 |
2.Android對(duì)NFC的支持
不同的NFC標(biāo)簽之間差異很大,有的只支持簡(jiǎn)單的讀寫(xiě)操作,有時(shí)還會(huì)采用支持一次性寫(xiě)入的芯片,將NFC標(biāo)簽設(shè)計(jì)成只讀的。當(dāng)然,也存在一些復(fù)雜的NFC標(biāo)簽,例如,有一些NFC標(biāo)簽可以通過(guò)硬件加密的方式限制對(duì)某一區(qū)域的訪問(wèn)。還有一些標(biāo)簽自帶操作環(huán)境,允許NFC設(shè)備與這些標(biāo)簽進(jìn)行更復(fù)雜的交互。這些標(biāo)簽中的數(shù)據(jù)也會(huì)采用不同的格式。但Android SDK API主要支持NFC論壇標(biāo)準(zhǔn)(Forum Standard),這種標(biāo)準(zhǔn)被稱為NDEF(NFC Data Exchange Format,NFC數(shù)據(jù)交換格式)。
NDEF格式其實(shí)就類(lèi)似于硬盤(pán)的NTFS,下面我們看一下NDEF數(shù)據(jù):
(1)NDEF數(shù)據(jù)的操作
Android SDK API支持如下3種NDEF數(shù)據(jù)的操作:
1)從NFC標(biāo)簽讀取NDEF格式的數(shù)據(jù)。
2)向NFC標(biāo)簽寫(xiě)入NDEF格式的數(shù)據(jù)。
3)通過(guò)Android Beam技術(shù)將NDEF數(shù)據(jù)發(fā)送到另一部NFC設(shè)備。
用于描述NDEF格式數(shù)據(jù)的兩個(gè)類(lèi):
1)NdefMessage:描述NDEF格式的信息,實(shí)際上我們寫(xiě)入NFC標(biāo)簽的就是NdefMessage對(duì)象。
2)NdefRecord:描述NDEF信息的一個(gè)信息段,一個(gè)NdefMessage可能包含一個(gè)或者多個(gè)NdefRecord。
NdefMessage和NdefRecord是Android NFC技術(shù)的核心類(lèi),無(wú)論讀寫(xiě)NDEF格式的NFC標(biāo)簽,還是通過(guò)Android Beam技術(shù)傳遞Ndef格式的數(shù)據(jù),都需要這兩個(gè)類(lèi)。
(2)非NDEF數(shù)據(jù)的操作
對(duì)于某些特殊需求,可能要存任意的數(shù)據(jù),對(duì)于這些數(shù)據(jù),我們就需要自定義格式。這些數(shù)據(jù)格式實(shí)際上就是普通的字節(jié)流,至于字節(jié)流中的數(shù)據(jù)代表什么,就由開(kāi)發(fā)人員自己定義了。
(3)編寫(xiě)NFC程序的基本步驟
1)設(shè)置權(quán)限,限制Android版本、安裝的設(shè)備:
<uses-sdk android:minSdkVersion="14"/> <uses-permission android:name="android.permission.NFC" /> <!-- 要求當(dāng)前設(shè)備必須要有NFC芯片 --> <uses-feature android:name="android.hardware.nfc" android:required="true" />
2)定義可接收Tag的Activity
Activity清單需要配置一下launchMode屬性:
<activity android:name=".TagTextActivity" android:launchMode="singleTop"/>
而Activity中,我們也抽取了一個(gè)通用的BaseNfcActivity,如下(后面的Activity實(shí)現(xiàn)都繼承于BaseNfcActivity):
/** * 1.子類(lèi)需要在onCreate方法中做Activity初始化。 * 2.子類(lèi)需要在onNewIntent方法中進(jìn)行NFC標(biāo)簽相關(guān)操作。 * 當(dāng)launchMode設(shè)置為singleTop時(shí),第一次運(yùn)行調(diào)用onCreate方法, * 第二次運(yùn)行將不會(huì)創(chuàng)建新的Activity實(shí)例,將調(diào)用onNewIntent方法 * 所以我們獲取intent傳遞過(guò)來(lái)的Tag數(shù)據(jù)操作放在onNewIntent方法中執(zhí)行 * 如果在棧中已經(jīng)有該Activity的實(shí)例,就重用該實(shí)例(會(huì)調(diào)用實(shí)例的onNewIntent()) * 只要NFC標(biāo)簽靠近就執(zhí)行 */ public class BaseNfcActivity extends AppCompatActivity { private NfcAdapter mNfcAdapter; private PendingIntent mPendingIntent; /** * 啟動(dòng)Activity,界面可見(jiàn)時(shí) */ @Override protected void onStart() { super.onStart(); mNfcAdapter = NfcAdapter.getDefaultAdapter(this); //一旦截獲NFC消息,就會(huì)通過(guò)PendingIntent調(diào)用窗口 mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); } /** * 獲得焦點(diǎn),按鈕可以點(diǎn)擊 */ @Override public void onResume() { super.onResume(); //設(shè)置處理優(yōu)于所有其他NFC的處理 if (mNfcAdapter != null) mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); } /** * 暫停Activity,界面獲取焦點(diǎn),按鈕可以點(diǎn)擊 */ @Override public void onPause() { super.onPause(); //恢復(fù)默認(rèn)狀態(tài) if (mNfcAdapter != null) mNfcAdapter.disableForegroundDispatch(this); } }
注意:通常來(lái)說(shuō),所有處理NFC的Activity都要設(shè)置launchMode屬性為singleTop或者singleTask,保證了無(wú)論NFC標(biāo)簽靠近手機(jī)多少次,Activity實(shí)例只有一個(gè)。
接下來(lái)看幾個(gè)具體的NFC標(biāo)簽應(yīng)用實(shí)例,通過(guò)情景學(xué)習(xí)快速掌握NFC技術(shù):
3.兩個(gè)NFC標(biāo)簽的簡(jiǎn)單實(shí)例
1.利用NFC標(biāo)簽讓Android自動(dòng)運(yùn)行程序
場(chǎng)景是這樣的:現(xiàn)將應(yīng)用程序的包寫(xiě)到NFC程序上,然后我們將NFC標(biāo)簽靠近Android手機(jī),手機(jī)就會(huì)自動(dòng)運(yùn)行包所對(duì)應(yīng)的程序,這個(gè)是NFC比較基本的一個(gè)應(yīng)用。下面以貼近標(biāo)簽自動(dòng)運(yùn)行Android自帶的“短信”為例。
向NFC標(biāo)簽寫(xiě)入數(shù)據(jù)一般分為三步:
1)獲取Tag對(duì)象
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
2)判斷NFC標(biāo)簽的數(shù)據(jù)類(lèi)型(通過(guò)Ndef.get方法)
Ndef ndef = Ndef.get(tag);
3)寫(xiě)入數(shù)據(jù)
ndef.writeNdefMessage(ndefMessage);
詳細(xì)實(shí)現(xiàn)代碼如下:
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對(duì)象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); writeNFCTag(detectedTag); } /** * 往標(biāo)簽寫(xiě)數(shù)據(jù)的方法 * * @param tag */ public void writeNFCTag(Tag tag) { if (tag == null) { return; } NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createApplicationRecord(mPackageName)}); //轉(zhuǎn)換成字節(jié)獲得大小 int size = ndefMessage.toByteArray().length; try { //2.判斷NFC標(biāo)簽的數(shù)據(jù)類(lèi)型(通過(guò)Ndef.get方法) Ndef ndef = Ndef.get(tag); //判斷是否為NDEF標(biāo)簽 if (ndef != null) { ndef.connect(); //判斷是否支持可寫(xiě) if (!ndef.isWritable()) { return; } //判斷標(biāo)簽的容量是否夠用 if (ndef.getMaxSize() < size) { return; } //3.寫(xiě)入數(shù)據(jù) ndef.writeNdefMessage(ndefMessage); Toast.makeText(this, "寫(xiě)入成功", Toast.LENGTH_SHORT).show(); } else { //當(dāng)我們買(mǎi)回來(lái)的NFC標(biāo)簽是沒(méi)有格式化的,或者沒(méi)有分區(qū)的執(zhí)行此步 //Ndef格式類(lèi) NdefFormatable format = NdefFormatable.get(tag); //判斷是否獲得了NdefFormatable對(duì)象,有一些標(biāo)簽是只讀的或者不允許格式化的 if (format != null) { //連接 format.connect(); //格式化并將信息寫(xiě)入標(biāo)簽 format.format(ndefMessage); Toast.makeText(this, "寫(xiě)入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "寫(xiě)入失敗", Toast.LENGTH_SHORT).show(); } } } catch (Exception e) { } } }
注意:設(shè)置 RunAppActivity 的 launchMode 屬性為 singleTop。
現(xiàn)在看一下效果圖:
將NFC標(biāo)簽貼近手機(jī)背面,自動(dòng)寫(xiě)入數(shù)據(jù),此時(shí)退出所有程序,返回桌面,然后再將NFC標(biāo)簽貼近手機(jī)背面,將會(huì)看到自動(dòng)打開(kāi)了“短信”。
下來(lái)再看一個(gè)有趣的例子:
2.利用NFC標(biāo)簽讓Android自動(dòng)打開(kāi)網(wǎng)頁(yè)
如何讓NFC標(biāo)簽貼近手機(jī),手機(jī)可以自動(dòng)打開(kāi)一個(gè)網(wǎng)頁(yè)呢?
首先我們創(chuàng)建一個(gè)NdefRecord,Android已經(jīng)為我們提供好了這樣的方法:
//直接接受一個(gè)Uri public NdefRecord createUri(String uriString); //接受一個(gè)Uri的對(duì)象 public NdefRecord createUri(Uri uri);
實(shí)現(xiàn)代碼對(duì)比“3.利用NFC標(biāo)簽讓Android自動(dòng)運(yùn)行程序”部分只是修改了writeNFCTag方法中
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createApplicationRecord(mPackageName)});
為
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createUri(Uri.parse(http://www.baidu.com))});
其余不變。
上面這個(gè)功能還是比較有用的,例如我們往某些商品上貼上NFC標(biāo)簽,里面寫(xiě)入該商品的詳細(xì)介紹網(wǎng)頁(yè)Uri,當(dāng)用戶貼近商品時(shí),就會(huì)自動(dòng)打開(kāi)該商品的詳情介紹。
通過(guò)上面這兩個(gè)案例的學(xué)習(xí)相信很多人已經(jīng)對(duì)NFC感起了興趣,那么下來(lái)滲透式的分析一下NDEF文本格式,看看NDEF到底是個(gè)什么東西。
4.NDEF文本格式深度解析
獲取NFC標(biāo)簽中的數(shù)據(jù)要通過(guò) NdefRecord.getPayload 方法完成。當(dāng)然,在處理這些數(shù)據(jù)之前,最好判斷一下NdefRecord對(duì)象中存儲(chǔ)的是不是NDEF文本格式數(shù)據(jù)。
(1)判斷數(shù)據(jù)是否為NDEF格式
1)TNF(類(lèi)型名格式,Type Name Format)必須是NdefRecord.TNF_WELL_KNOWN。
2)可變的長(zhǎng)度類(lèi)型必須是NdefRecord.RTD_TEXT。
如果這兩個(gè)標(biāo)準(zhǔn)同時(shí)滿足,那么就為NDEF格式。
(2)NDEF文本格式規(guī)范
不管什么格式的數(shù)據(jù)本質(zhì)上都是由一些字節(jié)組成的。對(duì)于NDEF文本格式來(lái)說(shuō),這些數(shù)據(jù)的第1個(gè)字節(jié)描述了數(shù)據(jù)的狀態(tài),然后若干個(gè)字節(jié)描述文本的語(yǔ)言編碼,最后剩余字節(jié)表示文本數(shù)據(jù)。這些數(shù)據(jù)格式由NFC Forum的相關(guān)規(guī)范定義,可以通過(guò) http://members.nfc-forum.org/specs/spec_dashboard 下載相關(guān)的規(guī)范。
下面這兩張表是規(guī)范中 3.2節(jié) 相對(duì)重要的翻譯部分:
NDEF文本數(shù)據(jù)格式:
偏移量 | 長(zhǎng)度(bytes) | 描述 |
---|---|---|
0 | 1 | 狀態(tài)字節(jié),見(jiàn)下表(狀態(tài)字節(jié)編碼格式) |
1 | n | ISO/IANA語(yǔ)言編碼。例如”en-US”,”fr-CA”。編碼格式是US-ASCII,長(zhǎng)度(n)由狀態(tài)字節(jié)的后6位指定。 |
n+1 | m | 文本數(shù)據(jù)。編碼格式是UTF-8或UTF-16。編碼格式由狀態(tài)字節(jié)的前3位指定。 |
狀態(tài)字節(jié)編碼格式:
字節(jié)位(0是最低位,7是最高位) | 含義 |
---|---|
7 | 0:文本編碼為UTF-8,1:文本編碼為UTF-16 |
6 | 必須設(shè)為0 |
5..0 | 語(yǔ)言編碼的長(zhǎng)度(占用的字節(jié)個(gè)數(shù)) |
下面我們動(dòng)手實(shí)現(xiàn)NFC標(biāo)簽中的文本數(shù)據(jù)的讀寫(xiě)操作:
1.讀NFC標(biāo)簽文本數(shù)據(jù)
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對(duì)象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //2.獲取Ndef的實(shí)例 Ndef ndef = Ndef.get(detectedTag); mTagText = ndef.getType() + "\nmaxsize:" + ndef.getMaxSize() + "bytes\n\n"; readNfcTag(intent); mNfcText.setText(mTagText); } /** * 讀取NFC標(biāo)簽文本數(shù)據(jù) */ 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文本數(shù)據(jù),從第三個(gè)字節(jié)開(kāi)始,后面的文本數(shù)據(jù) * @param ndefRecord * @return */ public static String parseTextRecord(NdefRecord ndefRecord) { /** * 判斷數(shù)據(jù)是否為NDEF格式 */ //判斷TNF if (ndefRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) { return null; } //判斷可變的長(zhǎng)度的類(lèi)型 if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) { return null; } try { //獲得字節(jié)數(shù)組,然后進(jìn)行分析 byte[] payload = ndefRecord.getPayload(); //下面開(kāi)始NDEF文本數(shù)據(jù)第一個(gè)字節(jié),狀態(tài)字節(jié) //判斷文本是基于UTF-8還是UTF-16的,取第一個(gè)字節(jié)"位與"上16進(jìn)制的80,16進(jìn)制的80也就是最高位是1, //其他位都是0,所以進(jìn)行"位與"運(yùn)算后就會(huì)保留最高位 String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8" : "UTF-16"; //3f最高兩位是0,第六位是1,所以進(jìn)行"位與"運(yùn)算后獲得第六位 int languageCodeLength = payload[0] & 0x3f; //下面開(kāi)始NDEF文本數(shù)據(jù)第二個(gè)字節(jié),語(yǔ)言編碼 //獲得語(yǔ)言編碼 String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII"); //下面開(kāi)始NDEF文本數(shù)據(jù)后面的字節(jié),解析出文本 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"/>
2.寫(xiě)NFC標(biāo)簽文本數(shù)據(jù)
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對(duì)象 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, "寫(xiě)入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "寫(xiě)入失敗", Toast.LENGTH_SHORT).show(); } } /** * 創(chuàng)建NDEF文本數(shù)據(jù) * @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"); //將文本轉(zhuǎn)換為UTF-8格式 byte[] textBytes = text.getBytes(utfEncoding); //設(shè)置狀態(tài)字節(jié)編碼最高位數(shù)為0 int utfBit = 0; //定義狀態(tài)字節(jié) char status = (char) (utfBit + langBytes.length); byte[] data = new byte[1 + langBytes.length + textBytes.length]; //設(shè)置第一個(gè)狀態(tài)字節(jié),先將狀態(tài)碼轉(zhuǎn)換成字節(jié) data[0] = (byte) status; //設(shè)置語(yǔ)言編碼,使用數(shù)組拷貝方法,從0開(kāi)始拷貝到data中,拷貝到data的1到langBytes.length的位置 System.arraycopy(langBytes, 0, data, 1, langBytes.length); //設(shè)置文本字節(jié),使用數(shù)組拷貝方法,從0開(kāi)始拷貝到data中,拷貝到data的1 + langBytes.length //到textBytes.length的位置 System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length); //通過(guò)字節(jié)傳入NdefRecord對(duì)象 //NdefRecord.RTD_TEXT:傳入類(lèi)型 讀寫(xiě) NdefRecord ndefRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); return ndefRecord; } /** * 寫(xiě)數(shù)據(jù) * @param ndefMessage 創(chuàng)建好的NDEF文本數(shù)據(jù) * @param tag 標(biāo)簽 * @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; } }
我們將手機(jī)貼近NFC標(biāo)簽,當(dāng)寫(xiě)入成功會(huì)彈出“寫(xiě)入成功”的吐司。下面我們?cè)衮?yàn)證一下是否成功寫(xiě)入:
我們看到,數(shù)據(jù)已經(jīng)寫(xiě)入成功了,說(shuō)明到此我們已經(jīng)成功的讀寫(xiě)NFC標(biāo)簽中的文本數(shù)據(jù)了。
5.NDEF Uri格式深度解析
與NDEF文本格式一樣,存儲(chǔ)在NFC標(biāo)簽中的Uri也有一定的格式,http://members.nfc-forum.org/specs/spec_dashboard
(1)Uri的格式規(guī)范要比文本格式簡(jiǎn)單一些:
Name | 偏移 | 大小 | 值 | 描述 |
---|---|---|---|---|
識(shí)別碼 | 0 | 1byte | Uri識(shí)別碼 | 用于存儲(chǔ)已知Uri的前綴 |
Uri字段 | 1 | N | UTF-8類(lèi)型字符串 | 用于存儲(chǔ)剩余字符串 |
(2)Uri的前綴如下(都是十六進(jìn)制的一個(gè)數(shù)):
十進(jìn)制 | 十六進(jìn)制 | 協(xié)議 | 十進(jìn)制 | 十六進(jìn)制 | 協(xié)議 |
---|---|---|---|---|---|
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:id: | 31 | 0x1F | urn:epc:tag: |
32 | 0x20 | urn:epc:pat: | 33 | 0x21 | urn:epc:raw: |
34 | 0x22 | urn:epc: | 35 | 0x23 | urn:nfc: |
每一個(gè)協(xié)議,都是用十六進(jìn)制來(lái)存儲(chǔ)于識(shí)別碼位置(占1byte)。
是不是相對(duì)簡(jiǎn)單了些,那么下來(lái)我們來(lái)解析一個(gè)Uri。
(3)預(yù)先定義已知Uri前綴
這里我們定義一個(gè)UriPrefix類(lèi),以便方便的獲取Uri前綴:
public class UriPrefix { public static final Map<Byte, String> URI_PREFIX_MAP = new HashMap<Byte, String>(); // 預(yù)先定義已知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:"); } }
然后我們來(lái)看一下清單文件中Activity的相關(guān)配置:
<activity android:name=".ReadWriteUriActivity" android:label="讀寫(xiě)NFC標(biāo)簽的Uri" android:launchMode="singleTop" > <intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> <!-- 攔截NFC標(biāo)簽中存儲(chǔ)有以下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>
好了,接下來(lái)就可以進(jìn)行讀寫(xiě)NFC標(biāo)簽中的Uri數(shù)據(jù)了:
1.讀NFC標(biāo)簽中的Uri數(shù)據(jù)
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對(duì)象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //獲取Ndef的實(shí)例 Ndef ndef = Ndef.get(detectedTag); mTagText = ndef.getType() + "\n max size:" + ndef.getMaxSize() + " bytes\n\n"; readNfcTag(intent); mNfcText.setText(mTagText); } /** * 讀取NFC標(biāo)簽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數(shù)據(jù) * @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); } /** * 處理絕對(duì)的Uri * 沒(méi)有Uri識(shí)別碼,也就是沒(méi)有Uri前綴,存儲(chǔ)的全部是字符串 * @param ndefRecord 描述NDEF信息的一個(gè)信息段,一個(gè)NdefMessage可能包含一個(gè)或者多個(gè)NdefRecord * @return */ private static Uri parseAbsolute(NdefRecord ndefRecord) { //獲取所有的字節(jié)數(shù)據(jù) byte[] payload = ndefRecord.getPayload(); Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8"))); return uri; } /** * 處理已知類(lèi)型的Uri * @param ndefRecord * @return */ private static Uri parseWellKnown(NdefRecord ndefRecord) { //判斷數(shù)據(jù)是否是Uri類(lèi)型的 if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_URI)) return null; //獲取所有的字節(jié)數(shù)據(jù) 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; } }
2.寫(xiě)NFC標(biāo)簽中的Uri數(shù)據(jù)
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, "寫(xiě)入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "寫(xiě)入失敗", Toast.LENGTH_SHORT).show(); } } /** * 將Uri轉(zhuǎn)成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; } /** * 寫(xiě)入標(biāo)簽 * @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; } }
我們將手機(jī)貼近NFC標(biāo)簽,寫(xiě)入成功后驗(yàn)證一下是否成功寫(xiě)入:
我們看到,數(shù)據(jù)已經(jīng)寫(xiě)入成功了,說(shuō)明到此我們已經(jīng)成功的讀寫(xiě)NFC標(biāo)簽中的Uri數(shù)據(jù)了。
到這里,NDEF格式就大致說(shuō)完了,那么接下來(lái)看一下非NDEF格式的數(shù)據(jù)。
6.非NDEF格式深度解析
1.MifareUltralight數(shù)據(jù)格式
將NFC標(biāo)簽的存儲(chǔ)區(qū)域分為16個(gè)頁(yè),每一個(gè)頁(yè)可以存儲(chǔ)4個(gè)字節(jié),一個(gè)可存儲(chǔ)64個(gè)字節(jié)(512位)。頁(yè)碼從0開(kāi)始(0至15)。前4頁(yè)(0至3)存儲(chǔ)了NFC標(biāo)簽相關(guān)的信息(如NFC標(biāo)簽的序列號(hào)、控制位等)。從第5頁(yè)開(kāi)始存儲(chǔ)實(shí)際的數(shù)據(jù)(4至15頁(yè))。
使用MifareUltralight.get方法獲取MifareUltralight對(duì)象,然后調(diào)用MifareUltralight.connect方法進(jìn)行連接,并使用MifareUltralight.writePage方法每次寫(xiě)入1頁(yè)(4個(gè)字節(jié))。也可以使用MifareUltralight.readPages方法每次連續(xù)讀取4頁(yè)。如果讀取的頁(yè)的序號(hào)超過(guò)15,則從頭開(kāi)始讀。例如,從第15頁(yè)(序號(hào)為14)開(kāi)始讀。readPages方法會(huì)讀取14、15、0、1頁(yè)的數(shù)據(jù)。
2.讀MifareUltralight格式數(shù)據(jù)
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數(shù)據(jù)格式", 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; } }
3.寫(xiě)MifareUltralight格式數(shù)據(jù)
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數(shù)據(jù)格式", Toast.LENGTH_SHORT).show(); return; } writeTag(tag); } public void writeTag(Tag tag) { MifareUltralight ultralight = MifareUltralight.get(tag); try { ultralight.connect(); //寫(xiě)入八個(gè)漢字,從第五頁(yè)開(kāi)始寫(xiě),中文需要轉(zhuǎn)換成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, "寫(xiě)入成功", Toast.LENGTH_SHORT).show(); } catch (Exception e) { } finally { try { ultralight.close(); } catch (Exception e) { } } } }
我們將手機(jī)貼近NFC標(biāo)簽,寫(xiě)入成功后驗(yàn)證一下是否成功寫(xiě)入:
我們看到,彈出了“北京上海廣州天津”,說(shuō)明數(shù)據(jù)已經(jīng)寫(xiě)入成功了,說(shuō)明到此我們已經(jīng)成功的讀寫(xiě)NFC非NDEF格式的數(shù)據(jù)了。
源碼下載:demo
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)彈出列表、單選、多選框
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)彈出列表、單選、多選框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10Flutter實(shí)現(xiàn)簡(jiǎn)單的內(nèi)容高亮效果
內(nèi)容高亮并不陌生,特別是在搜索內(nèi)容頁(yè)面,可以說(shuō)四處可見(jiàn),這篇文章主要為大家介紹了如何使用Flutter實(shí)現(xiàn)簡(jiǎn)單的內(nèi)容高亮效果,需要的可以參考下2023-08-08Android開(kāi)發(fā)實(shí)現(xiàn)自定義Toast、LayoutInflater使用其他布局示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)自定義Toast、LayoutInflater使用其他布局,涉及Android自定義Toast與界面布局相關(guān)操作技巧,需要的朋友可以參考下2019-03-03Android中通過(guò)樣式來(lái)去除app的頭及界面全屏(備忘)的實(shí)現(xiàn)方法
這篇文章主要介紹了Android中通過(guò)樣式來(lái)去除app的頭及界面全屏(備忘)的相關(guān)資料,需要的朋友可以參考下2016-12-12Android實(shí)現(xiàn)簡(jiǎn)易的計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)易的計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10