武磊进球/足球即时此分/迈克马龙/北爱尔兰时间 - 中国vs澳大利亚

最新消息:歡迎加入QQ群276412263一起交流! | 購買NFC標(biāo)簽前往官方淘寶店 | 投稿郵箱:nfchome@qq.com

安卓NFC標(biāo)簽讀取快速開發(fā)教程(附源代碼demo下載)

NFC技術(shù) 小天 104176瀏覽

Demo下載

1.NFC的工作模式

NFC支持如下3種工作模式:讀卡器模式(Reader/writer mode)、仿真卡模式(Card Emulation Mode)、點(diǎn)對點(diǎn)模式(P2P mode)。

下來分別看一下這三種模式:

(1)讀卡器模式

數(shù)據(jù)在NFC芯片中,可以簡單理解成“刷標(biāo)簽”。本質(zhì)上就是通過支持NFC的手機(jī)或其它電子設(shè)備從帶有NFC芯片的標(biāo)簽、貼紙、名片等媒介中讀寫信息。通常NFC標(biāo)簽是不需要外部供電的。當(dāng)支持NFC的外設(shè)向NFC讀寫數(shù)據(jù)時(shí),它會發(fā)送某種磁場,而這個(gè)磁場會自動的向NFC標(biāo)簽供電。

(2)仿真卡模式

數(shù)據(jù)在支持NFC的手機(jī)或其它電子設(shè)備中,可以簡單理解成“刷手機(jī)”。本質(zhì)上就是將支持NFC的手機(jī)或其它電子設(shè)備當(dāng)成借記卡、公交卡、門禁卡等IC卡使用。基本原理是將相應(yīng)IC卡中的信息憑證封裝成數(shù)據(jù)包存儲在支持NFC的外設(shè)中 。
在使用時(shí)還需要一個(gè)NFC射頻器(相當(dāng)于刷卡器)。將手機(jī)靠近NFC射頻器,手機(jī)就會接收到NFC射頻器發(fā)過來的信號,在通過一系列復(fù)雜的驗(yàn)證后,將IC卡的相應(yīng)信息傳入NFC射頻器,最后這些IC卡數(shù)據(jù)會傳入NFC射頻器連接的電腦,并進(jìn)行相應(yīng)的處理(如電子轉(zhuǎn)帳、開門等操作)。

(3)點(diǎn)對點(diǎn)模式

該模式與藍(lán)牙、紅外差不多,用于不同NFC設(shè)備之間進(jìn)行數(shù)據(jù)交換,不過這個(gè)模式已經(jīng)沒有有“刷”的感覺了。其有效距離一般不能超過4厘米,但傳輸建立速度要比紅外和藍(lán)牙技術(shù)快很多,傳輸速度比紅外塊得多,如過雙方都使用Android4.2,NFC會直接利用藍(lán)牙傳輸。這種技術(shù)被稱為Android Beam。所以使用Android Beam傳輸數(shù)據(jù)的兩部設(shè)備不再限于4厘米之內(nèi)。
點(diǎn)對點(diǎn)模式的典型應(yīng)用是兩部支持NFC的手機(jī)或平板電腦實(shí)現(xiàn)數(shù)據(jù)的點(diǎn)對點(diǎn)傳輸,例如,交換圖片或同步設(shè)備聯(lián)系人。因此,通過NFC,多個(gè)設(shè)備如數(shù)字相機(jī),計(jì)算機(jī),手機(jī)之間,都可以快速連接,并交換資料或者服務(wù)。

下面看一下NFC、藍(lán)牙和紅外之間的差異:

對比項(xiàng) NFC 藍(lán)牙 紅外
網(wǎng)絡(luò)類型 點(diǎn)對點(diǎn) 單點(diǎn)對多點(diǎn) 點(diǎn)對點(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í)除外
通信模式 主動-主動/被動 主動-主動 主動-主動
成本

2.Android對NFC的支持

不同的NFC標(biāo)簽之間差異很大,有的只支持簡單的讀寫操作,有時(shí)還會采用支持一次性寫入的芯片,將NFC標(biāo)簽設(shè)計(jì)成只讀的。當(dāng)然,也存在一些復(fù)雜的NFC標(biāo)簽,例如,有一些NFC標(biāo)簽可以通過硬件加密的方式限制對某一區(qū)域的訪問。還有一些標(biāo)簽自帶操作環(huán)境,允許NFC設(shè)備與這些標(biāo)簽進(jìn)行更復(fù)雜的交互。這些標(biāo)簽中的數(shù)據(jù)也會采用不同的格式。但Android SDK API主要支持NFC論壇標(biāo)準(zhǔn)(Forum Standard),這種標(biāo)準(zhǔn)被稱為NDEF(NFC Data Exchange Format,NFC數(shù)據(jù)交換格式)。

NDEF格式其實(shí)就類似于硬盤的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)簽寫入NDEF格式的數(shù)據(jù)。
3)通過Android Beam技術(shù)將NDEF數(shù)據(jù)發(fā)送到另一部NFC設(shè)備。

用于描述NDEF格式數(shù)據(jù)的兩個(gè)類:

1)NdefMessage:描述NDEF格式的信息,實(shí)際上我們寫入NFC標(biāo)簽的就是NdefMessage對象。
2)NdefRecord:描述NDEF信息的一個(gè)信息段,一個(gè)NdefMessage可能包含一個(gè)或者多個(gè)NdefRecord。

NdefMessage和NdefRecord是Android NFC技術(shù)的核心類,無論讀寫NDEF格式的NFC標(biāo)簽,還是通過Android Beam技術(shù)傳遞Ndef格式的數(shù)據(jù),都需要這兩個(gè)類。

(2)非NDEF數(shù)據(jù)的操作

對于某些特殊需求,可能要存任意的數(shù)據(jù),對于這些數(shù)據(jù),我們就需要自定義格式。這些數(shù)據(jù)格式實(shí)際上就是普通的字節(jié)流,至于字節(jié)流中的數(shù)據(jù)代表什么,就由開發(fā)人員自己定義了。

(3)編寫NFC程序的基本步驟

1)設(shè)置權(quán)限,限制Android版本、安裝的設(shè)備:

1
2
3
4
<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屬性:

1
2
3
<activity
    android:name=".TagTextActivity"
    android:launchMode="singleTop"/>

而Activity中,我們也抽取了一個(gè)通用的BaseNfcActivity,如下(后面的Activity實(shí)現(xiàn)都繼承于BaseNfcActivity):

Java
1
2
3
4
5
6
7
8
9
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
/**
* 1.子類需要在onCreate方法中做Activity初始化。
* 2.子類需要在onNewIntent方法中進(jìn)行NFC標(biāo)簽相關(guān)操作。
*   當(dāng)launchMode設(shè)置為singleTop時(shí),第一次運(yùn)行調(diào)用onCreate方法,
*   第二次運(yùn)行將不會創(chuàng)建新的Activity實(shí)例,將調(diào)用onNewIntent方法
*   所以我們獲取intent傳遞過來的Tag數(shù)據(jù)操作放在onNewIntent方法中執(zhí)行
*   如果在棧中已經(jīng)有該Activity的實(shí)例,就重用該實(shí)例(會調(diào)用實(shí)例的onNewIntent())
*   只要NFC標(biāo)簽靠近就執(zhí)行
*/
public class BaseNfcActivity extends AppCompatActivity {
    private NfcAdapter mNfcAdapter;
    private PendingIntent mPendingIntent;
    /**
     * 啟動Activity,界面可見時(shí)
     */
    @Override
    protected void onStart() {
        super.onStart();
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        //一旦截獲NFC消息,就會通過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);
    }
}

注意:通常來說,所有處理NFC的Activity都要設(shè)置launchMode屬性為singleTop或者singleTask,保證了無論NFC標(biāo)簽靠近手機(jī)多少次,Activity實(shí)例只有一個(gè)。

接下來看幾個(gè)具體的NFC標(biāo)簽應(yīng)用實(shí)例,通過情景學(xué)習(xí)快速掌握NFC技術(shù):

3.兩個(gè)NFC標(biāo)簽的簡單實(shí)例

1.利用NFC標(biāo)簽讓Android自動運(yùn)行程序

場景是這樣的:現(xiàn)將應(yīng)用程序的包寫到NFC程序上,然后我們將NFC標(biāo)簽靠近Android手機(jī),手機(jī)就會自動運(yùn)行包所對應(yīng)的程序,這個(gè)是NFC比較基本的一個(gè)應(yīng)用。下面以貼近標(biāo)簽自動運(yùn)行Android自帶的“短信”為例。

向NFC標(biāo)簽寫入數(shù)據(jù)一般分為三步:

1)獲取Tag對象

1
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

2)判斷NFC標(biāo)簽的數(shù)據(jù)類型(通過Ndef.get方法)

1
Ndef ndef = Ndef.get(tag);

3)寫入數(shù)據(jù)

1
ndef.writeNdefMessage(ndefMessage);

詳細(xì)實(shí)現(xiàn)代碼如下:

1
2
3
4
5
6
7
8
9
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
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);
    }
    /**
     * 往標(biāo)簽寫數(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ù)類型(通過Ndef.get方法)
            Ndef ndef = Ndef.get(tag);
            //判斷是否為NDEF標(biāo)簽
            if (ndef != null) {
                ndef.connect();
                //判斷是否支持可寫
                if (!ndef.isWritable()) {
                    return;
                }
                //判斷標(biāo)簽的容量是否夠用
                if (ndef.getMaxSize() < size) {
                    return;
                }
                //3.寫入數(shù)據(jù)
                ndef.writeNdefMessage(ndefMessage);
                Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show();
            } else { //當(dāng)我們買回來的NFC標(biāo)簽是沒有格式化的,或者沒有分區(qū)的執(zhí)行此步
                //Ndef格式類
                NdefFormatable format = NdefFormatable.get(tag);
                //判斷是否獲得了NdefFormatable對象,有一些標(biāo)簽是只讀的或者不允許格式化的
                if (format != null) {
                    //連接
                    format.connect();
                    //格式化并將信息寫入標(biāo)簽
                    format.format(ndefMessage);
                    Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(this, "寫入失敗", Toast.LENGTH_SHORT).show();
                }
            }
        } catch (Exception e) {
        }
    }
}

注意:設(shè)置 RunAppActivity 的 launchMode 屬性為 singleTop。

現(xiàn)在看一下效果圖:

android-nfc-dev1

將NFC標(biāo)簽貼近手機(jī)背面,自動寫入數(shù)據(jù),此時(shí)退出所有程序,返回桌面,然后再將NFC標(biāo)簽貼近手機(jī)背面,將會看到自動打開了“短信”。

android-nfc-dev2
下來再看一個(gè)有趣的例子:

2.利用NFC標(biāo)簽讓Android自動打開網(wǎng)頁

如何讓NFC標(biāo)簽貼近手機(jī),手機(jī)可以自動打開一個(gè)網(wǎng)頁呢?

首先我們創(chuàng)建一個(gè)NdefRecord,Android已經(jīng)為我們提供好了這樣的方法:

1
2
3
4
//直接接受一個(gè)Uri
public NdefRecord createUri(String uriString); 
//接受一個(gè)Uri的對象
public NdefRecord createUri(Uri uri); 

實(shí)現(xiàn)代碼對比“3.利用NFC標(biāo)簽讓Android自動運(yùn)行程序”部分只是修改了writeNFCTag方法中

1
2
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord
        .createApplicationRecord(mPackageName)});

1
2
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord
        .createUri(Uri.parse("http://www.huyi8.cn"))});

其余不變。

android-nfc-dev3

上面這個(gè)功能還是比較有用的,例如我們往某些商品上貼上NFC標(biāo)簽,里面寫入該商品的詳細(xì)介紹網(wǎng)頁Uri,當(dāng)用戶貼近商品時(shí),就會自動打開該商品的詳情介紹。

通過上面這兩個(gè)案例的學(xué)習(xí)相信很多人已經(jīng)對NFC感起了興趣,那么下來滲透式的分析一下NDEF文本格式,看看NDEF到底是個(gè)什么東西。

4.NDEF文本格式深度解析

獲取NFC標(biāo)簽中的數(shù)據(jù)要通過 NdefRecord.getPayload 方法完成。當(dāng)然,在處理這些數(shù)據(jù)之前,最好判斷一下NdefRecord對象中存儲的是不是NDEF文本格式數(shù)據(jù)。

(1)判斷數(shù)據(jù)是否為NDEF格式

1)TNF(類型名格式,Type Name Format)必須是NdefRecord.TNF_WELL_KNOWN。
2)可變的長度類型必須是NdefRecord.RTD_TEXT。

如果這兩個(gè)標(biāo)準(zhǔn)同時(shí)滿足,那么就為NDEF格式。

(2)NDEF文本格式規(guī)范

不管什么格式的數(shù)據(jù)本質(zhì)上都是由一些字節(jié)組成的。對于NDEF文本格式來說,這些數(shù)據(jù)的第1個(gè)字節(jié)描述了數(shù)據(jù)的狀態(tài),然后若干個(gè)字節(jié)描述文本的語言編碼,最后剩余字節(jié)表示文本數(shù)據(jù)。這些數(shù)據(jù)格式由NFC Forum的相關(guān)規(guī)范定義,可以通過 http://members.nfc-forum.org/specs/spec_dashboard 下載相關(guān)的規(guī)范。

下面這兩張表是規(guī)范中 3.2節(jié) 相對重要的翻譯部分:

NDEF文本數(shù)據(jù)格式:

偏移量 長度(bytes) 描述
0 1 狀態(tài)字節(jié),見下表(狀態(tài)字節(jié)編碼格式)
1 n ISO/IANA語言編碼。例如”en-US”,”fr-CA”。編碼格式是US-ASCII,長度(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 語言編碼的長度(占用的字節(jié)個(gè)數(shù))

下面我們動手實(shí)現(xiàn)NFC標(biāo)簽中的文本數(shù)據(jù)的讀寫操作:

1.讀NFC標(biāo)簽文本數(shù)據(jù)

1
2
3
4
5
6
7
8
9
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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的實(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é)開始,后面的文本數(shù)據(jù)
     * @param ndefRecord
     * @return
     */
    public static String parseTextRecord(NdefRecord ndefRecord) {
        /**
         * 判斷數(shù)據(jù)是否為NDEF格式
         */
        //判斷TNF
        if (ndefRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) {
            return null;
        }
        //判斷可變的長度的類型
        if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
            return null;
        }
        try {
            //獲得字節(jié)數(shù)組,然后進(jìn)行分析
            byte[] payload = ndefRecord.getPayload();
            //下面開始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)算后就會保留最高位
            String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8" : "UTF-16";
            //3f最高兩位是0,第六位是1,所以進(jìn)行"位與"運(yùn)算后獲得第六位
            int languageCodeLength = payload[0] & 0x3f;
            //下面開始NDEF文本數(shù)據(jù)第二個(gè)字節(jié),語言編碼
            //獲得語言編碼
            String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
            //下面開始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屬性(后面一樣要注意):

1
2
3
<activity
    android:name=".ReadTextActivity"
    android:launchMode="singleTop"/>

2.寫NFC標(biāo)簽文本數(shù)據(jù)

1
2
3
4
5
6
7
8
9
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
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();
        }
    }
    /**
     * 創(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è)置語言編碼,使用數(shù)組拷貝方法,從0開始拷貝到data中,拷貝到data的1到langBytes.length的位置
        System.arraycopy(langBytes, 0, data, 1, langBytes.length);
        //設(shè)置文本字節(jié),使用數(shù)組拷貝方法,從0開始拷貝到data中,拷貝到data的1 + langBytes.length
        //到textBytes.length的位置
        System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
        //通過字節(jié)傳入NdefRecord對象
        //NdefRecord.RTD_TEXT:傳入類型 讀寫
        NdefRecord ndefRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
                NdefRecord.RTD_TEXT, new byte[0], data);
        return ndefRecord;
    }
    /**
     * 寫數(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)寫入成功會彈出“寫入成功”的吐司。下面我們再驗(yàn)證一下是否成功寫入:

android-nfc-dev5

我們看到,數(shù)據(jù)已經(jīng)寫入成功了,說明到此我們已經(jīng)成功的讀寫NFC標(biāo)簽中的文本數(shù)據(jù)了。

5.NDEF Uri格式深度解析

與NDEF文本格式一樣,存儲在NFC標(biāo)簽中的Uri也有一定的格式,http://members.nfc-forum.org/specs/spec_dashboard

(1)Uri的格式規(guī)范要比文本格式簡單一些:

Name 偏移 大小 描述
識別碼 0 1byte Uri識別碼 用于存儲已知Uri的前綴
Uri字段 1 N UTF-8類型字符串 用于存儲剩余字符串

(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)制來存儲于識別碼位置(占1byte)。

是不是相對簡單了些,那么下來我們來解析一個(gè)Uri。

(3)預(yù)先定義已知Uri前綴

這里我們定義一個(gè)UriPrefix類,以便方便的獲取Uri前綴:

1
2
3
4
5
6
7
8
9
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
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:");
    }
}

然后我們來看一下清單文件中Activity的相關(guān)配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<activity
    android:name=".ReadWriteUriActivity"
    android:label="讀寫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)簽中存儲有以下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>

好了,接下來就可以進(jìn)行讀寫NFC標(biāo)簽中的Uri數(shù)據(jù)了:

1.讀NFC標(biāo)簽中的Uri數(shù)據(jù)

1
2
3
4
5
6
7
8
9
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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的實(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);
    }
    /**
     * 處理絕對的Uri
     * 沒有Uri識別碼,也就是沒有Uri前綴,存儲的全部是字符串
     * @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;
    }
    /**
     * 處理已知類型的Uri
     * @param ndefRecord
     * @return
     */
    private static Uri parseWellKnown(NdefRecord ndefRecord) {
        //判斷數(shù)據(jù)是否是Uri類型的
        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.寫NFC標(biāo)簽中的Uri數(shù)據(jù)

1
2
3
4
5
6
7
8
9
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
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轉(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;
    }
    /**
     * 寫入標(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)簽,寫入成功后驗(yàn)證一下是否成功寫入:

android-nfc-dev6

我們看到,數(shù)據(jù)已經(jīng)寫入成功了,說明到此我們已經(jīng)成功的讀寫NFC標(biāo)簽中的Uri數(shù)據(jù)了。

到這里,NDEF格式就大致說完了,那么接下來看一下非NDEF格式的數(shù)據(jù)。

6.非NDEF格式深度解析

1.MifareUltralight數(shù)據(jù)格式

將NFC標(biāo)簽的存儲區(qū)域分為16個(gè)頁,每一個(gè)頁可以存儲4個(gè)字節(jié),一個(gè)可存儲64個(gè)字節(jié)(512位)。頁碼從0開始(0至15)。前4頁(0至3)存儲了NFC標(biāo)簽相關(guān)的信息(如NFC標(biāo)簽的序列號、控制位等)。從第5頁開始存儲實(shí)際的數(shù)據(jù)(4至15頁)。

使用MifareUltralight.get方法獲取MifareUltralight對象,然后調(diào)用MifareUltralight.connect方法進(jìn)行連接,并使用MifareUltralight.writePage方法每次寫入1頁(4個(gè)字節(jié))。也可以使用MifareUltralight.readPages方法每次連續(xù)讀取4頁。如果讀取的頁的序號超過15,則從頭開始讀。例如,從第15頁(序號為14)開始讀。readPages方法會讀取14、15、0、1頁的數(shù)據(jù)。

2.讀MifareUltralight格式數(shù)據(jù)

1
2
3
4
5
6
7
8
9
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
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.寫MifareUltralight格式數(shù)據(jù)

1
2
3
4
5
6
7
8
9
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
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();
            //寫入八個(gè)漢字,從第五頁開始寫,中文需要轉(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, "寫入成功", Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
        } finally {
            try {
                ultralight.close();
            } catch (Exception e) {
            }
        }
    }
}

我們將手機(jī)貼近NFC標(biāo)簽,寫入成功后驗(yàn)證一下是否成功寫入:

android-nfc-dev8

我們看到,彈出了“北京上海廣州天津”,說明數(shù)據(jù)已經(jīng)寫入成功了,說明到此我們已經(jīng)成功的讀寫NFC非NDEF格式的數(shù)據(jù)了。

NFC標(biāo)簽開發(fā)深度解析到此就結(jié)束了!

 

作者:郭朝?轉(zhuǎn)載請注明出處:http://blog.csdn.net/smartbetter/article/details/53173217

nfchome-contact-us

轉(zhuǎn)載請注明:NFC之家 » 安卓NFC標(biāo)簽讀取快速開發(fā)教程(附源代碼demo下載)

項(xiàng)目咨詢服務(wù)