關於NFC P2P模式

P2P模式是NFC的三種工作模式之一,主要完成在兩個NFC設備之間數據的傳遞,傳輸的一方同時也可以接收數據。P2P模式是在Android2.3.3+(API 10)中開始加入的,之後在Android4.0+(API 14)後又重新提供了一套新的API函數。
如上所述,在API 10以上的系統開發p2p功能時,Android系統提供兩套API函數。在API 10到API 13之間提供的是enableForegroundNdefPush方法,在API 14之後提供的則是setNdefPushMessageCallback方法和setNdefPushMessage方法,本文中主要就是對這兩種新的方法的介紹。

一. Android Beam功能
在Android API 14+後,將兩個NFC設備之間通過p2p模式傳輸數據稱爲Beam,Android Beam功能允許設備把一個NDEF消息推送到物理上相互監聽的另一個設備上。這種交互提供了比其他無線技術(如藍牙)更容易的發送數據的方法。因爲 NFC不需要手動的設備發現或配對要求。兩個設備在接近到一定範圍時會自動的連接。Android Beam通過一組NFC API來使用,以便應用程序能夠在設備之間來傳輸信息。例如,通信錄、瀏覽器以及YouTube等應用程序都使用Android Beam來跟其他設備共享通信錄、網頁和視頻。
在兩個NFC設備之間實現Beam功能必須滿足以下條件:
1) 兩臺設備的Android系統設置裏的Android beam必須打開;
2) 想要發送數據的應用程序必須是在前臺;
3) 接收數據的設備必須不鎖屏;
4) 當發射設備跟接收設備的距離足夠近的時候,發射設備會顯示”Touch to Beam(觸摸發射)”的UI。然後,用戶能夠選擇是否把消息發射給接收設備。
利用Android Beam功能,你可以傳輸NDEF消息給另一臺設備。爲了傳輸NDEF消息,必須要生成一個NDEF消息。而且如果發射設備上當前打開的應用程序沒有實現Android Beam功能, 系統會自動發送一個默認的包含你這個app的URI的NDEFmessage給接收端。如果目標設備的API level在9到13之間,這臺設備會提醒用戶該app未打開。如果API level在14之上,設備就會自動跳轉到Google Play中這個應用的下載鏈接頁供用戶下載。基於這個功能,用戶同樣可以和其他用戶分享app而不需要在谷歌商店中搜索,這個功能適用於API level 14 或更高。
通過調用下列兩個方法中的任意一個,就能夠爲你的應用程序啓用Android Beam:
1) setNdefPushMessage():這個方法把接收到的NdefMessage對象作爲一個消息 設置給Beam。當兩個設備足夠近的時候,就會自動的發送消息。
2) setNdefPushMessageCallback():接收包含createNdefMessage()方法的回調, 當設備在發射數據的範圍內時,這個回調方法會被調用。回調會讓你只在需要的時候創建NDEF消息。
一個Activity一次只能推送一條NDEF消息,因此如果同時使用了這兩種方法,那麼setNdefPushMessageCallback()方法的優先級要高於setNdefPushMessage()方法。 要使用Android Beam,通常必須滿足以下條件:
1) 發射數據的Activity必須是在前臺。兩個設備的屏幕都必須沒有被鎖定;
2) 必須要把發射的數據封裝到一個NdefMessage對象中;
3) 接收發射數據的NFC設備必須支持Android NDEF推送協議或是NFC論壇的SNEP協議(簡單的NDEF交換協議)。在API Level9(Android2.3)到API Level 13(Android3.2)的設備上需要NDEF推送協議。在API Level 14(Android4.0)和以後的設備上NDEF推送協議和SNEP都需要。NDEF Push Protocol 是Android獨有的協議,而SNEP是NFC論壇的標準,所以其他操作系統也支持。如果android設備有NFC功能,則兩個協議支持。
注意:如果在前臺的Activity 啓用了Android Beam,那麼標準的Intent調度系統就會被禁用,這時Android是不能掃描標籤的。但是,如果該Activity還啓用了前臺調度,那麼在前臺調度系統中,它依然能夠掃描到跟Intent過濾器匹配的NFC標籤。

二、 發送數據-setNdefPushMessageCallback()方法
1. 啓動Beam,發送NDEF message的步驟:
1) 在你的activity類對象中實現CreateNdefMessageCallback接口;
2) 調用setNdefPushMessageCallback(NfcAdapter.CreateNdefMessageCallback callback, Activity activity, Activity … activities)方法,當這個方法被調用,activity接收到一個回調而且如果一個要傳輸數據的設備被發現,方法creatNdefMessage(NfcEvent)會被自動調用。

3) 在creatNdefMessage方法中,創建一個NDEF消息並返回這個message。這個NDEF message會被傳輸到目標設備上。
setNdefPushMessageCallback()方法動態的生成NDEF消息。你可以在activity中的任意地方調用這個方法。當然最好是在activity中的onCreate方法中調用它。
當目標設備被發現後,creatNdefMessage方法會被調用,它會返回一個NDEF消息。而這個NDEF消息會被傳輸給目標設備。在這期間,Touch to Beam的UI會顯示在你的activity之上,所以在這個方法中你不能得到任意用戶的輸入。因此只能生成你想要傳輸的NDEF消息,然後返回這個消息。

三、 發送數據-setNdefPushMessage()方法
1. 啓動Beam,發送NDEF message的步驟:
1) 創建一個NDEF消息;
2) 調用setNdefPushMessage (NdefMessage message, Activity activity, Activity … activities)方法。當這個方法被調用時,activity會將接收到的NdefMessage參數作爲一個NDEF消息發送出去。
其中,第2步中,setNdefPushMessage()中的NDEF消息的生成是靜態的,即由用戶選擇生成然後作爲參數進行傳遞。

四、 接收數據
兩個NFC設備之間通過Beam實現數據傳遞時,數據接受端即接受Beam消息端。接受Beam消息的實現步驟如下:
1.在你的activity中實現onNewIntent(Intent)方法,該方法會調用setIntent(Intent),由Android生命週期描述可知,在調用onNewIntent方法後,onResume()方法會自動調用。
2.在activity的onResume()方法中,檢測當前消息是否來自Beam,如果是,獲取並處理該NDEF消息。
3.調用自己定義的消息解析函數,將獲取的NDEF消息解析並獲取Payload,再對Payload進行進一步UI操作。

代碼如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.nfcdemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="21" />

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

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data
                    android:host="*"
                    android:pathPrefix=""
                    android:scheme="http" />
            </intent-filter>
        </activity>
    </application>

</manifest>

1) 實現NFC的beam功能需要在AndroidManifest.xml中添加NFC權限。

2) 中的 應用程序支持的最低的skd版本應該是14,因爲本文的兩種方法都是在API 14之後新提供的。
3) 在中添加應用程序要處理的intent類型

import java.nio.charset.Charset;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * NFC test ClassName: MainActivity
 * @Description: TODO
 * @author fzq
 * @date 2017-11-25
 */
public class MainActivity extends Activity implements CreateNdefMessageCallback {

    private TextView messagetv;
    private NfcAdapter nfcAdapter;
    private String payload = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        messagetv = (TextView) findViewById(R.id.message_tv);
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (nfcAdapter == null) {
            messagetv.setText("nfc error");
            return;
        }
        messagetv.setText("touch another device to beam nfc");
        nfcAdapter.setNdefPushMessageCallback(this, this, this);
    }

    @Override
    public void onPointerCaptureChanged(boolean hasCapture) {
        Log.e("fzq", "--hasCapture  :  "+hasCapture);
    }

    /**
     * 發送nfc message
     */
    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        byte[] uriFiled = "fengzhiqi say hello".getBytes(Charset
                .forName("US-ASCII"));
        byte[] payload = new byte[uriFiled.length + 1];
        payload[0] = 0x01;
        System.arraycopy(uriFiled, 0, payload, 1, uriFiled.length);
        NdefRecord URIRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
                NdefRecord.RTD_URI, new byte[0], payload);
        NdefMessage message = new NdefMessage(new NdefRecord[] { URIRecord });
        return message;
    }

    /**
     * 接受nfc
     */
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
            ProcessIntenet(getIntent());
        }
    }

    /**
     * 處理nfc消息
     * 
     * @Description: TODO
     * @param @param intent
     * @return void
     * @throws
     * @author fzq
     * @date 2017-11-25
     */
    private void ProcessIntenet(Intent intent) {
        NdefMessage[] messages = getNdefMessage(getIntent());
        for (int i = 0; i < messages.length; i++) {
            for (int j = 0; j < messages[i].getRecords().length; j++) {
                NdefRecord record = messages[i].getRecords()[j];
                payload = new String(record.getPayload(), 1,
                        record.getPayload().length - 1,
                        Charset.forName("UTF-8"));
                messagetv.setText(payload);

            }
        }
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
        Button button = new Button(this);
        this.addContentView(button, params);
        messagetv.setText("");
        button.setText("OPEN LINK: " + payload);
        //點擊跳轉網頁
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_VIEW);
                intent.setData(Uri.parse("http://www." + payload));
                try {
                    startActivity(intent);
                } catch (ActivityNotFoundException e) {
                }
            }
        });
    }

    /**
     * 接受數據方法
     * @Description: TODO
     * @param @param intent
     * @param @return   
     * @return NdefMessage[]  
     * @throws
     * @author fzq
     * @date 2017-11-25
     */
    private NdefMessage[] getNdefMessage(Intent intent) {
        NdefMessage[] msgs = null;
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
            Parcelable[] rawMsgs = intent
                    .getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
            if (rawMsgs != null) {
                msgs = new NdefMessage[rawMsgs.length];
                for (int i = 0; i < rawMsgs.length; i++) {
                    msgs[i] = (NdefMessage) rawMsgs[i];
                }
            } else {
                byte[] empty = new byte[] {};
                NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN,
                        empty, empty, empty);
                NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
                msgs = new NdefMessage[] { msg };

            }
        } else {
            Log.e("fzq", "no message data");
        }
        return msgs;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章