Android NFC 近場(chǎng)通訊開(kāi)發(fā)技術(shù)全解

前言:


由于開(kāi)發(fā)的業(yè)務(wù)需求,涉及到了Android NFC開(kāi)發(fā),所以開(kāi)始了NFC開(kāi)發(fā)的研究探討。寫(xiě)下這博客以分享經(jīng)驗(yàn),“share your knowledge to the world!”.

NFC設(shè)備的普及率很低,并且Android 3.0 開(kāi)始系統(tǒng)才支持,而且現(xiàn)在NFC功能的設(shè)備并不多,這樣的應(yīng)用也并不多。
設(shè)備:需要一部Andriod帶NFC的設(shè)備,至少一張標(biāo)簽紙 。

NFC:近場(chǎng)通信Near Field Communication
首先,了解NFC標(biāo)簽紙的類型有分A類,B類,F(xiàn)類
所以你需要知道你的應(yīng)用支持什么類型的標(biāo)簽,設(shè)置你需要過(guò)濾的標(biāo)簽。

我們創(chuàng)建一個(gè)NFCActivity.java ,也就是處理我們的邏輯類。

在android Minifest.xml 文件中,你需要聲明NFCActivity,使用singleTask

    <activity
    android:name=".NFCActivity"
    android:launchMode="singleTask"
    android:screenOrientation="portrait" >

重點(diǎn)1:聲明Action,順序相關(guān)

如果是有內(nèi)容的標(biāo)簽,則優(yōu)先獲得android.nfc.action.NDEF_DISCOVERED 這個(gè)Action動(dòng)作

   <!-- NDEF_DISCOVERED 優(yōu)先級(jí)最高的Action -->
    <intent-filter>
      <action android:name="android.nfc.action.NDEF_DISCOVERED" />
      <category android:name="android.intent.category.DEFAULT" />
      <data android:mimeType="text/plain" />
    </intent-filter>

如果未獲得上面的Action,就會(huì)獲取android.nfc.action.TECH_DISCOVERED 這個(gè)Action動(dòng)作

    <!-- TECH_DISCOVERED 優(yōu)先級(jí)第二的Action -->
    <intent-filter>
      <action android:name="android.nfc.action.TECH_DISCOVERED" />
      <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>

如果上面兩個(gè)都沒(méi)有,就是這個(gè)優(yōu)先級(jí)最低的,也是沒(méi)有任何Action的情況下最終會(huì)獲取的android.nfc.action.TAG_DISCOVERED 這個(gè)Action 動(dòng)作。

<!-- TAG_DISCOVERED 優(yōu)先級(jí)最低的最終處理 Action -->
<intent-filter>
  <action android:name="android.nfc.action.TAG_DISCOVERED" />
  <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

最后需要聲明你需要過(guò)濾的標(biāo)簽類型

    <!-- 技術(shù)標(biāo)簽過(guò)濾的便簽卡類型 android:resource="@xml/nfc_tech_filter" -->
    
     <meta-data
       android:name="android.nfc.action.TECH_DISCOVERED"
       android:resource="@xml/nfc_tech_filter" />

@xml/nfc_tech_filter,在res下面建立一個(gè)XML文件夾,然后在文件夾下面建立
Nfc_tech_filter.xml文件,名字隨便取,但是最好的見(jiàn)名知意的名稱。

重點(diǎn)2:Nfc_tech_filter.xml 文件過(guò)濾

    <tech-list>
    <tech>android.nfc.tech.Ndef</tech>
    </tech-list>

這個(gè)是一個(gè)組,組中的Ndef是代表需要捕獲F類型標(biāo)簽,即F類型的才是符合條件的
如果要同時(shí)支持F類型和A類型的標(biāo)簽,則是與關(guān)系,可以寫(xiě)成:

    <tech-list>
             <tech>android.nfc.tech.NdeA</tech>
             <tech>android.nfc.tech.Ndef</tech>
    </tech-list>

如果我需要支持A類型,或者是F類型,則是:

    <tech-list>
            <tech>android.nfc.tech.NdeA</tech>
    </tech-list>
    <tech-list>
            <tech>android.nfc.tech.Ndef</tech>
    </tech-list>

或者關(guān)系。

然后是聲明權(quán)限:

     <uses-permission android:name="android.permission.NFC" />
     <uses-permission android:name="android.permission.GET_TASKS" />

重點(diǎn)3:Intent發(fā)布系統(tǒng)

在minifasts.xml中聲明的配置,都會(huì)在系統(tǒng)內(nèi)部注冊(cè),然后系統(tǒng)匹配哪些應(yīng)用符合Action,然后檢測(cè)到這樣的Action時(shí)候就會(huì)在桌面彈出讓用戶選擇的應(yīng)用列表,這就是系統(tǒng)的Intent發(fā)布系統(tǒng),幸運(yùn)的是,我們可以在代碼中截獲系統(tǒng)的Intent發(fā)布系統(tǒng),這樣我們就可以指定特定的Action,跳轉(zhuǎn)之我們指定的應(yīng)用的頁(yè)面,這樣就不需要系統(tǒng)彈出應(yīng)用選擇的列表了,即我們?cè)诖a中接管了系統(tǒng)的Intent發(fā)布系統(tǒng)。

獲取PendingIntent

    pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_CANCEL_CURRENT);

獲取intentFilter,
可以添加你想要的Action和mimeType:

    IntentFilter ndef = new IntentFilter();
    ndef.addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
    ndef.addAction(NfcAdapter.ACTION_TECH_DISCOVERED);
    try { //
    ndef.addDataType("text/plain");
    } catch (IntentFilter.MalformedMimeTypeException e) {
    throw new RuntimeException("fail", e);
    }

添加卡類型:

intentFiltersArray = new IntentFilter[]{ndef};
       techListsArray = new String[][]{new String[]{
               Ndef.class.getName(), NdefFormatable.class.getName(),
               NfcA.class.getName(), MifareClassic.class.getName(),
               MifareUltralight.class.getName(), NfcB.class.getName(),
               IsoDep.class.getName(), NfcF.class.getName(), NfcV.class.getName(),
               NfcBarcode.class.getName()}};

在onNewIntent方法中

     @Override
    protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    setIntent(intent);
       }

重點(diǎn)4,獲取Intent前臺(tái)發(fā)布系統(tǒng)

/**
        * 獲取Intent TAG前臺(tái)分發(fā)系統(tǒng)權(quán)限
        */
       private void getForgroundDispatch() {
           if (nfcAdapter != null) {
               nfcAdapter.enableForegroundDispatch(NFCActivity.this, pendingIntent, null, null);//// 獲取前臺(tái)發(fā)布系統(tǒng)
               hasforgrounddispatch = true;
           }
       }
      

nfcAdapter.enableForegroundDispatch(NFCActivity.this, pendingIntent, null, null);

由于我的Demo業(yè)務(wù)需求,首次通過(guò)系統(tǒng)Intent發(fā)布系統(tǒng)在應(yīng)用列表中選擇了我的應(yīng)用,然后應(yīng)用獲取Intent發(fā)布系統(tǒng)。

由于獲取Intent發(fā)布系統(tǒng)第三個(gè)參數(shù)為null,即不指定Action,這個(gè)時(shí)候Action將會(huì)是NfcAdapter.ACTION_TAG_DISCOVERED,優(yōu)先級(jí)最低的。

詳細(xì)看系統(tǒng)API介紹:

public void enableForegroundDispatch (Activity activity, PendingIntent intent, IntentFilter[] filters, String[][] techLists)

  • Added in API level 10
  • Enable foreground dispatch to the given Activity.
  • This will give give priority to the foreground activity when dispatching a discovered Tag to an application.
  • If any IntentFilters are provided to this method they are used to match dispatch Intents for both the ACTION_NDEF_DISCOVERED and ACTION_TAG_DISCOVERED. Since ACTION_TECH_DISCOVERED relies on meta data outside of the IntentFilter matching for that dispatch Intent is handled by passing in the tech lists separately. Each first level entry in the tech list represents an array of technologies that must all be present to match. If any of the first level sets match then the dispatch is routed through the given PendingIntent. In other words, the second level is ANDed together and the first level entries are ORed together.
  • If you pass null for both the filters and techLists parameters that acts a wild card and will cause the foreground activity to receive all tags via the ACTION_TAG_DISCOVERED intent.
  • This method must be called from the main thread, and only when the activity is in the foreground (resumed). Also, activities must call disableForegroundDispatch(Activity) before the completion of their onPause() callback to disable foreground dispatch after it has been enabled.
  • Requires the NFC permission.

上述大概意思是:

  • 至少api10以上的系統(tǒng),即Android3.0
  • 為Activity獲取前臺(tái)發(fā)布系統(tǒng), 你需要給出一些Activity去匹配Tag的屬性值
  • 你給予的任何的IntentFilter必須是匹配ACTION_NDEF_DISCOVERED 和ACTION_TAG_DISCOVERED.的,ACTION_TECH_DISCOVERED 是依賴于meta-data的列表說(shuō)明,每一個(gè)tech list 代表一些技術(shù)的匹配,大概就是說(shuō)匹配Tag標(biāo)簽就是通過(guò)tech list的配置來(lái)的 。
  • 重要的是這句話:
    If you pass null for both the filters and techLists parameters that acts a wild card and will cause the foreground activity to receive all tags via the ACTION_TAG_DISCOVERED intent.
  • 即第三個(gè)參數(shù) IntentFilter[] filters 你設(shè)置為null的時(shí)候,標(biāo)簽就會(huì)獲取 優(yōu)先級(jí)最低的ACTION_TAG_DISCOVERED這個(gè)Action動(dòng)作

這個(gè)方法在主線程中調(diào)用,你必須要在onPause() 方法中取消捕獲的Intent分發(fā)系統(tǒng)

    nfcAdapter.disableForegroundDispatch(this);

核心是在OnResume中根據(jù)不同的Action去分發(fā)不同的邏輯,詳細(xì)看Demo簡(jiǎn)單的邏輯處理

     @Override
    protected void onResume() {
    .......
    ....
    
    }

重點(diǎn)5 :擦除標(biāo)簽內(nèi)容

這個(gè)除標(biāo)簽功能,在網(wǎng)上很難找到介紹方法,當(dāng)初我也想過(guò),寫(xiě)入空信息就擦除了內(nèi)容,然后自己試了試,沒(méi)有試對(duì),然后繼續(xù)找資料,在GitHub找,然后找到了javaNFC項(xiàng)目,找到了方法,Java項(xiàng)目API跟AndroidAPI大同小異,方法介紹(“format NFC tag is just write a empty Record”)
呵呵呵呵呵呵....

傻眼了,當(dāng)初自己思路是對(duì)的,原來(lái)是empty Record 構(gòu)造錯(cuò)了,心塞啊,又多花了一天時(shí)間找資料,(Google找資料才是王道,百度low了)
擦除數(shù)據(jù)的核心代碼:

    // TODO,格式化NFC標(biāo)簽,即寫(xiě)入空信息即可
    NdefRecord emptyrecord = new NdefRecord(NdefRecord.TNF_EMPTY, null, null, null);
    NdefRecord[] emptyArray = {emptyrecord};
    NdefMessage emptyMsg = new NdefMessage(emptyArray);
    startWriteMsg(getIntent(), emptyMsg);

下面是一些核心操作,沒(méi)有什么難點(diǎn)的:


    /**
     * 創(chuàng)建一個(gè)封裝要寫(xiě)入的文本的NdefRecord對(duì)象
     *
     * @param text
     * @return
     */
    public NdefRecord createTextRecord(String text) {
        // 生成語(yǔ)言編碼的字節(jié)數(shù)組,中文編碼
        byte[] langBytes = Locale.US.getLanguage().getBytes(Charset.forName("US-ASCII"));
        // 將要寫(xiě)入的文本以UTF_8格式進(jìn)行編碼
        Charset utfEncoding = Charset.forName("UTF-8");
        // 由于已經(jīng)確定文本的格式編碼為UTF_8,所以直接將payload的第1個(gè)字節(jié)的第7位設(shè)為0
        byte[] textBytes = text.getBytes(utfEncoding);
        int utfBit = 0;
        // 定義和初始化狀態(tài)字節(jié)
        char status = (char) (utfBit + langBytes.length);
        // 創(chuàng)建存儲(chǔ)payload的字節(jié)數(shù)組
        byte[] data = new byte[1 + langBytes.length + textBytes.length];
        // 設(shè)置狀態(tài)字節(jié)
        data[0] = (byte) status;
        // 設(shè)置語(yǔ)言編碼
        System.arraycopy(langBytes, 0, data, 1, langBytes.length);
        // 設(shè)置實(shí)際要寫(xiě)入的文本
        System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
        // 根據(jù)前面設(shè)置的payload創(chuàng)建NdefRecord對(duì)象
        NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
        return record;
    }

    /**
     * 開(kāi)始寫(xiě)入信息
     *
     * @param intent
     * @param ndefMessage
     */
    private void startWriteMsg(Intent intent, NdefMessage ndefMessage) {
        final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        try {
            Ndef ndef = Ndef.get(tag);
            if (ndef != null) {
                ndef.connect();
                if (!ndef.isWritable()) {
                    Tools.showTip(NFCActivity.this, "標(biāo)簽是只讀!");
                    NFCActivity.this.onBackPressed();
                    NFCActivity.this.finish();
                    return;
                }
                int size = ndefMessage.toByteArray().length;
                if (ndef.getMaxSize() < size) {
                    Tools.showTip(NFCActivity.this, "空間不足!");
                    NFCActivity.this.onBackPressed();
                    NFCActivity.this.finish();
                    return;
                }
                ndef.writeNdefMessage(ndefMessage);
                Tools.showTip(NFCActivity.this, "寫(xiě)入成功");// 寫(xiě)入成功
                // TODO 寫(xiě)入成功,操作UI
                return;
            } else {
                // 獲取可以格式化和向標(biāo)簽寫(xiě)入數(shù)據(jù)NdefFormatable對(duì)象
                NdefFormatable format = NdefFormatable.get(tag);
                // 向非NDEF格式或未格式化的標(biāo)簽寫(xiě)入NDEF格式數(shù)據(jù)
                if (format != null) {
                    try {
                        // 允許對(duì)標(biāo)簽進(jìn)行IO操作
                        format.connect();
                        format.format(ndefMessage);
                        Tools.showTip(NFCActivity.this, "寫(xiě)入成功");// 寫(xiě)入成功
                        // TODO 寫(xiě)入成功,操作UI
                        return;
                    } catch (Exception e) {
                        Tools.showTip(NFCActivity.this, "寫(xiě)入失敗");
                        // TODO 寫(xiě)入失敗 操作UI
                        return;
                    }
                } else {
                    Tools.showTip(NFCActivity.this, "NFC標(biāo)簽不支持NDEF格式");
                    // TODO NFC標(biāo)簽不支持NDEF格式! 操作UI
                    return;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

  • 到此,NFC功能完成,涉及到了空標(biāo)簽如何識(shí)別,如何在觸發(fā)標(biāo)簽,在應(yīng)用前臺(tái),非前臺(tái)如何觸發(fā)邏輯處理,如何修改標(biāo)簽內(nèi)容,如何擦除標(biāo)簽內(nèi)容,基本上所有的業(yè)務(wù)邏輯都囊括了,滿足了基本的開(kāi)發(fā)需求,如果你涉及到更加復(fù)雜的需求開(kāi)發(fā),這個(gè)Demo也能很好的幫助你理解NFC邏輯跳轉(zhuǎn),甚至可以直接搬過(guò)來(lái)使用,直接套上你的業(yè)務(wù)邏輯。當(dāng)然,由于能力有限,可能有很多紕漏,望大神指點(diǎn)。
  • 比如這個(gè)單是NFC的邏輯跳轉(zhuǎn)就看上去代碼比較混亂,再加上你的業(yè)務(wù)邏輯,可能就沒(méi)有那么清晰了,如果你有更好的方法,把NFC技術(shù)邏輯分離出去,然后再嵌入你的業(yè)務(wù)邏輯,可能就更加完美了,做到技術(shù)跟業(yè)務(wù)分離的。代碼的可維護(hù)性就更高了。
    最后附上Demo下載地址http://download.csdn.net/detail/u011661372/8951595
    Android 智能家居開(kāi)發(fā)群:468191212,歡迎從事智能開(kāi)發(fā)的朋友加入,當(dāng)然,一樣歡迎非智能家居的Android開(kāi)發(fā)者,智能家居,未來(lái)的一片天地。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容