NFC--P2P模式 java层源码码流程解析。
来源:互联网 发布:网站源码基地 编辑:程序博客网 时间:2024/06/05 15:37
文章只对P2P模式的java层的代码进行了学习,且只在后续一步步深入NFC相关知识后会找时间更新,等学完NFC java层
构架,熟悉C/C++以后,再分析一下native层的构架.
直接从P2P模式的JNI层回调开始分析,(不清楚native层的逻辑,暂且记住回调接口)
当有可被用作P2P模式的NFC设备被发现后,经过底层的一系列处理传输到JNI然后回调到:NativeNfcManager当中的
onLlcpLinkActivated().所有和native层交互的基本都在这个类中实现的。
/**
* Notifies P2P Device detected, to activate LLCP link
*/
private void notifyLlcpLinkActivation(NativeP2pDevice device) {
mListener.onLlcpLinkActivated(device);
}
mListener是DeviceHostListener由NfcService实现的,进入onLlcpLinkActivated()
开始java层的处理,native层感应到了P2P设备,java层需要解析一下,看看是什么格式,做什么操作等。
/**
* Notifies P2P Device detected, to activate LLCP link
* NfcDepEndpoint:代表了一个远程的P2p设备,可以是target也可以是initiator.
* 看native处理完返回来的是什么了。
*/
@Override
public void onLlcpLinkDeactivated(NfcDepEndpoint device) {
sendMessage(NfcService.MSG_LLCP_LINK_DEACTIVATED, device);
}
void sendMessage(int what, Object obj) {
Message msg = mHandler.obtainMessage();
msg.what = what;
msg.obj = obj;
mHandler.sendMessage(msg);
}
对应到mHandler中的MSG_LLCP_LINK_DEACTIVATED.
......
case MSG_LLCP_LINK_ACTIVATION:
......
//主要就是回调下面,底层llcp链路建立完毕的回调。
llcpActivated((NfcDepEndpoint) msg.obj);
break;
......
此处先说一下远程的P2P设备会有两种模式:
一种是Target:当前手机往外发送数据的时候,远程的设备就处于Target模式.
一种是Initiator:当前手机接受数据的时候,远程的设备就会处于Initiator模式.
在上层就体现在NfcDepEndpoint.getMode()的不同,注意区分和传输数据是Socket的区分,这个下面再说.并且他们的基本
流程都一致,差异点是在Target端会先执行connect(),调用到NativeP2pDevice中的native方法doConnect()进行数据链路层的连接
保证底层已经是发现对方并且是可以连接的。
再说两个native方法,位于NativeNfcManager当中,方便我们下面的分析.
@Override
public native boolean doCheckLlcp();检查llcp协议的合法性,如版本是否兼容。
@Override
public native boolean doActivateLlcp();执行链路,就是执行native层的llcp协议的连接
接下来就是llcpActivated:
private boolean llcpActivated(NfcDepEndpoint device) {
Log.d(TAG, "LLCP Activation message");
//当远程p2p是Target端的时候,一定要注意,此时的模式指的是远端的用于和当前设备进行P2P操作的设备。
if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) {
if (device.connect()) {
/* Check LLCP compliance */
if (mDeviceHost.doCheckLlcp()) {
/* Activate LLCP Link */
if (mDeviceHost.doActivateLlcp()) {
synchronized (NfcService.this) {
//当放到集合当中
mObjectMap.put(device.getHandle(), device);
}
//底层按照llcp协议建立完链路连接的时候调用如下方法。
mP2pLinkManager.onLlcpActivated(device.getLlcpVersion());
return true;
} else {device.disconnect();//断开链接}
} else {//断开连接打印相关log}
} else {//不能连接远程的target,然后去执行发送数据.}
//当是远程的P2P是发起者initiator的时候.
} else if (device.getMode() == NfcDepEndpoint.MODE_P2P_INITIATOR) {
//可以看到对比MODE_P2P_TARGET就是少了一个device.connect().
/* Check LLCP compliancy */
if (mDeviceHost.doCheckLlcp()) {
/* Activate LLCP Link */
if (mDeviceHost.doActivateLlcp()) {
if (DBG) Log.d(TAG, "Target Activate LLCP OK");
synchronized (NfcService.this) {
// Register P2P device
mObjectMap.put(device.getHandle(), device);
}
mP2pLinkManager.onLlcpActivated(device.getLlcpVersion());
return true;
}
} else { Log.w(TAG, "checkLlcp failed");}
}
return false;
}
//这个集合中放置了所有的P2p链接的device
final HashMap<Integer, Object> mObjectMap = new HashMap<Integer, Object>();
key = device.getHandle() ,value = NfcDepEndpoint。
另外native层执行完链路连接以后会回调很多状态给上层,在P2pLinkManager类当中有如下三个回调:
public void onLlcpFirstPacketReceived()
public void onLlcpDeactivated()
void onSendComplete(NdefMessage msg, long elapsedRealtime)
无远端是Target还是initaitor,最后都调用了,P2pLinkManager中的onLlcpActivated。至此,native层的llcp链路建立成功并
且也通过了check,我们接下来只需要按照传输协议建立socket传递数据即可!
进入到P2PLinkManager当中
public void onLlcpActivated(byte peerLlcpVersion) {
Log.i(TAG, "LLCP activated");
synchronized (P2pLinkManager.this) {
//一开始有一快是测试nfcpy用的服务器,不太清楚这个,先省略.
......
mLastLlcpActivationTime = SystemClock.elapsedRealtime();
mPeerLlcpVersion = peerLlcpVersion;
//分别对不同的链接状态进行处理,它是在P2pLinkManager的构造中初始化为LINK_STATE_DOWN.
switch (mLinkState) {
//所以刚开始的时候是进入到这里。
case LINK_STATE_DOWN:
//加上了一些判断,保证现在是要进行发送数据的状态。
//NFC的整个P2P的流程中会有很多的类似的状态判断,因为它的链接时刻都可能终端,毕竟要我们
//拿着手机相互靠近,一不小心就离开了
if (!mEventListener.isP2pIdle()
&& mSendState != SEND_STATE_PENDING) {
break;
}
if (DBG) Log.d(TAG, "onP2pInRange()");
//这个方法内就会得到一个当前屏幕截图的bitmap对象,但在这块还不会弹出提示的界面.
mEventListener.onP2pInRange();
//更改链接状态。
mLinkState = LINK_STATE_UP;
//mSendState状态在构造中初始化,为SEND_STATE_NOTHING_TO_SEND.所以走到else
if (mSendState == SEND_STATE_PENDING) {
......
} else {
mSendState = SEND_STATE_NOTHING_TO_SEND;
//按照app传入的各个要发送的数据进行
prepareMessageToSend(true);
//判断是否有数据要往外发,有数据往外发的时候才会进入。
//当设备作为服务端Socket接收数据的时候,判断条件无法通过,接下来也不会进行什么操作,
- //而是会等待对应的SocketServer阻塞处的相应。
if (mMessageToSend != null ||
(mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) {
// We have data to send, connect LLCP services
connectLlcpServices();
//在上面connectLlcpServices还未异步执行完毕的时候,这块肯定就赋值了
if ((mSendFlags & NfcAdapter.FLAG_NDEF_PUSH_NO_CONFIRM) != 0) {
//标记不需要确认直接发送
mSendState = SEND_STATE_SENDING;
} else {
//标记需要在UI上确认的才能P2p
mSendState = SEND_STATE_NEED_CONFIRMATION;
}
}
}
break;
......
}
}
}
我们一步步看onLlcpActivated()中的重要的方法。
mEventListener.onP2pInRange(); mEventListener是P2pEventListener.
//进行P2P链接的时候回调事件用来更新UI界面.
interface P2pEventListener {
......
//Indicates a P2P device is in range.
public void onP2pInRange();
......
}
而P2pEventManager实现了P2pEventListener这个接口,所以进入P2pEventManager中
@Override
public void onP2pInRange() {
mNdefSent = false;
mNdefReceived = false;
mInDebounce = false;
//未锁屏状态时,发送声音,震动.
if (mNfcService.mScreenState == ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED) {
mNfcService.playSound(NfcService.SOUND_START);
mVibrator.vibrate(VIBRATION_PATTERN, -1);
}
if (mSendUi != null) {
//SendUi就是P2P过程中负责和见面交互的类。
mSendUi.takeScreenshot();
}
}
SendUI的实例化在NfcService的启动流程中,实例化P2pEventManager的时候,构造中会调用mSendUi = new SendUi(cont
ext,this),this的类型是SendUi.Callback也可以看到:P2pEventManager implements P2pEventListener, SendUi.Callback。
此处有个重要的点,mCallback定义在SendUi当中。
注意命名此处就是SendUi的完成发送和取消发送的回调
public interface Callback {
public void onSendConfirmed();//确认发送
public void onCanceled();//取消发送
}
mCallback是在SendUi实例化的时候传入的,
public SendUi(Context context, Callback callback) {
mContext = context;
mCallback = callback;
......
}
也就可知实现是在P2pEventManager当中,后面我们用到的时候直接去P2pEventManager中找实现.
接着到SenUi中:
public void takeScreenshot() {
...
//更新当前状态
mState = STATE_W4_SCREENSHOT;
//去异步的执行画界面的操作.
new ScreenshotTask().execute();
}
ScreenshotTask是SendUi的内部类Task当中:
protected Bitmap doInBackground(Void... params) {
return createScreenshot();
}
/**
* 此处旨在理解流程,具体的画图算大小省略,这个方法会返回当前界面内容的一个bitmap对象.
*/
Bitmap createScreenshot() {
......
//根据不屏幕进行适配
final int navBarHeight = ...;
final int navBarHeightLandscape = ...;
final int navBarWidth = ...;
......
Bitmap bitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
......
int newLeft = ...;
int newTop = ...;
int newWidth = ...;
int newHeight = ...;
float smallestWidth = ...;
float smallestWidthDp = ...;
if (bitmap.getWidth() < bitmap.getHeight()) {
newHeight = ...;
} else {
if (smallestWidthDp > 599) {
newHeight = ...;
} else {
newHeight = ...;
newWidth = ...;
}
}
bitmap = Bitmap.createBitmap(bitmap, newLeft, newTop, newWidth, newHeight);
return bitmap;
}
//执行完doInBackground以后到onPostExecute当中.
@Override
protected void onPostExecute(Bitmap result) {
//mState在前面我们设置的是STATE_W4_SCREENSHOT,不同的状态做出不同的处理
if (mState == STATE_W4_SCREENSHOT) {/
//生成的截图设置到mScreenshotBitmap,这个在后面显示用户确认操作时候用.
mScreenshotBitmap = result;
//并且把mState变成将要发送的状态
mState = STATE_W4_PRESEND;
} else if (mState == STATE_W4_SCREENSHOT_THEN_STOP) {
......
} else if (mState == STATE_W4_SCREENSHOT_PRESEND_REQUESTED ||
mState == STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED) {
......
} else {
Log.e(TAG, "Invalid state on screenshot completion: " + Integer.toString(mState));
}
}
到此其实也只是获取到了一个用屏幕的截图绘制的bitmap,把他设置给了mScreenshotBitmap,并不会显示什么界面,然后
回到P2PLinkManager中的onLlcpActivated 接这前面的往下看先是:LinkState = LINK_STATE_UP;而对于mSendState的状态,在
构造中初始化的时候为SEND_STATE_NOTHING_TO_SEND,所以会走到else的分支。
//依据APP设置的信息,准备发送的信息.
//主要是把最终解析出来的数据放到mMessageToSend和mUrisToSend中
//ndef的小数据一般用mMessageToSend存储,如:Contact、Email、等
//picture、video等一般会放在mUrisToSend中
void prepareMessageToSend(boolean generatePlayLink) {
synchronized (P2pLinkManager.this) {
//NdefMessage mMessageToSend;
mMessageToSend = null;
//Uri[] mUrisToSend
mUrisToSend = null;
//由在构造中的时候默认是false,但是在NfcService一系列附加的行为启动完毕后
//调用到P2pLinkManager中enableDisable,会把它赋值为true.
if (!mIsSendEnabled) {
return;
}
//此处有一些判断,如是否检测到如前台程序Beam是否使能。
//前台程序就是指,你有没有打开比如联系人、图库等注意是可分享的activity界面。
//你打开后,选中某个图片,一轻触就开始走流程了,否则的话没啥反应.
if (foregroundUids.isEmpty()) {
Log.e(TAG, "Could not determine foreground UID.");
return;
}
if (isBeamDisabled(foregroundUids.get(0))) {
if (DBG) Log.d(TAG, "Beam is disabled by policy.");
return;
}
...
//mCallbackNdef是IAppCallback接口的实现,通过调用接口setNdefCallback,进行设置的
//一般无论是第三方app还是系统的联系人、图库等,都会设置这个回调
if (mCallbackNdef != null) {
//如果是自己添加的前台进程的话,走下面的流程
if (foregroundUids.contains(mNdefCallbackUid)) {
try {
//createBeamShareData,在framework中有实现,在NfcActivityManager中实现
//此处还需要深入的再看看怎么确定beam的数据的,(??????)
//通过Log发现:
// 图片资源的时候 mMessageToSend = null、
// mUrisToSend = [Landroid.net.Uri;@473547e
// mUserHandle = UserHandle{0}
// mSendFlags = 0
//*******************************
// 联系人资源的时候 mMessageToSend = NdefMessage
// [NdefRecord tnf=2 type=746578742F782D7663617264
// payload=424547494E3A56434152
// 440D0A56455253494F4E3A322E310D0A4E3A3B5A686
// 16F7975616E3B3B3B0D0A464E3A5A68616F7975616E
// 200D0A54454C3B43454C4C3A3133363938353233363
// 9380D0A454E443A56434152440D0A]
// mUrisToSend = null.
// mUserHandle = UserHandle{0}
// mSendFlags = 0
//总的来说是由createBeamShareData内部实现的区分!
BeamShareData shareData = mCallbackNdef.createBeamShareData(mPeerLlcpVersion);
mMessageToSend = shareData.ndefMessage;
mUrisToSend = shareData.uris;
mUserHandle = shareData.userHandle;
mSendFlags = shareData.flags;
return;
} catch (Exception e) {...}
} else {
//当如图库退出前台的时候,可能它的回调并没有清除。
//比如你想作为Socket接收端,就会到当前判断不会去专门打开指定的应用界面去发送东西
if (DBG) Log.d(TAG, "Last registered callback is not running in the foreground.");
}
}
//当没有设置回调,就调用系统默认的,将当前Pkg的信息打包发送到上层。
List<Integer> foregroundPids =
mForegroundUtils.getForegroundPids(foregroundUids.get(0));
if (foregroundPids.isEmpty()) {
Log.e(TAG, "Could not determine foreground PID.");
return;
}
//找到正在运行的pkg
String pkgName = null;
ActivityManager manager =
(ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> appList = manager.getRunningAppProcesses();
for (RunningAppProcessInfo info : appList) {
for (Integer pid : foregroundPids) {
if (pid.intValue() == info.pid) {
if (DBG) Log.d(TAG, "PID:" + info.pid + " Name:" + info.processName);
pkgName = info.processName;
break;
}
}
...
}
//
if (pkgName != null) {
if (!generatePlayLink || beamDefaultDisabled(pkgName)) {
if (DBG) Log.d(TAG, "Disabling default Beam behavior");
mMessageToSend = null;
mUrisToSend = null;
} else {
mMessageToSend = createDefaultNdef(pkgName);
mUrisToSend = null;
mSendFlags = 0;
}
}
//作为接收端的时候此处就会为null,然后返回不作处理
if (DBG) Log.d(TAG, "mMessageToSend = " + mMessageToSend);
if (DBG) Log.d(TAG, "mUrisToSend = " + mUrisToSend);
}
}
再回到P2pLinkManager中的onLlcpActivated当中,继续往下看,接下来就是connectLlcpServices要连接llcp中相关service
了.此时就有个问题是什么时候启动的server那?
是在NfcService启动的流程中,按照协议的规定对相应的Socket进行实例化,
public P2pLinkManager(Context context, HandoverDataParser handoverDataParser, int defaultMiu,
int defaultRwSize) {
......
mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback);
mDefaultSnepServer = new SnepServer(mDefaultSnepCallback, defaultMiu, defaultRwSize);
mHandoverServer = new HandoverServer(context, HANDOVER_SAP, handoverDataParser, mHandoverCallback);
......
}
在每个ServerSocket当中实例化完毕后会监听Socket的链接,此处以SnepServer为例简单展示部分代码
首先在P2pLinkManager中调用mDefaultSnepServer.start();
public void start() {
synchronized (SnepServer.this) {
if (DBG) Log.d(TAG, "start, thread = " + mServerThread);
if (mServerThread == null) {
if (DBG) Log.d(TAG, "starting new server thread");
mServerThread = new ServerThread();
mServerThread.start();
mServerRunning = true;
}
}
}
在ServerThread的run方法中
@Override
public void run() {
......
LlcpServerSocket mServerSocket;
while (threadRunning) {
synchronized (SnepServer.this) {
mServerSocket = NfcService.getInstance().createLlcpServerSocket(mServiceSap,
mServiceName, mMiu, mRwSize, 1024);
}
......
//下面就会阻塞,等待Client端发送数据,此处一定注意和NFC的target/Initiator没啥关系
//这块说的Client、Server是指建立的socket。当你想往外发数据是,会弹出确认提示,谁主动点击了
//屏幕,执行了确定的动作,那么就是发送端,发送端默认是Client端,因为接收端默认已经启动了Server端,
//等待对方发起连接.
LlcpSocket communicationSocket = serverSocket.accept();
}
}
其它的serverSocket启动模式类似。简单介绍完server端socket的启动,继续回去跟踪connectLlcpServices
void connectLlcpServices() {
synchronized (P2pLinkManager.this) {
//调用ConnectTask异步的实现。
mConnectTask = new ConnectTask();
mConnectTask.execute();
}
}
根据传输的格式来,来实例化对应的链接如:HandoverClient、SnepClient、NdefPushClient连接成功后返回true
final class ConnectTask extends AsyncTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... params){
//初始化一些参数,用来标记使用那种协议来解析数据.
boolean needsHandover = false;
boolean needsNdef = false;
boolean success = false;
//下面就是依次初始化这几个client,对应的server端就是我们前面说的
//这些客户端的socket创建也是按照llcp协议的要求来设置一些属性如访问节点等.
HandoverClient handoverClient = null;
SnepClient snepClient = null;
NdefPushClient nppClient = null;
synchronized(P2pLinkManager.this) {
//进入到此处的时候就会用bt进行传输
if (mUrisToSend != null) {
needsHandover = true;
}
//日历、联系人这一类的小数据
if (mMessageToSend != null) {
needsNdef = true;
}
}
if (needsHandover) {
handoverClient = new HandoverClient();
try {
//调用connect,最终会调用到:
// LlcpSocket当中connectToService(HandoverServer.HANDOVER_SERVICE_NAME);
//LlcpSocket是由NativeLlcpSocket实现,代表一个llcp的面向连接中的客户端。
//去连接它对应的服务端的Socket,此处疑问(???)链接的时候服务端不会响应,等到往服务
//端发数据就会响应了.
//service.createLlcpSocket(0, MIU, 1, 1024);
handoverClient.connect();
success = true; // Regardless of NDEF result
} catch (IOException e) {
handoverClient = null;
}
}
//当需要用ndef发送的时候
if (needsNdef || (needsHandover && handoverClient == null))
{
//认证测试工具DTA,当处于DTA模式下的时候的处理
if(NfcService.sIsDtaMode) {
......
}else{
//正常模式下,实例化SnepClient。
snepClient = new SnepClient();
}
try
{
if(NfcService.sIsDtaMode) {
......
}
else
{
//也是调用连接,连接到对应的服务端主要分三步
// NfcService.getInstance().createLlcpSocket(0, mMiu, mRwSize, 1024)
// socket.connectToService(mServiceName)/socket.connectToSap(mPort);
// 依据服务名或者Port连接
// new SnepMessenger(),用于接收或发送SNEP消息
// 主要函数:put()/get()
snepClient.connect();
}
success = true;
mDtaSnepClient = null;
} catch (IOException e) {...}
//可以看到当snepClient不能成功的时候才会用NdefPushClient npp.
//这个是早期没引入P2P模式的时候的选择.
if (!success) {
nppClient = new NdefPushClient();
try {
// 进行连接操作,主要分两步
// 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();
}
if(mDtaSnepClient != null) {
mDtaSnepClient.close();
}
return false;
} else {
......
mHandoverClient = handoverClient;
mSnepClient = snepClient;
mNdefPushClient = nppClient;
return success;
}
}
}
//执行完doInBackground之后,走到这里.
@Override
protected void onPostExecute(Boolean result) {
...
//如果成功连接到服务端的Socket.
if (result) {
onLlcpServicesConnected();
} else {
Log.e(TAG, "Could not connect required NFC transports");
}
}
}
至此socket的客户端启动完毕,并与server端建立起了链接,但是还未往外发送数据,根据mSendFlags设计的标记,来决
定是否需要用户确认,前面分析createBeamShareData的时候发现默认好像都是0.
void onLlcpServicesConnected() {
synchronized (P2pLinkManager.this) {
//先检查连接状态
if (mLinkState != LINK_STATE_UP) {
return;
}
mLlcpServicesConnected = true;
//mSendState在前面的onLlcpActivated,异步设置了
if (mSendState == SEND_STATE_NEED_CONFIRMATION) {
//需要用户手动确认的,
if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()");
mEventListener.onP2pSendConfirmationRequested();
} else if (mSendState == SEND_STATE_SENDING) {
//不需要确认直接发送
mEventListener.onP2pResumeSend();
sendNdefMessage();
} else {
// Either nothing to send or canceled/complete, ignore
}
}
}
注意此时还未显示出来用户提示的界面,(缩小屏幕,提示你轻触使用beam进行传送)。
下面我们分析需要确认的情况,因为需要确认的情况包含不需要确认的情况,只不过多了个确认的步骤罢了。
又到P2pEventManager中的onP2pSendConfirmationRequested中
@Override
public void onP2pSendConfirmationRequested() {
mNfcService.playSound(NfcService.SOUND_START);
mVibrator.vibrate(VIBRATION_PATTERN, -1);
//SendUi我们前面已经说过.
if (mSendUi != null) {
//注意传入的参数是false
mSendUi.showPreSend(false);
} else {...}
}
展示准备send的画面
public void showPreSend(boolean promptToNfcTap) {
......
//mScreenshotLayout是个View对象用来承载预先显示的这个画面,里面会放入一系列的组件
mScreenshotLayout.setOnTouchListener(this);
//mScreenshotBitmap这就是前面在范围内的时候onP2pInRange截图生成的bitmap,
mScreenshotView.setImageBitmap(mScreenshotBitmap);
......
//由于promptToNfcTap是false,所以STATE_W4_TOUCH.
//接下来你点击屏幕的时候(中间没有异常情况)
mState = promptToNfcTap ? STATE_W4_NFC_TAP : STATE_W4_TOUCH;
}
到现在为止才会显示出来整体的用户提示界面如下图,
原图:打算往外发的
检测到远程的P2P设备时候:
点击屏幕以后:
之后按照提示,如果你此时两个P2P设备已经远离,再把设备靠近即可完成发送。如果不清楚这些流程,需要自己找手机试试。
一旦用户点中间的界面,那么就会触发onTouch事件。
@Override
public boolean onTouch(View v, MotionEvent event) {
//由于第一次到这执行完showPreSend后mState = STATE_W4_TOUCH。
if (mState != STATE_W4_TOUCH) {
return false;
}
......
//调用onSendConfirmed去弹出确认提示框
mCallback.onSendConfirmed();
return true;
}
此时是在SendUi当中,前面我们也说了它内部的callback的实现是在P2pEventManager如下:
@Override
public void onSendConfirmed() {
if (!mSending) {
if (mSendUi != null) {
//调用下面这个界面变化成指定的传输提示动画。 图!
mSendUi.showStartSend();
}
//此时的mCallback是在P2pEventListener声明,注意和SendUI中的区别.
mCallback.onP2pSendConfirmed();
}
mSending = true;//标记处于发送当中
}
@Override
public void onCanceled() {
mSendUi.finish(SendUi.FINISH_SCALE_UP);
mCallback.onP2pCanceled();
}
此处的mCallback定义在P2pEventListener
public interface Callback {
public void onP2pSendConfirmed();
public void onP2pCanceled();
}
实现在P2pLinkManager implements P2pEventListener.Callback
@Override
public void onP2pSendConfirmed() {
onP2pSendConfirmed(true);
}
private void onP2pSendConfirmed(boolean requireConfirmation) {
if (DBG) Log.d(TAG, "onP2pSendConfirmed()");
synchronized (this) {
//在一开始的onLlcpActivated当中被
//mSendState = SEND_STATE_NEED_CONFIRMATION;
//mSendState = SEND_STATE_SENDING; 这两种之一
//mLinkState也在前面的onLlcpActivated当中被被设置为LINK_STATE_UP(中间没异常情况时,到这不会改变状态的),
if (mLinkState == LINK_STATE_DOWN || (requireConfirmation
&& mSendState != SEND_STATE_NEED_CONFIRMATION)) {
return;
}
//发送状态置为正在发送.
mSendState = SEND_STATE_SENDING;
//通过检查链接状态,如果llcp已经连接上了,则进行Ndef消息的发送
if (mLinkState == LINK_STATE_UP) {
if (mLlcpServicesConnected) {
sendNdefMessage();
} // else, will send messages when link comes up
} else if (mLinkState == LINK_STATE_DEBOUNCE) {
......
}
}
}
void sendNdefMessage() {
synchronized (this) {
//下面是假如正在执行一个发送任务,把它取消掉.
//两个handover,第二个到来的时候会提示正忙.
//还有个问题,handover协议的时候一定要用bt吗(????)
cancelSendNdefMessage();
mSendTask = new SendTask();
mSendTask.execute();
}
}
最终是通过SendTask异步执行的,进入到SendTask类当中.
@Override
public Void doInBackground(Void... args){
NdefMessage m;
Uri[] uris;
UserHandle userHandle;
boolean result = false;
synchronized (P2pLinkManager.this) {
if (mLinkState != LINK_STATE_UP || mSendState != SEND_STATE_SENDING) {
return null;
}
//依次取得P2pEventManager设置好的变量
m = mMessageToSend;
uris = mUrisToSend;
userHandle = mUserHandle;
snepClient = mSnepClient;
handoverClient = mHandoverClient;
nppClient = mNdefPushClient;
}
long time = SystemClock.elapsedRealtime();
//uri有赋值的话,就直接进入Handover的处理,还是感觉uris就代表大数据的handover
//需要通过Bt来进行传输的
if (uris != null) {
if (DBG) Log.d(TAG, "Trying handover request");
try {
int handoverResult = doHandover(uris, userHandle);
switch (handoverResult) {
case HANDOVER_SUCCESS:
result = true;
break;
...
}
} catch (IOException e) {
result = false;
}
}
//result为false且前面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;
......
}
} catch (IOException e) {...}
}
//若不支持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) {
//发送成功后的回调,最终发送MSG_SEND_COMPLETE消息.
onSendComplete(m, time);
}
return null;
}
接下来依次介绍doHandover(uris)、doSnepProtocol(m)、nppClient.push(m)、onSendComplete(m, time)。
网上资料对doHandover的总结:
doHandover():在AOSP中主要是生成BT 的NDEF Message,主要流程是先产生NDEF Record,然后将Record组成MSG,然
后调用socket的send(),等send() 调用完成,在while循环中等待Handover的Response。如果对方不支持Handover时,就会尝试
调用SNEP的处理函数,处理完成之后,开始解析Response数据,然后将解析的数据存放在Intent中,然后发送广播消息,启动
对事件关心的BT activity。
注意doHandover是位于SendTask类内的,详细的实现没有看(???)因为没看懂协议,这边肯定也是按照协议写的.
int doHandover(Uri[] uris, UserHandle userHandle) throws IOException {
NdefMessage response = null;
//Handover就是beam的模式,
BeamManager beamManager = BeamManager.getInstance();
//如果有一个正在进行的handover,那么会提示正忙
if (beamManager.isBeamInProgress()) {
return HANDOVER_BUSY;
}
//创建handover支持的数据格式
NdefMessage request = mHandoverDataParser.createHandoverRequestMessage();
if (request != null) {
if (handoverClient != null) {
//下面内部会调用到LlcpSocket的sock.send(tmpBuffer).
//它会作为socket的client端,就是客户端。(既然两个手机都开启了服务端,不会冲突到本地吗???)
//最终会调用到native的对应的方法.
response = handoverClient.sendHandoverRequest(request);
}
//当是null的时候远程不支持handover,使用snepClient进行发送
//snepClient是android4.1以后加入的。会发送一个get请求???不是put
if (response == null && snepClient != null) {
// Remote device may not support handover service,
SnepMessage snepResponse = snepClient.get(request);
response = snepResponse.getNdefMessage();
}
if (response == null) {
return HANDOVER_UNSUPPORTED;
}
} else {
return HANDOVER_UNSUPPORTED;
}
//不要以为到这才开始,这只是判断,是否有个正在进行的,方法如下
if (!beamManager.startBeamSend(mContext,
mHandoverDataParser.getOutgoingHandoverData(response), uris, userHandle)) {
return HANDOVER_BUSY;
}
return HANDOVER_SUCCESS;
}
public boolean startBeamSend(Context context,
HandoverDataParser.BluetoothHandoverData outgoingHandoverData,
Uri[] uris, UserHandle userHandle) {
synchronized (mLock) {
if (mBeamInProgress) {
return false;
} else {
mBeamInProgress = true;
}
}
接下来就是SNEP的分析了,也是按照SNEP协议来进行发送数据
int doSnepProtocol(NdefMessage msg) throws IOException {
if (msg != null) {
snepClient.put(msg);
return SNEP_SUCCESS;
} else {
return SNEP_FAILURE;
}
}
SnepClient类中的put方法,
public void put(NdefMessage msg) throws IOException {
//一些个判断.
SnepMessenger messenger;
synchronized (this) {
if (mState != CONNECTED) {
throw new IOException("Socket not connected.");
}
messenger = mMessenger;
}
//重要的是SnepMessenger的sendMessage和getMessage
synchronized (mTransmissionLock) {
try {
//下面两个方法就是Android按照SNEP协议进行编写执行的。
//通过SnepMessenger进行发送,SnepMessenger实现了协议.
messenger.sendMessage(SnepMessage.getPutRequest(msg));
messenger.getMessage();
} catch (SnepException e) {
throw new IOException(e);
}
}
}
客户端生成一个put请求的SnepMessage信息。
public static SnepMessage getPutRequest(NdefMessage ndef) {
return new SnepMessage(VERSION, REQUEST_PUT, ndef.toByteArray().length, 0, ndef);
}
接下来就是真正的按照协议写的地方
public void sendMessage(SnepMessage msg) throws IOException {
byte[] buffer = msg.toByteArray();
...
//取当前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");
//将消息透过socket发送出去
//final LlcpSocket mSocket,实例化SnepMessage的时候传入的,最初是在SnepCLient实例化的时候
//调用到connect的时候,调用如下产生
//socket = NfcService.getInstance().createLlcpSocket(0, mMiu, mRwSize, 1024);
mSocket.send(tmpBuffer);
//若数据包不用切割(一个数据包能发完),则直接返回
if (length == buffer.length) {
return;
}
// Look for Continue or Reject from peer.
//按照Snep协议,若切片后发送,则需要等待对方的回复后再决定下一步行动
int offset = length;
byte[] responseBytes = new byte[HEADER_LENGTH];
//调用socket去接受回复。
mSocket.receive(responseBytes);
SnepMessage snepResponse;
try {
snepResponse = SnepMessage.fromByteArray(responseBytes);
} catch (FormatException e) {
throw new IOException("Invalid SNEP message", e);
}
if (DBG) Log.d(TAG, "Got response from first fragment: " + snepResponse.getField());
//如果对方的回复不是continue,在返回错误
if (snepResponse.getField() != remoteContinue) {
throw new IOException("Invalid response from server (" +
snepResponse.getField() + ")");
}
//dta模式
if(NfcService.sIsDtaMode) {
......
}
// 符合要求后,将剩余的数据发送完成
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");
//一直发送!!!!!!!感觉socket这可以好好看看啊
mSocket.send(tmpBuffer);
if(NfcService.sIsDtaMode) {
......
}
offset += length;
}
}
根据协议,发送完毕后需要等待对方的response消息,就进入了getMessage()阶段,代码分析如下:
public SnepMessage getMessage() throws IOException, SnepException {
......
//等待对方的回复消息.
size = mSocket.receive(partial);
if (DBG) Log.d(TAG, "read " + size + " bytes");
if (size < 0) {
try {
//接收的数据大小小于0,直接回复Reject
mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
} catch (IOException e) {}
throw new IOException("Error reading SNEP message.");
} else if (size < HEADER_LENGTH) {
//根据协议,接收的数据小于Header.size(因为Snep数据必须包含Header),直接回复Reject.
try {
if((NfcService.sIsDtaMode)&&(mIsClient)){
if (DBG) Log.d(TAG, "Invalid header length");
close();
} else {
mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
}
mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
} catch (IOException e) {
// Ignore
}
throw new IOException("Invalid fragment from sender.");
} else {
//更新buffer值
readSize = size - HEADER_LENGTH;
buffer.write(partial, 0, size);
}
DataInputStream dataIn = new DataInputStream(new ByteArrayInputStream(partial));
requestVersion = dataIn.readByte();
requestField = dataIn.readByte();
requestLength = dataIn.readInt();
if (DBG) Log.d(TAG, "read " + readSize + " of " + requestLength);
//Header中携带的Version不匹配的时候,直接接收完成,退出。
if (((requestVersion & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) {
if(NfcService.sIsDtaMode) {
sendMessage(SnepMessage.getMessage(SnepMessage.RESPONSE_UNSUPPORTED_VERSION));
close();
} else {
......
}
}
if(NfcService.sIsDtaMode) {
//dta模式的处理
}
//如果接收的数据小于Header中规定的数据,则请求对方继续发送,回复Continue,也是按照协议来的
if (requestLength > readSize) {
if (DBG) Log.d(TAG, "requesting continuation");
mSocket.send(SnepMessage.getMessage(fieldContinue).toByteArray());
} else {
doneReading = true;
}
// Remaining fragments
//让他continue以后,才能接受生剩余的
while (!doneReading) {
try {
size = mSocket.receive(partial);
if (DBG) Log.d(TAG, "read " + size + " bytes");
if (size < 0) {
try {
mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
} catch (IOException e) {
// Ignore
}
throw new IOException();
} else {
//正确接收数据,不断更新收到的数据
readSize += size;
buffer.write(partial, 0, size);
if (readSize == requestLength) {
doneReading = true;
}
}
} catch (IOException e) {
......
}
}
// Build NDEF message set from the stream
try {
//接收的肯定是二进制的数据流,将接收的数据转换成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) {
LlcpSocket sock = null;
synchronized (mLock) {
......
sock = mSocket;
}
//按照制定格式创建NdefPushProtoco,没看过这个协议.
NdefPushProtocol proto = new NdefPushProtocol(msg, NdefPushProtocol.ACTION_IMMEDIATE);
......
try {
remoteMiu = sock.getRemoteMiu();
if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message");
// 将当前信息完整的发送出去,一直到完成.
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;
} catch (IOException e) {
......
} finally {
......
}
return false;
}
上述操作都完成后,表示send数据已经完成,需要通知一下用户界面,我们继续返回P2pLinkManager中的SendTask当
中,往下走是调用onSendComplete去进一步处理.
void onSendComplete(NdefMessage msg, long elapsedRealtime) {
// Make callbacks on UI thread
mHandler.sendEmptyMessage(MSG_SEND_COMPLETE);
}
走到mHandler中的MSG_SEND_COMPLETE
case MSG_SEND_COMPLETE:
synchronized (P2pLinkManager.this) {
......
mSendState = SEND_STATE_COMPLETE;
mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT);
if (DBG) Log.d(TAG, "onP2pSendComplete()");
//回调发送完成的接口
mEventListener.onP2pSendComplete();
if (mCallbackNdef != null) {
try {
//发送完成通知用户界面.
mCallbackNdef.onNdefPushComplete(mPeerLlcpVersion);
} catch (Exception e) {
Log.e(TAG, "Failed NDEF completed callback: " + e.getMessage());
}
}
}
break;
由前面可知在mEventListener是现实P2pEventManager当中
@Override
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;
}
接下来是mCallbackNdef前面也说了在framework中NfcActivityManager extends IAppCallback.Stub实现了这个callback
@Override
public void onNdefPushComplete(byte peerLlcpVersion) {
NfcAdapter.OnNdefPushCompleteCallback callback;
synchronized (NfcActivityManager.this) {
NfcActivityState state = findResumedActivityState();
if (state == null) return;
callback = state.onNdefPushCompleteCallback;
}
NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
// Make callback without lock
if (callback != null) {
//最终就调用到了这里(???)
callback.onNdefPushComplete(event);
}
}
待整理问题:
1)、文章中多的(???)的地方
2)、需要配合llcp协议看看那几个socket的声明和实现。
后面对NFC相关知识越来越了解的话,会持续更新这个。
文章参考:
http://blog.csdn.net/xuwen0306/article/details/44724859
0 0
- NFC--P2P模式 java层源码码流程解析。
- NFC--Tag读写模式java层源码分析
- android nfc P2P模式
- 关于NFC P2P模式
- [NFC]P2P设备响应流程
- [NFC]P2P设备响应流程
- Nfc--Handover相关java层源码分析
- Android NFC P2P学习1 - API层
- Android NFC P2P学习2 - Service层
- NFC源码分析之初始化流程
- [Android] Handler源码解析 (Java层)
- Android nfc读卡模式流程
- Android nfc读卡模式流程
- Android NFC 技术解析,附 Demo 源码
- STKFramework层源码解析
- Hadoop源码解析-作业执行流程-本地模式
- Hadoop源码解析-作业执行流程-集群模式
- Volley源码流程解析
- Android——自由拖动并显示文字的悬浮框实现
- Linux下为 IDEA 安装 Tomcat
- sys/socket.h
- PAT 乙等 1015 C语言
- SpringMVC错误③
- NFC--P2P模式 java层源码码流程解析。
- L1-017. 到底有多二
- Chapter 1 构造过程抽象
- (一) 从Angular1到Angular2的杂谈
- HDU
- 叠框
- Eclipse在导入项目时显示 “Invalid Project Description”时的处理方法
- Java 中参数传递的问题
- PuTsangTo