[NFC]P2P设备响应流程

来源:互联网 发布:网络遭到劫持 编辑:程序博客网 时间:2024/06/03 21:59

        前文[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()是进行数据链路层的连接,保证底层已经是发现对方并且是可以连接的。


        对于我们来说,需要关注的是llcp链路建立成功后的操作,插播一下P2pLinkManager.java中的一些状态变化函数:

<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_DOWNswitch (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_SENDif (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;...}}}
        上述函数的重点就是:prepareMessageToSend() 以及mEventListener.onP2pSendConfirmationRequested(); 分析完需要确认的流程,那不需要确认的流程也就打通了。

         由于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()。

@Overridepublic boolean onTouch(View v, MotionEvent event) {if (mState != STATE_W4_TOUCH) {return false;}mState = STATE_SENDING;// Ignore future touchesmScreenshotView.setOnTouchListener(null);// Cancel any ongoing animationsmFrameCounterAnimator.cancel();mPreAnimator.cancel();//@Paul: 启动onSendConfirmed()mCallback.onSendConfirmed();return true;}@Overridepublic 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 againscheduleTimeoutLocked(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,并执行此TaskmConnectTask = new ConnectTask();mConnectTask.execute();}}@Overrideprotected 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存在,则可能进行进行HandoverneedsHandover = true;}if (mMessageToSend != null) {//@Paul: 如果要发送的消息不为空,则可能是Ndef消息needsNdef = true;}}// We know either is requested - otherwise this task// wouldn't have been started.if (needsHandover) {//@Paul: 创建HandoverClienthandoverClient = 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: 创建SnepClientsnepClient = 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 ClientnppClient = 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 threadif (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后自动执行,具体的代码如下:

@Overridepublic 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_COMPLETEonSendComplete(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,直接回复RejectmSocket.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),直接回复RejectmSocket.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中规定的数据,则请求对方继续发送,回复Continueif (requestSize > readSize) {if (DBG) Log.d(TAG, "requesting continuation");mSocket.send(SnepMessage.getMessage(fieldContinue).toByteArray());} else {doneReading = true;}//@Paul: 发送完Continue后,执行loop中的receive(),等待后续的package// Remaining fragmentswhile (!doneReading) {try {size = mSocket.receive(partial);if (DBG) Log.d(TAG, "read " + size + " bytes");if (size < 0) {try {//@Paul:接收的数据段错误,直接回复RejectmSocket.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 streamtry {//@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: 按照制定格式创建NdefPushProtocolNdefPushProtocol 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,最终又到SendUImEventListener.onP2pSendComplete();//@Paul:如果有定义callback,则执行callback中定义的onNdefPushCompleteif (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);


                                             
1 0