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;
}
}