[NFC]P2P设备响应流程
前文[NFC]Tag设备响应流程中有提到P2P设备的发现的函数始于:onLlcpLinkActivated().
本文将基于onLlcpLinkActivated()开始后文的分析,进而引出P2P流程中的SNEP,NDEFPUSH,HANDOVER以及ECHOSERVER的响应过程.
程序进入 onLlcpLinkActivated() 后,有点类似notify,开始同时上层,有P2P设备靠近了,解析一下,是什么格式,看看要做什么操作.
- @Override
- public void onLlcpLinkActivated(NfcDepEndpoint device) {
- sendMessage(NfcService.MSG_LLCP_LINK_ACTIVATION, device);
- }
消息MSG_LLCP_LINK_ACTIVATION被NfcService.Java自身注册的NfcServiceHandler进行处理
- case MSG_LLCP_LINK_ACTIVATION:
- if (mIsDebugBuild) {
- //@paul: 发送LLCP_UP的广播
- Intent actIntent = new Intent(ACTION_LLCP_UP);
- mContext.sendBroadcast(actIntent);
- }
- //@paul: 解析底层传递上来的NfcDepEndpoint信息
- llcpActivated((NfcDepEndpoint) msg.obj);
- break;
在进入llcpActivated()之后,系统会区分Target和Initiator,这两种角色的流程基本上相同,差异点是在Target端会先执行connect(),此connect()是进行数据链路层的连接,保证底层已经是发现对方并且是可以连接的。
- <span style="font-size:12px;">public void onLlcpActivated()
- public void onLlcpFirstPacketReceived()
- public void onLlcpDeactivated()
- void onSendComplete(NdefMessage msg, long elapsedRealtime)</span>
这些函数必须在UI main thread 调用,用于接收到底层连接的各种状态的更新。各个函数的意义依照名字基本上就能理解了。
直接分析onLlcpActivated()函数:
- /**
- * Must be called on UI Thread.
- */
- public void onLlcpActivated() {
- Log.i(TAG, "LLCP activated");
- synchronized (P2pLinkManager.this) {
- ...
- mLlcpConnectDelayed = false;
- //@paul: 初始状态mLinkState为LINK_STATE_DOWN
- switch (mLinkState) {
- case LINK_STATE_DOWN:
- if (DBG) Log.d(TAG, "onP2pInRange()");
- mLinkState = LINK_STATE_WAITING_PDU;
- //@paul: 通知UI,发现P2P设备
- mEventListener.onP2pInRange();
- //@paul: 初始状态mSendState为SEND_STATE_NOTHING_TO_SEND
- if (mSendState == SEND_STATE_PENDING) {
- if (DBG) Log.d(TAG, "Sending pending data.");
- mHandler.removeMessages(MSG_WAIT_FOR_LINK_TIMEOUT);
- mSendState = SEND_STATE_SENDING;
- onP2pSendConfirmed(false);
- } else {
- mSendState = SEND_STATE_NOTHING_TO_SEND;
- //@paul: 依据APP设置的信息,准备发送的信息
- prepareMessageToSend(true);
- if (mMessageToSend != null ||
- (mUrisToSend != null && mHandoverManager.isHandoverSupported())) {
- if ((mSendFlags & NfcAdapter.FLAG_NDEF_PUSH_NO_CONFIRM) != 0) {
- mSendState = SEND_STATE_SENDING;
- //@paul: 不需要UI确认
- onP2pSendConfirmed(false);
- } else {
- mSendState = SEND_STATE_NEED_CONFIRMATION;
- if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()");
- //@paul: 需要UI上确认过才能发送
- mEventListener.onP2pSendConfirmationRequested();
- }
- }
- }
- break;
- ...
- }
- }
- }
由于NFC APP(例如Gallery,Calendar,联系簿等)在使用NFC发送数据时,都需要先设置要发送的数据的格式。设置的主要内容存放在变量:mMessageToSend ,mUrisToSend 中。此处涉及到APP部分,后续在单独开一节说明这部分。
先说明一下prepareMessageToSend(),此函数流程在注释中说明:
- void prepareMessageToSend(boolean generatePlayLink) {
- synchronized (P2pLinkManager.this) {
- //@Paul:准备要发送的消息,分别存储在mMessageToSend和mUrisToSend中
- mMessageToSend = null;
- mUrisToSend = null;
- //@Paul:如果没有启动send,则直接返回,该变量有上层APP设定
- if (!mIsSendEnabled) {
- return;
- }
- //@Paul:判断前台程序是否启动
- List<Integer> foregroundUids = mForegroundUtils.getForegroundUids();
- if (foregroundUids.isEmpty()) {
- ...
- }
- //@Paul: 由上层定义的Callback信息
- if (mCallbackNdef != null) {
- if (foregroundUids.contains(mNdefCallbackUid)) {
- try {
- //@Paul: 如果有定义,则调用上层的createBeamShareData()函数
- BeamShareData shareData = mCallbackNdef.createBeamShareData();
- mMessageToSend = shareData.ndefMessage;
- mUrisToSend = shareData.uris;
- mSendFlags = shareData.flags;
- return;
- } catch (Exception e) {
- Log.e(TAG, "Failed NDEF callback: " + e.getMessage());
- }
- } else {
- ...
- }
- }
- // fall back to default NDEF for the foreground activity, unless the
- // application disabled this explicitly in their manifest.
- //@Paul: 如果前面没有进入,则使用默认值,将当前Pkg在Google Play的信息打包发送到上层
- String[] pkgs = mPackageManager.getPackagesForUid(foregroundUids.get(0));
- if (pkgs != null && pkgs.length >= 1) {
- if (!generatePlayLink || beamDefaultDisabled(pkgs[0])
- || isManagedOrBeamDisabled(foregroundUids.get(0))) {
- if (DBG) Log.d(TAG, "Disabling default Beam behavior");
- mMessageToSend = null;
- mUrisToSend = null;
- } else {
- mMessageToSend = createDefaultNdef(pkgs[0]);
- mUrisToSend = null;
- }
- }
- if (DBG) Log.d(TAG, "mMessageToSend = " + mMessageToSend);
- if (DBG) Log.d(TAG, "mUrisToSend = " + mUrisToSend);
- }
- }
当要发送的信息准备好时,等待上层APP的确认动作(如果需要确认,否则直接把前面准备好的信息发送出去),确认的动作是上层APP来进行的。
其实前文也有稍微提到,关于P2P和上层SendUI之间会有一些交互流程,此部分的流程用图形的方式说明如下:
接下来就是onP2pSendConfirmationRequested()的介绍,此部分主要的目的是通知到SendUI层,请求用户确认或者拒绝。
- public void onP2pSendConfirmationRequested() {
- //@Paul: 一般来讲,都会有UI界面,所以默认会进入showPreSend()
- if (mSendUi != null) {
- //@Paul: showPreSend从感官上看,就是将当前的荧幕进行缩小,提示用户进行点击确认
- mSendUi.showPreSend(false);
- } else {
- //@Paul: 如果没有用户界面,默认就会进行到确认的流程
- mCallback.onP2pSendConfirmed();
- }
- }
上述onP2pSendConfirmed(),在后面你又会看到,多留心。接上面介绍,一旦进入上述showPreSend()后,屏幕会缩小,然后提示用户点击确认,一旦用户执行了点击确认的动作,就会进入到SendUI.onTouch(),提前剧透一下,一旦点击了onTouch()后,就能看到上面的onP2pSendConfirmed()。
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (mState != STATE_W4_TOUCH) {
- return false;
- }
- mState = STATE_SENDING;
- // Ignore future touches
- mScreenshotView.setOnTouchListener(null);
- // Cancel any ongoing animations
- mFrameCounterAnimator.cancel();
- mPreAnimator.cancel();
- //@Paul: 启动onSendConfirmed()
- mCallback.onSendConfirmed();
- return true;
- }
- @Override
- public void onSendConfirmed() {
- //@Paul: 如果没有发送动作,则调用showStartSend()
- if (!mSending) {
- if (mSendUi != null) {
- mSendUi.showStartSend();
- }
- //@Paul: 又调用了onP2pSendConfirmed()
- mCallback.onP2pSendConfirmed();
- }
- mSending = true;
- }
这个地方补充说明一下,关于角色的确定,如果谁主动点击了屏幕,执行了确定的动作,那么就是发送端,发送端默认是Client端,因为接收端默认已经启动了Server端,等待对方发起连接. 所以后续在分析onP2pSendConfirmed()时,你会看到代码会启动clinet端.
- private void onP2pSendConfirmed(boolean requireConfirmation) {
- if (DBG) Log.d(TAG, "onP2pSendConfirmed()");
- synchronized (this) {
- //@Paul:状态检查
- if (mLinkState == LINK_STATE_DOWN || (requireConfirmation
- && mSendState != SEND_STATE_NEED_CONFIRMATION)) {
- return;
- }
- mSendState = SEND_STATE_SENDING;
- if (mLinkState == LINK_STATE_WAITING_PDU) {
- //@Paul: 如果当前状态时WAITING PDU,就执行llcp连接
- mLinkState = LINK_STATE_UP;
- connectLlcpServices();
- } else if (mLinkState == LINK_STATE_UP && mLlcpServicesConnected) {
- //@Paul: 如果llcp已经连接上了,则进行Ndef消息的发送
- sendNdefMessage();
- } else if (mLinkState == LINK_STATE_UP && mLlcpConnectDelayed) {
- // Connect was delayed to interop with pre-MR2 stacks; send connect now.
- connectLlcpServices();
- } else if (mLinkState == LINK_STATE_DEBOUNCE) {
- // Restart debounce timeout and tell user to tap again
- scheduleTimeoutLocked(MSG_DEBOUNCE_TIMEOUT, LINK_SEND_CONFIRMED_DEBOUNCE_MS);
- mEventListener.onP2pSendDebounce();
- }
- }
- }
进入ConnectLlcpServices()后,你就看到LLCP的client服务就可能一项一项的启动.用代码说话吧:
- void connectLlcpServices() {
- synchronized (P2pLinkManager.this) {
- //@Paul: 如果有connectTask正在运行,则返回
- if (mConnectTask != null) {
- Log.e(TAG, "Still had a reference to mConnectTask!");
- }
- //@Paul: 创建新的connectTask,并执行此Task
- mConnectTask = new ConnectTask();
- mConnectTask.execute();
- }
- }
- @Override
- protected Boolean doInBackground(Void... params) {
- boolean needsHandover = false;
- boolean needsNdef = false;
- boolean success = false;
- HandoverClient handoverClient = null;
- SnepClient snepClient = null;
- NdefPushClient nppClient = null;
- synchronized(P2pLinkManager.this) {
- if (mUrisToSend != null) {
- //@Paul:如果URI存在,则可能进行进行Handover
- needsHandover = true;
- }
- if (mMessageToSend != null) {
- //@Paul: 如果要发送的消息不为空,则可能是Ndef消息
- needsNdef = true;
- }
- }
- // We know either is requested - otherwise this task
- // wouldn't have been started.
- if (needsHandover) {
- //@Paul: 创建HandoverClient
- handoverClient = new HandoverClient();
- try {
- //@Paul: 进行连接操作,主要分两步
- // service.createLlcpSocket(0, MIU, 1, 1024);
- // sock.connectToService(HandoverServer.HANDOVER_SERVICE_NAME);
- // 上述连接是依据服务名来的
- // 主要函数:sendHandoverRequest()
- handoverClient.connect();
- success = true; // Regardless of NDEF result
- } catch (IOException e) {
- handoverClient = null;
- }
- }
- if (needsNdef || (needsHandover && handoverClient == null)) {
- //@Paul: 创建SnepClient
- snepClient = new SnepClient();
- try {
- //@Paul: 进行连接操作,主要分三步
- // NfcService.getInstance().createLlcpSocket(0, mMiu, mRwSize, 1024)
- // socket.connectToService(mServiceName)/socket.connectToSap(mPort);
- // 依据服务名或者Port连接
- // new SnepMessenger(),用于接收或发送SNEP消息
- // 主要函数:put()/get()
- snepClient.connect();
- success = true;
- } catch (IOException e) {
- snepClient = null;
- }
- if (!success) {
- //@Paul:如果上述SNEP Client创建失败,则创建NPP Client
- nppClient = new NdefPushClient();
- try {
- //@Paul:进行连接操作,主要分两步
- // service.createLlcpSocket(0, MIU, 1, 1024);
- // sock.connectToService(NdefPushServer.SERVICE_NAME);
- // 主要函数:push()/close()
- nppClient.connect();
- success = true;
- } catch (IOException e) {
- nppClient = null;
- }
- }
- }
- synchronized (P2pLinkManager.this) {
- //如果有取消,则将前面的client端全部关闭
- if (isCancelled()) {
- // Cancelled by onLlcpDeactivated on UI thread
- if (handoverClient != null) {
- handoverClient.close();
- }
- if (snepClient != null) {
- snepClient.close();
- }
- if (nppClient != null) {
- nppClient.close();
- }
- return false;
- } else {
- // Once assigned, these are the responsibility of
- // the code on the UI thread to release - typically
- // through onLlcpDeactivated().
- mHandoverClient = handoverClient;
- mSnepClient = snepClient;
- mNdefPushClient = nppClient;
- return success;
- }
- }
- }
一旦连接上之后, 就会顺序进入onP2pSendConfirmed() 中的第2个else中的sendNdefMessage()。此函数看名字就能知道意义,就是将前面的Ndef消息发送出去:
- void sendNdefMessage() {
- synchronized (this) {
- cancelSendNdefMessage();
- //@Paul:启动新的进程,处理要发送的数据
- mSendTask = new SendTask();
- mSendTask.execute();
- }
- }
启动SendTask,该类是继承于AsyncTask,调用execute后自动执行,具体的代码如下:
- @Override
- public Void doInBackground(Void... args) {
- NdefMessage m;
- Uri[] uris;
- boolean result = false;
- //@Paul: 前面connect时候创建的mSnepClient等
- synchronized (P2pLinkManager.this) {
- if (mLinkState != LINK_STATE_UP || mSendState != SEND_STATE_SENDING) {
- return null;
- }
- m = mMessageToSend;
- uris = mUrisToSend;
- snepClient = mSnepClient;
- handoverClient = mHandoverClient;
- nppClient = mNdefPushClient;
- }
- long time = SystemClock.elapsedRealtime();
- //@Paul: 前面uri有赋值的话,就直接进入Handover的处理
- if (uris != null) {
- if (DBG) Log.d(TAG, "Trying handover request");
- try {
- int handoverResult = doHandover(uris);
- switch (handoverResult) {
- case HANDOVER_SUCCESS:
- result = true;
- break;
- case HANDOVER_FAILURE:
- result = false;
- break;
- case HANDOVER_UNSUPPORTED:
- result = false;
- onHandoverUnsupported();
- break;
- }
- } catch (IOException e) {
- result = false;
- }
- }
- //@Paul: 前面message有赋值的话,进入NDEF消息的处理
- if (!result && m != null && snepClient != null) {
- if (DBG) Log.d(TAG, "Sending ndef via SNEP");
- try {
- int snepResult = doSnepProtocol(m);
- switch (snepResult) {
- case SNEP_SUCCESS:
- result = true;
- break;
- case SNEP_FAILURE:
- result = false;
- break;
- default:
- result = false;
- }
- } catch (IOException e) {
- result = false;
- }
- }
- //@Paul: 若不支持Snep格式,则用NPP方式放松
- if (!result && m != null && nppClient != null) {
- result = nppClient.push(m);
- }
- time = SystemClock.elapsedRealtime() - time;
- if (DBG) Log.d(TAG, "SendTask result=" + result + ", time ms=" + time);
- if (result) {
- //@Paul: 若发送成功后,调用onSendComplete,发送MSG_SEND_COMPLETE
- onSendComplete(m, time);
- }
- return null;
- }
下面分别介绍doHandover(uris),doSnepProtocol(m),nppClient.push(m); onSendComplete(m, time)。
doHandover():在AOSP中主要是生成BT 的NDEF Message,主要流程是先产生NDEF Record,然后将Record组成MSG,然后调用socket的send(),等send() 调用完成,在while循环中等待Handover的Response。如果对方不支持Handover时,就会尝试调用SNEP的处理函数,处理完成之后,开始解析Response数据,然后将解析的数据存放在Intent中,然后发送广播消息,启动对事件关心的BT activity。
doSnepProtocol():主要是依据SNEP协议规定,进行Snep协议的处理。简单的流程分析如下:
为了进一步说清楚SNEP,可以先参考一下,前面介绍的SNEP的协议,首先分析sendMessage(),此函数的目的是将request消息依据spec规定发送出去:
- public void sendMessage(SnepMessage msg) throws IOException {
- ...
- if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message");
- // Send first fragment
- //@Paul: 去当前buffer的长度和定义的Fragment长度中较小值
- int length = Math.min(buffer.length, mFragmentLength);
- byte[] tmpBuffer = Arrays.copyOfRange(buffer, 0, length);
- if (DBG) Log.d(TAG, "about to send a " + length + " byte fragment");
- //@Paul: 将消息透过socket发送出去
- mSocket.send(tmpBuffer);
- //@Paul: 若数据包不用切割(一个数据包能发完),则直接返回
- if (length == buffer.length) {
- return;
- }
- //@Paul: 若切片后发送,则需要等待对方的回复后再决定下一步行动
- // Look for Continue or Reject from peer.
- int offset = length;
- byte[] responseBytes = new byte[HEADER_LENGTH];
- mSocket.receive(responseBytes);
- //@Paul: 接收对方的Response消息
- SnepMessage snepResponse;
- try {
- snepResponse = SnepMessage.fromByteArray(responseBytes);
- } catch (FormatException e) {
- throw new IOException("Invalid SNEP message", e);
- }
- //@Paul: 如果对方的回复不是continue,在返回错误
- if (DBG) Log.d(TAG, "Got response from first fragment: " + snepResponse.getField());
- if (snepResponse.getField() != remoteContinue) {
- throw new IOException("Invalid response from server (" +
- snepResponse.getField() + ")");
- }
- // Send remaining fragments.
- //@Paul: 对方要求继续发送时,将剩余的数据发送完成
- while (offset < buffer.length) {
- length = Math.min(buffer.length - offset, mFragmentLength);
- tmpBuffer = Arrays.copyOfRange(buffer, offset, offset + length);
- if (DBG) Log.d(TAG, "about to send a " + length + " byte fragment");
- mSocket.send(tmpBuffer);
- offset += length;
- }
- }
在上述消息发送完成后,需要等待对方的response消息,就进入了getMessage()阶段,代码分析如下:
- public SnepMessage getMessage() throws IOException, SnepException {
- ...
- //@Paul: 等待对方的回复消息
- size = mSocket.receive(partial);
- if (DBG) Log.d(TAG, "read " + size + " bytes");
- if (size < 0) {
- try {
- //@Paul:接收的数据大小小于0,直接回复Reject
- mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
- } catch (IOException e) {
- // Ignore
- }
- throw new IOException("Error reading SNEP message.");
- } else if (size < HEADER_LENGTH) {
- try {
- //@Paul:接收的数据大小小于Header Size(因为Snep数据必须包含Header),直接回复Reject
- mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
- } catch (IOException e) {
- // Ignore
- }
- throw new IOException("Invalid fragment from sender.");
- } else {
- //@Paul: 更新buffer值
- readSize = size - HEADER_LENGTH;
- buffer.write(partial, 0, size);
- }
- //@Paul: 解析第一包数据收到的头中的字段
- DataInputStream dataIn = new DataInputStream(new ByteArrayInputStream(partial));
- requestVersion = dataIn.readByte();
- byte requestField = dataIn.readByte();
- requestSize = dataIn.readInt();
- if (DBG) Log.d(TAG, "read " + readSize + " of " + requestSize);
- //@Paul: 如果SNEP的Version不匹配,则认为接收完成
- if (((requestVersion & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) {
- // Invalid protocol version; treat message as complete.
- return new SnepMessage(requestVersion, requestField, 0, 0, null);
- }
- //@Paul: 如果接收的数据小于Header中规定的数据,则请求对方继续发送,回复Continue
- if (requestSize > readSize) {
- if (DBG) Log.d(TAG, "requesting continuation");
- mSocket.send(SnepMessage.getMessage(fieldContinue).toByteArray());
- } else {
- doneReading = true;
- }
- //@Paul: 发送完Continue后,执行loop中的receive(),等待后续的package
- // Remaining fragments
- while (!doneReading) {
- try {
- size = mSocket.receive(partial);
- if (DBG) Log.d(TAG, "read " + size + " bytes");
- if (size < 0) {
- try {
- //@Paul:接收的数据段错误,直接回复Reject
- mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
- } catch (IOException e) {
- // Ignore
- }
- throw new IOException();
- } else {
- //@Paul:不断更新收到的数据
- readSize += size;
- buffer.write(partial, 0, size);
- if (readSize == requestSize) {
- doneReading = true;
- }
- }
- } catch (IOException e) {
- try {
- mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
- } catch (IOException e2) {
- // Ignore
- }
- throw e;
- }
- }
- // Build NDEF message set from the stream
- try {
- //@Paul:将接收的数据转换成SNEP格式
- return SnepMessage.fromByteArray(buffer.toByteArray());
- } catch (FormatException e) {
- Log.e(TAG, "Badly formatted NDEF message, ignoring", e);
- throw new SnepException(e);
- }
- }
为了兼容性的问题,可能当前的P2P并不支持SNEP协议,那么就会尝试用NPP协议进行数据的解析,这样就到了NdefPushClient的push流程,此部分的协议比较简单,流程也比较简单:
- public boolean push(NdefMessage msg) {
- ...
- // We only handle a single immediate action for now
- //@Paul: 按照制定格式创建NdefPushProtocol
- NdefPushProtocol proto = new NdefPushProtocol(msg, NdefPushProtocol.ACTION_IMMEDIATE);
- byte[] buffer = proto.toByteArray();
- int offset = 0;
- int remoteMiu;
- try {
- remoteMiu = sock.getRemoteMiu();
- if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message");
- //@Paul: 将当前信息完整的发送出去,一直到完成
- while (offset < buffer.length) {
- int length = Math.min(buffer.length - offset, remoteMiu);
- byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length);
- if (DBG) Log.d(TAG, "about to send a " + length + " byte packet");
- sock.send(tmpBuffer);
- offset += length;
- }
- return true;
- }
- ...
- return false;
- }
当上述操作都完成后,表示send已经完成,自然就需要通知上层,你需要做的操作已经做完,如果后续有操作,请另行指示。
通知上层的方式,就是用Message的方式执行, 系统会给当前的进程的handler发送MSG_SEND_COMPLETE消息,收到此消息后,进入处理函数,主要内容如下:
- {
- ...
- //@Paul: 又是Listener,最终又到SendUI
- mEventListener.onP2pSendComplete();
- //@Paul:如果有定义callback,则执行callback中定义的onNdefPushComplete
- if (mCallbackNdef != null) {
- try {
- mCallbackNdef.onNdefPushComplete();
- } catch (Exception e) {
- Log.e(TAG, "Failed NDEF completed callback: " + e.getMessage());
- }
- }
- }
Listerner的处理比较简单,就是用户的notify,函数为:
- public void onP2pSendComplete() {
- mNfcService.playSound(NfcService.SOUND_END);
- mVibrator.vibrate(VIBRATION_PATTERN, -1);
- if (mSendUi != null) {
- mSendUi.finish(SendUi.FINISH_SEND_SUCCESS);
- }
- mSending = false;
- mNdefSent = true;
- }
至此,P2P的流程基本已经分析完成。这样基本上可以对NFC中P2P的流程有了基本的了解。
后续会有一个APP的实例说明,参考实例能更加深刻的理解NFC处理流程,加深对该功能的理解。
补充说明:
1. 上述P2P的各个Server是在哪里创建?
Answer:参考P2pLinkManager.java中的构造函数P2pLinkManager():
- mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback);
- mDefaultSnepServer = new SnepServer(mDefaultSnepCallback, defaultMiu, defaultRwSize);
- mHandoverServer = new HandoverServer(HANDOVER_SAP, handoverManager, mHandoverCallback);