接上部分的分析,當前系統已經進入到applyRouting()階段,後續應該是需要一直去監聽當前是否有NFC設備進入通訊範圍。如果有適合的NFC設備,則底層會先進行溝通,並將消息通知給上層。
進入NFC設備發現流程
下面從applyRouting()函數開始分析,可以參考系統註釋:
void applyRouting(boolean force) {
synchronized (this) {
//@paul: 如果NFC沒有打開或者已經關閉,則直接發揮
if (!isNfcEnabledOrShuttingDown()) {
return;
}
...
if (mInProvisionMode) {
mInProvisionMode = Settings.Secure.getInt(mContentResolver,
Settings.Global.DEVICE_PROVISIONED, 0) == 0;
if (!mInProvisionMode) {
//@paul: 原生的Android裏面Provision只做了一件事,就是寫入一個DEVICE_PROVISIONED標記。
//@paul: 不過這個標記作用很大,這個標記只會在系統全新升級(雙清)的時候寫入一次,代表了Android系統升級準備完成,可以正常工作。
mNfcDispatcher.disableProvisioningMode();
mHandoverManager.setEnabled(true);
}
}
//@paul: 如果有tag正在通訊時,delay一段時間再更新參數
// Special case: if we're transitioning to unlocked state while
// still talking to a tag, postpone re-configuration.
if (mScreenState == ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED && isTagPresent()) {
Log.d(TAG, "Not updating discovery parameters, tag connected.");
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESUME_POLLING),
APPLY_ROUTING_RETRY_TIMEOUT_MS);
return;
}
try {
watchDog.start();
//@paul: 依據前面初始化的參數來更新NfcDiscoveryParameters
// Compute new polling parameters
NfcDiscoveryParameters newParams = computeDiscoveryParameters(mScreenState);
if (force || !newParams.equals(mCurrentDiscoveryParameters)) {
//@paul: 判斷條件爲:mTechMask != 0 || mEnableHostRouting
//@paul:mTechMask一般不爲0,mEnableHostRouting一般爲false
if (newParams.shouldEnableDiscovery()) {
boolean shouldRestart = mCurrentDiscoveryParameters.shouldEnableDiscovery();
//@paul:系統一般會進入enableDiscovery()
mDeviceHost.enableDiscovery(newParams, shouldRestart);
} else {
mDeviceHost.disableDiscovery();
}
mCurrentDiscoveryParameters = newParams;
} else {
Log.d(TAG, "Discovery configuration equal, not updating.");
}
} finally {
watchDog.cancel();
}
}
}
由於系統調用到enableDiscovery()函數,此函數不論是nxp還是nci,都會調用到native函數doEnableDiscovery(),繼續追蹤此函數(後續都已NXP爲例),最終調用到com_android_nfc_NativeNfcManager.cpp中的:
{"doEnableDiscovery", "(IZZZ)V",
(void *)com_android_nfc_NfcManager_enableDiscovery},
繼續追蹤com_android_nfc_NfcManager_enableDiscovery():
static void com_android_nfc_NfcManager_enableDiscovery(JNIEnv *e, jobject o, jint modes,
jboolean, jboolean reader_mode, jboolean restart)
{
...
/* Register callback for remote device notifications.
* Must re-register every time we turn on discovery, since other operations
* (such as opening the Secure Element) can change the remote device
* notification callback*/
//@paul: 註冊偵聽到NFC設備時的回調函數,後續的流程從此開始
REENTRANCE_LOCK();
ret = phLibNfc_RemoteDev_NtfRegister(&nat->registry_info, nfc_jni_Discovery_notification_callback, (void *)nat);
REENTRANCE_UNLOCK();
...
//@paul: 啓動discovery流程
nfc_jni_start_discovery_locked(nat, restart);
clean_and_return:
CONCURRENCY_UNLOCK();
}
此處的兩個函數nfc_jni_start_discovery_locked()和nfc_jni_Discovery_notification_callback()都需要在深入一點,一個一個的看:
static void nfc_jni_start_discovery_locked(struct nfc_jni_native_data *nat, bool resume)
{
...
configure:
/* Start Polling loop */
TRACE("****** Start NFC Discovery ******");
REENTRANCE_LOCK();
//@paul: nfc_jni_discover_callback()是discover後消息notify的開始
ret = phLibNfc_Mgt_ConfigureDiscovery(resume ? NFC_DISCOVERY_RESUME : NFC_DISCOVERY_CONFIG,
nat->discovery_cfg, nfc_jni_discover_callback, (void *)&cb_data);
REENTRANCE_UNLOCK();
...
clean_and_return:
nfc_cb_data_deinit(&cb_data);
}
一旦上面進入了nfc_jni_discover_callback(),後續就會進入nfc_jni_Discovery_notification_callback(),此函數就會把底層看到的信息開始一層一層的notify:
static void nfc_jni_Discovery_notification_callback(void *pContext,
phLibNfc_RemoteDevList_t *psRemoteDevList,
uint8_t uNofRemoteDev, NFCSTATUS status)
{
...
if(status == NFCSTATUS_DESELECTED)
{
LOG_CALLBACK("nfc_jni_Discovery_notification_callback: Target deselected", status);
/* Notify manager that a target was deselected */
//@paul: 執行cached_NfcManager_notifyTargetDeselected對應的java函數
e->CallVoidMethod(nat->manager, cached_NfcManager_notifyTargetDeselected);
if(e->ExceptionCheck())
{
ALOGE("Exception occurred");
kill_client(nat);
}
}
else
{
...
if((remDevInfo->RemDevType == phNfc_eNfcIP1_Initiator)
|| (remDevInfo->RemDevType == phNfc_eNfcIP1_Target))
{
...
/* Set P2P Target mode */
jfieldID f = e->GetFieldID(tag_cls.get(), "mMode", "I");
if(remDevInfo->RemDevType == phNfc_eNfcIP1_Initiator)
{
ALOGD("Discovered P2P Initiator");
e->SetIntField(tag.get(), f, (jint)MODE_P2P_INITIATOR);
}
else
{
ALOGD("Discovered P2P Target");
e->SetIntField(tag.get(), f, (jint)MODE_P2P_TARGET);
}
...
}
else
{
...
/* New tag instance */
...
/* Set tag UID */
...
/* Generate technology list */
...
}
...
/* Notify the service */
if((remDevInfo->RemDevType == phNfc_eNfcIP1_Initiator)
|| (remDevInfo->RemDevType == phNfc_eNfcIP1_Target))
{
/* Store the handle of the P2P device */
hLlcpHandle = remDevHandle;
/* Notify manager that new a P2P device was found */
//@paul: 偵測到P2P設備進入範圍,調用JNI層對應的API,最終call到JAVA層API
e->CallVoidMethod(nat->manager, cached_NfcManager_notifyLlcpLinkActivation, tag.get());
if(e->ExceptionCheck())
{
ALOGE("Exception occurred");
kill_client(nat);
}
}
else
{
/* Notify manager that new a tag was found */
//@paul:偵測到Tag設備進入範圍,
e->CallVoidMethod(nat->manager, cached_NfcManager_notifyNdefMessageListeners, tag.get());
if(e->ExceptionCheck())
{
ALOGE("Exception occurred");
kill_client(nat);
}
}
}
}
所以Tag的真正開始時在執行下列函數後:
e->CallVoidMethod(nat->manager, cached_NfcManager_notifyNdefMessageListeners, tag.get());
最終調用到JNI層的notifyNdefMessageListeners(),對應的定義在:
private void notifyNdefMessageListeners(NativeNfcTag tag) {
mListener.onRemoteEndpointDiscovered(tag);
}
爲後文作準備,P2P設備的Framework開始時在執行下列函數:
e->CallVoidMethod(nat->manager, cached_NfcManager_notifyLlcpLinkActivation, tag.get());
最終調用到JNI層的notifyLlcpLinkActivation(),對應的定義在:
private void notifyLlcpLinkActivation(NativeP2pDevice device) {
mListener.onLlcpLinkActivated(device);
}
Tag設備發現framework流程
下面開始分析Tag的Framework的流程:我們的分析會從mListener.onRemoteEndpointDiscovered(tag)開始:
@Override
public void onRemoteEndpointDiscovered(TagEndpoint tag) {
sendMessage(NfcService.MSG_NDEF_TAG, tag);
}
其中發送的消息MSG_NDEF_TAG會進入到NfcService.java的handleMessage(),其中處理MSG_NDEF_TAG的流程如下:
case MSG_NDEF_TAG:
if (DBG) Log.d(TAG, "Tag detected, notifying applications");
...
//@paul: 如果是read mode
if (readerParams != null) {
presenceCheckDelay = readerParams.presenceCheckDelay;
//@paul: 如果設置不檢查能否轉成NDEF的標記
if ((readerParams.flags & NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK) != 0) {
if (DBG) Log.d(TAG, "Skipping NDEF detection in reader mode");
//@paul: 判斷tag是否還在範圍內
tag.startPresenceChecking(presenceCheckDelay, callback);
//@paul: 將偵測的tag進行分發
dispatchTagEndpoint(tag, readerParams);
break;
}
}
boolean playSound = readerParams == null ||
(readerParams.flags & NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS) == 0;
if (mScreenState == ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED && playSound) {
//paul: 播放開始聲音
playSound(SOUND_START);
}
if (tag.getConnectedTechnology() == TagTechnology.NFC_BARCODE) {
// When these tags start containing NDEF, they will require
// the stack to deal with them in a different way, since
// they are activated only really shortly.
// For now, don't consider NDEF on these.
if (DBG) Log.d(TAG, "Skipping NDEF detection for NFC Barcode");
//@paul: 如果是Barcode,直接進行tag的分發
tag.startPresenceChecking(presenceCheckDelay, callback);
dispatchTagEndpoint(tag, readerParams);
break;
}
//@paul: 依據底層格式,進行NDEF的轉換
NdefMessage ndefMsg = tag.findAndReadNdef();
if (ndefMsg != null) {
//@paul: NDEF轉換成功後,進行Tag的分發
tag.startPresenceChecking(presenceCheckDelay, callback);
dispatchTagEndpoint(tag, readerParams);
} else {
//@paul: 無法轉換時,先進行底層的連接後(物理層先重連),將消息分發
if (tag.reconnect()) {
tag.startPresenceChecking(presenceCheckDelay, callback);
dispatchTagEndpoint(tag, readerParams);
} else {
tag.disconnect();
playSound(SOUND_ERROR);
}
}
break;
其中比較關鍵的函數有兩個:
findAndReadNdef()
dispatchTagEndpoint()
findAndReadNdef()是和芯片強相關的,其目的是依據芯片的支持能力,將讀到的Tag中的內容轉換成NDEF格式的數據. 不同芯片其支持的能力存在差異,此部分的code也是存在差異,針對NXP芯片簡單分析如下:
@Override
public NdefMessage findAndReadNdef() {
...
for (int techIndex = 0; techIndex < technologies.length; techIndex++) {
...
//@paul: 判斷connectedHandle與當前Index對應的handle的關係,並更新狀態,
status = connectWithStatus(technologies[techIndex]);
...
// Check if this type is NDEF formatable
if (!foundFormattable) {
//@paul: 依據芯片特性判斷哪些tag是可以轉成NDEF格式
if (isNdefFormatable()) {
//@paul: 更新對應的handle,handle用於後續的操作
foundFormattable = true;
formattableHandle = getConnectedHandle();
formattableLibNfcType = getConnectedLibNfcType();
// We'll only add formattable tech if no ndef is
// found - this is because libNFC refuses to format
// an already NDEF formatted tag.
}
reconnect();
}
...
status = checkNdefWithStatus(ndefinfo);
...
//@paul: 讀取tag上的數據
byte[] buff = readNdef();
if (buff != null) {
try {
//@paul: 將數據轉換成NDEF格式
ndefMsg = new NdefMessage(buff);
//@paul: 更新對應Tag的信息
addNdefTechnology(ndefMsg,
getConnectedHandle(),
getConnectedLibNfcType(),
getConnectedTechnology(),
supportedNdefLength, cardState);
reconnect();
} catch (FormatException e) {
// Create an intent anyway, without NDEF messages
generateEmptyNdef = true;
}
} else {
generateEmptyNdef = true;
}
...
}
if (ndefMsg == null && foundFormattable) {
// Tag is not NDEF yet, and found a formattable target,
// so add formattable tech to tech list.
addNdefFormatableTechnology(
formattableHandle,
formattableLibNfcType);
}
return ndefMsg;
}
Tag消息分發流程
上述執行完成後,理論上會進入Tag的Dispatch流程,中間的流程省略,我們直接進入mNfcDispatcher.dispatchTag(tag)函數的分析,這纔是tag分發的終極形式,直接看代碼吧:
/** Returns:
* <ul>
* <li /> DISPATCH_SUCCESS if dispatched to an activity,
* <li /> DISPATCH_FAIL if no activities were found to dispatch to,
* <li /> DISPATCH_UNLOCK if the tag was used to unlock the device
* </ul>
*/
public int dispatchTag(Tag tag) {
PendingIntent overrideIntent;
IntentFilter[] overrideFilters;
String[][] overrideTechLists;
boolean provisioningOnly;
//@paul: 如果上層APP定義了下列值,就會在這裏進行更新
synchronized (this) {
overrideFilters = mOverrideFilters;
overrideIntent = mOverrideIntent;
overrideTechLists = mOverrideTechLists;
provisioningOnly = mProvisioningOnly;
}
//@paul: Tag在screen on 且lock的情況下,需要嘗試unlock,否則如法處理
boolean screenUnlocked = false;
if (!provisioningOnly &&
mScreenStateHelper.checkScreenState() == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) {
screenUnlocked = handleNfcUnlock(tag);
if (!screenUnlocked) {
return DISPATCH_FAIL;
}
}
NdefMessage message = null;
//@paul: 將Tag解析成NDEF格式數據,並讀取內容
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
message = ndef.getCachedNdefMessage();
}
if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message);
DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
//@paul: 和APP相關,暫時沒有研究
resumeAppSwitches();
//@paul: 如果上層APP有定義前臺分發機制,則會調用到PendingIntent.send()功能,實現前臺分發機制
if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters,
overrideTechLists)) {
return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
}
//@paul: 判斷NDEF消息是否是handover格式
if (mHandoverManager.tryHandover(message)) {
if (DBG) Log.i(TAG, "matched BT HANDOVER");
return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
}
//@paul: 判斷NDEF消息是否是WifiConfiguration格式
if (NfcWifiProtectedSetup.tryNfcWifiSetup(ndef, mContext)) {
if (DBG) Log.i(TAG, "matched NFC WPS TOKEN");
return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
}
//@paul: 將消息發送給對ACTION_NDEF_DISCOVERED感興趣的APP處理
if (tryNdef(dispatch, message, provisioningOnly)) {
return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
}
if (screenUnlocked) {
// We only allow NDEF-based mimeType matching in case of an unlock
return DISPATCH_UNLOCK;
}
if (provisioningOnly) {
// We only allow NDEF-based mimeType matching
return DISPATCH_FAIL;
}
// Only allow NDEF-based mimeType matching for unlock tags
//@paul: <span style="font-family: Arial, Helvetica, sans-serif;">將消息發送給對</span>ACTION_TECH_DISCOVERED感興趣的APP處理
if (tryTech(dispatch, tag)) {
return DISPATCH_SUCCESS;
}
//@paul: 更新Intent爲ACTION_TAG_DISCOVERED
dispatch.setTagIntent();
//@paul: 將消息發送給對ACTION_TAG_DISCOVERED感興趣的APP處理
if (dispatch.tryStartActivity()) {
if (DBG) Log.i(TAG, "matched TAG");
return DISPATCH_SUCCESS;
}
if (DBG) Log.i(TAG, "no match");
return DISPATCH_FAIL;
}
下面分別介紹tryHandover(),tryNfcWifiSetup(),tryNdef(),tryTech().
首先看看tryHandover(),此函數主要做BlueTooth的handover,當然如果要做wifi的handover,從技術上看是完全沒有問題的.
public boolean tryHandover(NdefMessage m) {
...
BluetoothHandoverData handover = parseBluetooth(m);
...
synchronized (mLock) {
...
<span style="white-space:pre"> </span>//@paul: 發送MSG_PERIPHERAL_HANDOVER消息,在HandleMessage中進行處理
Message msg = Message.obtain(null, HandoverService.MSG_PERIPHERAL_HANDOVER, 0, 0);
Bundle headsetData = new Bundle();
headsetData.putParcelable(HandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device);
headsetData.putString(HandoverService.EXTRA_PERIPHERAL_NAME, handover.name);
headsetData.putInt(HandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport);
msg.setData(headsetData);
return sendOrQueueMessageLocked(msg);
}
}
比較重要的就是parseBluetooth()和最後的sendOrQueueMessageLocked()函數。其中parseBluetooth邏輯比較簡單,就是按照Spec的要求,把NDEF數據一個字節一個字節的解析出來,存放在對應的結構體中。sendOrQueueMessageLocked則是將BT的信息發送給上述完整的代碼分析如下:
BluetoothHandoverData parseBluetooth(NdefMessage m) {
...
// Check for BT OOB record
if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BT_OOB)) {
return parseBtOob(ByteBuffer.wrap(r.getPayload()));
}
// Check for BLE OOB record
if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BLE_OOB)) {
return parseBleOob(ByteBuffer.wrap(r.getPayload()));
}
// Check for Handover Select, followed by a BT OOB record
if (tnf == NdefRecord.TNF_WELL_KNOWN &&
Arrays.equals(type, NdefRecord.RTD_HANDOVER_SELECT)) {
return parseBluetoothHandoverSelect(m);
}
// Check for Nokia BT record, found on some Nokia BH-505 Headsets
if (tnf == NdefRecord.TNF_EXTERNAL_TYPE && Arrays.equals(type, TYPE_NOKIA)) {
return parseNokia(ByteBuffer.wrap(r.getPayload()));
}
return null;
}
public boolean sendOrQueueMessageLocked(Message msg) {
if (!mBound || mService == null) {
// Need to start service, let us know if we can queue the message
//@paul: 首先bind Service,成功後會調用onServiceConnected()
if (!bindServiceIfNeededLocked()) {
Log.e(TAG, "Could not start service");
return false;
}
// Queue the message to send when the service is bound
//@paul: 先將消息放在緩存隊列中
mPendingServiceMessages.add(msg);
} else {
try {
//@paul: 已經綁定後,則直接誒發送消息
mService.send(msg);
} catch (RemoteException e) {
Log.e(TAG, "Could not connect to handover service");
return false;
}
}
return true;
}
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
...
try {
//@paul: 直接發送消息
mService.send(msg);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register client");
}
// Send all queued messages
while (!mPendingServiceMessages.isEmpty()) {
//@paul: 如果mPendingServiceMessages有數據是,會一直嘗試把pending的數據發送完成
msg = mPendingServiceMessages.remove(0);
try {
mService.send(msg);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send queued message to service");
}
}
}
}
前文提到發送了MSG_PERIPHERAL_HANDOVER,最終進入doPeripheralHandover()函數,目前只是分析到BT流程啓動,後續建立連接部分暫不分析了。
void doPeripheralHandover(Message msg) {
Bundle msgData = msg.getData();
BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
//@paul: 如果存在mBluetoothPeripheralHandover,表明有handover正在進行
if (mBluetoothPeripheralHandover != null) {
Log.d(TAG, "Ignoring pairing request, existing handover in progress.");
return;
}
//@paul: mBluetoothPeripheralHandover
mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(HandoverService.this,
device, name, transport, HandoverService.this);
// TODO: figure out a way to disable polling without deactivating current target
if (transport == BluetoothDevice.TRANSPORT_LE) {
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS);
}
//@paul: 如果Bluetooth有啓動,則執行對應的start函數
if (mBluetoothAdapter.isEnabled()) {
if (!mBluetoothPeripheralHandover.start()) {
//@paul: Handover成功後,NFC繼續polling
mNfcAdapter.resumePolling();
}
} else {
if (!enableBluetooth()) {
Log.e(TAG, "Error enabling Bluetooth.");
mBluetoothPeripheralHandover = null;
}
}
}
//@paul:接上述start函數
public boolean start() {
...
//paul: 目前只關注此函數
nextStep();
return true;
}
//@paul: 進入對應的狀態機,啓動BT連接
void nextStep() {
if (mAction == ACTION_INIT) {
nextStepInit();
} else if (mAction == ACTION_CONNECT) {
nextStepConnect();
} else {
nextStepDisconnect();
}
}
分析完Handover,可能已經覺得有點累了,那我們來個簡單的,看看tryNfcWifiSetup(),其實這部分和BT的原理基本類似,就是透過NFC拿到WIFI相關的credential,然後利用credential簡歷WIFI連接,實現Handover的目的。
public static boolean tryNfcWifiSetup(Ndef ndef, Context context) {
...
//@paul: 解析得到Wifi相關的credential數據
final WifiConfiguration wifiConfiguration = parse(cachedNdefMessage);
if (wifiConfiguration != null &&!UserManager.get(context).hasUserRestriction(
UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT)) {
Intent configureNetworkIntent = new Intent()
.putExtra(EXTRA_WIFI_CONFIG, wifiConfiguration)
.setClass(context, ConfirmConnectToWifiNetworkActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//@paul: 將wifi數據放在intent中,然後以設置的參數啓動wifi相關的連接
context.startActivityAsUser(configureNetworkIntent, UserHandle.CURRENT);
return true;
}
return false;
}
接下來就是tryNdef()和tryTech()的分析,其實此部分的流程是前面提到的ACTION_TAG_DISCOVERED的流程是類似,主要是分兩步:
1. 設置對應的intent爲:ACTION_NDEF_DISCOVERED/ACTION_TECH_DISCOVERED
2. 以上述intent啓動相關的activity. 只不過會加入AAR消息的檢查,關於AAR的說明可以自行查找。簡單的說明如下:
在Android4.0(API Level 14)中引入的Android應用程序記錄(AAR),提供了較強的在掃描到NFC標籤時,啓動應用程序的確定性。AAR有嵌入到NDEF記錄內部的應用程序的包名。你能夠把一個AAR添加到你的NDEF消息的任何記錄中,因爲Android會針對AAR來搜索整個NDEF消息。如果它找到一個AAR,它就會基於AAR內部的包名來啓動應用程序。如果該應用程序不在當前的設備上,會啓動Google Play來下載對應的應用程序。
如果你想要防止其他的應用對相同的Intent的過濾並潛在的處理你部署的特定的NFC標籤,那麼AAR是有用的。AAR僅在應用程序級被支持,因爲包名的約束,並不能在Activity級別來過濾Intent。如果你想要在Activity級處理Intent,請使用Intent過濾器。
以上基本就介紹完Tag的整體處理流程。代碼流程稍微有點多,建議代碼分幾次看,以免遺忘。
後續會在介紹一下NFC P2P設備相互發現的流程。由於P2P的應用比較多,介紹的篇幅也會相對較多