NFC framework
NFC framework introduce
1
NFC简介
对于NFC,是google在android4.0上推出来的,简单介绍下。近场通讯(NFC)是一系列短距离无线技术,一般需要4cm或者更短去初始化连接。近场通讯(NFC)允许你在NFC
tag和Android设备或者两个Android设备间共享小负载数据。优酷上有其应用的视频:http://v.youku.com/v_show/id_XMjM3ODE5ODMy.html。
http://v.youku.com/v_show/id_XMzM1MTUyMzI4.html
总体框架
对于NFC框架的设计,同样是android的标准的c/s架构,其框架图如下:
n 客户端:android提供了两个API包给apk,分别是android.nfc.tech、android.nfc,实现了NFC的应用接口,代码路径frameworks/base/core/java/android/nfc/tech、frameworks/base/core/java/android/nfc。
n 服务端:packages/apps/Nfc是一个类似电话本的应用,这个程序在启动后自动运行,并一直运行,作为NFC的服务进程存在,是NFC的核心。
在这个程序代码中,有个JNI库,供NfcService调用,代码路径是packages/apps/Nfc/jni/
n 库文件:代码路径是external/libnfc-nxp,用C编写的库文件,有两个库,分别是libnfc.so和libnfc_ndef.so。libnfc.so是一个主要的库,实现了NFC
stack的大部分功能,主要提供NFC的服务进程调用。libnfc_ndef是一个很小的库,主要是实现NDEF消息的解析,供framework调用
2.1 总体类图关系
2.2 数据分类
NFC按照发展,分为两种,NFC
basics和Advanced
NFC。从字面上理解,第一种是最先设计的,第二种是在原来基础上扩展的。
2.2.1 NFC
basics
是一种点对点(P2P)数据交换的功能,传送的数据格式是NDEF,是Nfc
Data Exchange
Format的缩写,这个数据格式用于设备之间点对点数据的交换,例如网页地址、联系人、邮件、图片等。对于除图片以外的数据,数据量比较小,直接封装在类NdefMessage中,通过NFC将NdefMessage类型数据发送到另一台设备,而对于图片这样数据量比较大的数据,需要构建一个标准的NdefMessage数据,发送给另外一台设备,等有回应之后,再通过蓝牙传送数据。
NdefMessage类是用于数据的封装,其包含有一个或多个NdefRecord类,NdefRecord才是存储数据的实体,将联系人、邮件、网页地址等转换才byte类型的数据存储在NdefRecord当中,并且包含了数据类型。举个例子吧:
NdefRecord NdefRecord.TNF_ABSOLUTE_URI "http://developer.android.com/index.html".getBytes(Charset.forName("US-ASCII")), new new |
以上是用NdefMessage对一个NdefRecord数据进行分装。
为了更好理解数据的传送方式,需要更细的分为三种:
n 在一个Apk中,用NdefMessage封装Apk的数据,在设置NdefRecord的数据类型,然后发送给其他设备。在接收设备的同样的APK的AndroidManifest文件中设置接收数据的类型,这样通过Intent消息就能找到对应的Activity启动。
n 直接将当前运行(除home程序外)的Apk的包名封装到NdefMessage中,发送给其他设备。接收设备收到NdefMessage数据,转换才成包名,根据包名构造Intent,启动指定的Activity。如果包名不存在,那么会启动google
play去下载安装该Apk。
n 图片为数据量比较大的数据。需要封装一个标准的NdefMessage数据发送给其他设备,当有回应的时候,在将图片数据通过蓝牙发送给其他设备。
按照上面的分析,还可以将数据传送,分为小数据量的传送和大数据量的传送。小数据量是指联系人、邮件、网页地址、包名等,而大数据量是指图片等,需要通过蓝牙传送的。那么为什么NFC的功能还要蓝牙传送呢?原因是NFC的设计本来就是为了传送小的数据量,同我们通过NFC启动蓝牙传图片,更方便的不需要手动进行蓝牙的匹配,只需要将手机贴在一起就可以完成了蓝牙的匹配动作。
2.2.2
Advanced NFC
对于该类型的NFC,也是在点对点数据交换功能上的一个扩充,我们日常接触的有公交卡、饭卡,手机设备可以通过NFC功能读取该卡内的数据,也有支持NFC设备往这类卡里面写数据。所以,我们将这些卡类称为Tag。
需要直接通过Byte格式进行数据封装,对TAG数据进行读写。市面上有很多的卡,估计没个城市用的公交卡都不一样,就是使用的标准不一样,所以在android.nfc.tech 包下支持了多种technologies,如下图:
当tag设备与手机足够近的时候,手机设备首先收到了Tag信息,里面包含了当前Tag设备所支持的technology,然后将Tag信息发送到指定的Activity中。在Activity中,将读取Tag里面的数据,构造相应的technology,然后再以该technology的标准,对tag设备进行读写。
初始化流程
3.1 时序图
3.2 代码分析
初始化分两部分,第一是服务端的初始化,并将服务添加到ServiceManager中,第二是初始化NFC适配器NfcAdapter。
3.2.1 Server端初始化
NFC的服务端代码在packages/apps/Nfc中,并且还包含了JNI代码,前面也介绍过,NFC的服务端是一个应用程序,跟随系统启动并一直存在的一个服务进程。
NfcService继承于Application,当程序启动的时候,调用onCreate()方法,代码如下:
public super.onCreate(); mNfcTagService mNfcAdapter mExtrasService …… mDeviceHost mNfcDispatcher mP2pLinkManager …… ServiceManager.addService(SERVICE_NAME, ……. new } |
TagService是NfcService的内部类,并继承于INfcTag.stub,因此客户端可以通过Binder通信获取到TagService的实例mNfcTagService。其主要的功能是完成tag的读写。
NfcAdapterService也是NfcService的内部类,并继承于INfcAdapter.stub,同样客户端可以通过Binder通信获取到NfcAdapterService的实例mNfcAdapter。NfcAdapterService也是暴露给客户端的主要接口,主要完成对NFC的使能初始化,扫面读写tag,派发tag消息等。
NativeNfcManager类就像其名字一样,主要负责native
JNI的管理。
NfcDispatcher主要负责tag消息处理,并派发Intent消息,启动Activity。
3.2.2 NfcAdapter客户端初始化
在ContextImpl类中,有一个静态模块,在这里创建了NfcManager的实例,并注册到服务中,代码如下:
Static{ registerService(NFC_SERVICE, public return }}); } |
在NfcManager的构造函数中,调用了NfcAdapter.getNfcAdapter(context),创建NFC
Adapter。
public …… sService …… try sTagService } } …… NfcAdapter if adapter sNfcAdapters.put(context, } return } private IBinder if return } return } |
我们看看getServiceInterface()方法,在3.2.1我们也看到了,调用ServiceManager.addService()将NfcAdapterService的实例添加到系统的服务列表中,这里我们调用了ServiceManager.getService(“nfc”)获取到了服务端的NfcAdapterService对象的实例。
在NfcAdapterService类中提供了getNfcTagInterface接口,用于获取远程服务端的TagService对象的实例。
如果一切正常,那么将创建NfcAdapter的实例,在其构造函数中,创建了NfcActivityManager的实例。
启动NFC流程
4.1 时序图
4.2 代码分析
如果android设备有NFC硬件支持,那么将在设置应用的出现“无线和网络à更多àNFC”选项,点击将使能NFC功能。其实就是调用了NfcAdapter.enable()方法,代码如下:
public try return sService.enable(); } } } |
在NfcAdapterService.enable()方法中,创建了一个Task任务来完成使能工作,代码如下:
public …… new return } |
EnableDisableTask是NfcService的一个内部类,继承于AsyncTask,一个异步的任务线程,实际工作的doInBackground方法中。根据了TASK_ENABLE参数,选择调用到了EnableDisableTask.
enableInternal()完成NFC功能的使能,代码如下:
boolean …… if return } synchronized(NfcService.this) mObjectMap.clear(); mP2pLinkManager.enableDisable(mIsNdefPushEnabled, updateState(NfcAdapter.STATE_ON); } initSoundPool(); applyRouting(true); return } |
mDeviceHost其实是NativeNfcManager的实例,其继承于DeviceHost。调用了其initialize()方法,接着调用JNI方法doInitialize(),完成对NFC硬件的初始化。
硬件初始化完成之后,就需要初始化P2pLiskManager。P2p就是点对点传送的意思。这里初始化,需要创建读取数据线程,以及socket的创建。
下面看看P2pLinkManager.enableDisable(),启动P2p功能:
public if mDefaultSnepServer.start(); mNdefPushServer.start(); …… } } |
这里启动了两个服务,分别是SnepServer和NdefPushServer,但是在实际使用过程中优先使用SnepServer服务,只有当其使用失败的时候,才会用到NdefPushServer服务。所以,我们这里就看SnepServer就可以了,NdefPushServer也比较相似。SnepServer.start():
public mServerThread mServerThread.start(); mServerRunning } |
代码非常的简单,ServerThread是继承与Thread的,且是SnepServer的内部类。看看其run()方法,为了方便理解,剪切了不少代码:
public while if try mServerSocket mServiceName, while LlcpServerSocket synchronized serverSocket } LlcpSocket if int new } } } } |
这里主要是完成了Socket的创建,这个Socket是用于接收其他设备发送过来的数据的,ConnectionThread也是SnepServer的内部类,继承与Thread,看看其run()函数:
public try while if break; } synchronized running } } } } |
这个是一个连接线程,与客户端的Socket连接,如果有接收到Socket发送的数据的时候,就用handlerRequest处理数据。
以上已经完成了P2p设备的初始化,下面就需要去扫描查询tag及P2p设备。
本调用applyRouting(true)开始扫描tag及P2p消息。
void …… try …… // if mEeRoutingState if mDeviceHost.doSelectSecureElement(); } } if mDeviceHost.doDeselectSecureElement(); } } // if if mDeviceHost.enableDiscovery(); } } if mDeviceHost.disableDiscovery(); } } } watchDog.cancel(); } } } |
这里我们关注NativeNfcManager.enableDiscovery()方法,最终调用到JNI中,在JNI中注册了回调函数,当扫描到tag或p2p后,将回调Java层函数。如果发现Tag设备,将会回调NativeManager.notifyNdefMessageListeners()方法,如果发现P2p设备,将会回调NativeManager.notifyLlcpLinkActivation()方法。JNI代码我们就不分析了,我们就主要关注这两个方法就可以了:
private mListener.onRemoteEndpointDiscovered(tag); } private mListener.onLlcpLinkActivated(device); } |
5
NDEF数据读写流程
5.1 小数据量的传送
小数据量的传送,指的是传送联系人、网页地址、邮件、包名等,数据量比较小,可以直接用。
5.1.1读写流程图
5.1.2 数据写流程
5.1.2.1时序图
5.1.2.2代码分析
NfcAdapter提供了两个接口给应用程序设置推送的数据:
public public public public } |
第一种是直接在Apk中完成NdefMessage数据的封装,调用setNdefPushMessage()进行设置,第二种是通过注册回调的方式,创建NdefMessage数据。这两个方式都一样,都需要将创建好的数据存放在NfcActivityState.
ndefMessage变量中,等待着NfcService来取。NfcActivityState数据NfcActivityManager的内部类,每个Apk进行数据推送设置时,都会创建对应的NfcActivityState实例,该实例的ndefMessage变量就是用来存放封装好的NdefMessage数据的。
这里我需要说的是,当APK正在运行的时候,就已经完成了数据的封装,此时如果发现NFC设备,那么NfcService将取出数据进行推送。
前面介绍了NFC启动流程的时候,说到了在JNI中完成了回调函数的注册,当发现有P2p设备的时候,将会回调java层NativeNfcManager的notifyLlcpLinkActivation()方法:
private mListener.onLlcpLinkActivated(device); } |
这里的mListener其实是NfcService的实例,构造NativeNfcManager的时候注册进来的,那么将调用NfcService.
onLlcpLinkActivated():
public sendMessage(NfcService.MSG_LLCP_LINK_ACTIVATION, } |
发送Handler消息MSG_LLCP_LINK_ACTIVATION,那么将在NfcServiceHandler.handleMessage()中处理该消息,其是NfcService的内部类。接着调用了NfcServiceHandler.
llcpActivated().然后调用P2pLinkManager.onLlcpActivated(),我们看看:
public switch case mEventListener.onP2pInRange(); prepareMessageToSend(); if (mUrisToSend mSendState mEventListener.onP2pSendConfirmationRequested(); } break; } void prepareMessageToSend() if try mMessageToSend mUrisToSend return; } // } } List<RunningTaskInfo> if String if Log.d(TAG, mMessageToSend } mMessageToSend } } mMessageToSend } } } |
这里我们需要关注prepareMessageToSend()方法,这个方法就是完成准备将要被发送的数据。这里面有三种数据需要取,对于小数据量,我们只关注其中两种。
n 第一种,在当前运行Apk中准备有数据,mCallbackNdef变量其实是NfcActivityManager的实例,是当前运行的Apk设置的,通过Binder通信调用了其createMessage()方法,取出了当前运行Apk设置在NfcActivityState.
ndefMessage变量中的数据。
n 第二种,是当前Apk没有准备有推送的数据,那么就将其包名作为数据,封装在NdefMessage中
数据准备好之后,暂时存放在P2pLinkManager.
mMessageToSend变量中。
数据准备好后,将调用mEventListener.onP2pSendConfirmationRequested();发送P2p事件,mEventListener是P2pEventManager的实例,看看其代码:
public final & if mCallback.onP2pSendConfirmed(); } mSendUi.showPreSend();//缩小屏幕 } } |
根据模式的选择,调用到SendUi.showPreSend()方法,这个方法完成的功能是缩小屏幕供用户点击,当用户点击的时候才能推送数据,点击的时候,将回调P2pEventManager.onSendConfirmed()方法:
public if mSendUi.showStartSend(); mCallback.onP2pSendConfirmed(); } } |
mCallback其实是P2pLinkManager的实例,调用onP2pSendConfirmed():
public sendNdefMessage(); } void sendNdefMessage() synchronized cancelSendNdefMessage(); mSendTask mSendTask.execute(); } } |
调用了sendNdefMessage(),在该方法中,创建了SendTask实例,其继承于Task,且是P2pLinkManager的内部类,看看SendTask的doInBackground()方法:
public NdefMessage Uri[] boolean synchronized m uris } try int } if result } result } } } |
在异步在Task中,开始数据的推送。doSnepProtocol方法其实是通过SnepServer服务完成推送,而只有其出现异常的时候才会启用NdefPushServer完成推送。看看P2pLinkManagerdoSnepProtocol().:
static NdefMessage SnepClient try snepClient.connect(); } } try if …… } snepClient.put(msg); } return } // } } |
在该方法中,构建了一个SnepClient的实例,变调用snepClient.connect(),其实就是创建了Socket的客户端,并使其连接起来,通过Socket将数据推送 。
对于小数据量,uris为空,mgs不为空。调用snepClient.put(msg)了开始数据的推送。
5.1.3 数据读流程
5.1.3.1 时序图
5.1.3.2 代码分析
在前面接收启动NFC流程的时候,提到了P2pLinkManager的初始化,在初始化中,启动了一个线程,用于接收数据的,我们看看SnepServer.
ConnectionThread线程的run函数:
ConnectionThread(LlcpSocket super(TAG); mSock mMessager } public if try boolean synchronized running } while if break; } synchronized running } } } } } |
开启线程接收数据,在handlerRequest()完成数据的处理,我们注意到有两个参数,第一个mMessager是SnepMessenger的实例,在ConnectionThread的构造函数被创建的。看看SnepServer.handlerRequest()方法吧:
static SnepMessage try request } …… return } if …… } //在需要蓝牙传送大量数据的时候,用到的 …… } messenger.sendMessage(callback.doPut(request.getNdefMessage()));//回调doput方法,传送数据 } …… } return } |
SnepMessenger类其实是将数据有封装了一层到SnepMessage,调用SnepMessenger. getMessage,通过Socket读取到数据,调用SnepMessage.getNdefMessage将读取到的数据转换成NdefMessage,然后调用callback.doPut()将数据传送到P2pLinkManager。Callback接口在P2pLinkManager被实现了:
final @Override public onReceiveComplete(msg); return } @Override public NdefMessage } }; void onReceiveComplete(NdefMessage // mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, } |
在onReceiveComplete(msg)中,通过发送Handler消息对MSG_RECEIVE_COMPLETE,将NdefMessage数据的继续往上传。在P2pLinkManager.
handleMessage()接收处理消息:
public case NdefMessage mEventListener.onP2pReceiveComplete(true); NfcService.getInstance().sendMockNdefTag(m); } break; } |
调用了NfcService的sendMockDefTag()方法:
public sendMessage(MSG_MOCK_NDEF, } |
再一次发送Handler消息,将msg传到NfcServiceHandler中,代码如下:
case NdefMessage Bundle extras.putParcelable(Ndef.EXTRA_NDEF_MSG, ……. Tag new new boolean break; } |
在这里,对NdefMessage数据进行了一次封装,将其封装到Tag里面,然后调用NfcDispatcher.dispatchTag()派发Tag数据。详细代码如下:
public NdefMessage Ndef if message } …… DispatchInfo …… if return }…… return } public intent intent.putExtra(NfcAdapter.EXTRA_TAG, intent.putExtra(NfcAdapter.EXTRA_ID, if intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, ndefUri ndefMimeType } ndefUri ndefMimeType } } |
前面调用Tag.createMockTag创建Tag实例的时候,带有TagTechnology.NDEF参数,已经说明了Tag支持的数据类型是NDEF,所以这里调用Ndef.get(tag)返回的ndef不为空,message也不为空,我之前读取的NdefMessage数据。
接着够着了一个DispatchInfo的实例,在构造函数中,创建了Intent的实例,并将tag、message封装到Intent中,供Activity读取。这里还将NdefMessage转换为uri和mime,这两个数据也是用来找Activity的一个参数。
然后调用NfcDispatcher.tryNdef()尝试发送NDEF消息启动Activity,这个能成功启动。代码如下:
public intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED); if intent.setData(ndefUri); return } intent.setType(ndefMimeType); return } return } boolean dispatch.setNdefIntent();//设置Action为ACTION_NDEF_DISCOVERED // List<String> for dispatch.intent.setPackage(pkg); if if return } } // if String Intent if if return } // Intent if if return } } // dispatch.intent.setPackage(null); if if return } return } |
首先调用setNdefIntent设置Intent的Action为ACTION_NDEF_DISCOVERED,如果前面读取的ndefUri、ndefMimeType不为空,那么设置到Intent里。
种处理的方式:
n 第一种为AAR,为 Android
Application Record的简写
如果作为P2p的发送端,调用NdefRecord.createApplicationRecord (String packageName)将包名封装到Ndefmessage中,意思是只允许包名为packageName的Apk处理数据。那么现在我们分析的是接收端的代码,解析NdefMessage数据,将其转换成合法的包名。如果包名存在于当前的系统中,那么就启动该Apk来处理数据。所以对于AAR,接收数据的包名必须是packageName,Activity的ACTION必须包含ACTION_NDEF_DISCOVERED,数据类型必须满足ndefUri、ndefMimeType。
n 以包名封装Intent
如果NdefMessage中能转换成合法的包名,且前面的Intent没有Activity响应,那么就需要一包名封装的Intent启动Activity。这样情况是把当前正在运行的apk的包名发送给其他设备,其他设备将启动该apk
n 在第二种Intent的情况下,如果接收设备没有该Apk,那么将通过Intent启动google
play去下载该Apk
n 第四种为正常类型,通过Action为ACTION_NDEF_DISCOVERED、及ndefUri、ndefMimeType去启动Activity。有可能多个Activity响应的。
5.2 大数据量的传送
大数据量的传送,是指图片等数据量比较大的资源,需要通过NFC启动蓝牙的匹配,通过蓝牙来传送数据。
5.2.1 读写流程图
5.2.2 发送端发送蓝牙请求和发送数据流程
5.2.2.1时序图
大数据量的写操作跟小数据量的类似,我们这里主要关注差异的部分,我们从P2pLinkManager.doSenpProtocol()开始。前面部分的时序图,请查看5.1.2.1小数据量写操作的时序图.
5.2.2.2 代码分析
在看P2pLinkManager.doSenpProtocol()之前,我们先看看发送数据的Apk是如何设置数据的吧。
mNfcAdapter mNfcAdapter.setBeamPushUris(new |
以上代码是在Gallery2中设置图片资源的代码,将图片的路径封装在Uri数组中,并调用NfcAdapter.
setBeamPushUris()进行设置。这个跟小数据量的设置类似,是将数据保存在NfcActivityState.
Uris变量中。P2pLinkManager将回调NfcActivityManager.getUris()获取到该数据。我们看看代码吧:
void …… if try mMessageToSend mUrisToSend return; } } } } } |
P2pLinkManager.
prepareMessageToSend()方法相信已经不再陌生,前面也见到过,这里就是通过Binder回调了NfcActivityManager.getUris()方法,读取数据,并暂存在mUrisToSend变量中。
好了,经过简单的介绍,那么现在我们可以从P2pLinkManager.
doSenpProtocol()开始了,代码如下:
static NdefMessage SnepClient try snepClient.connect();//与socket连接 } } try if NdefMessage //封装蓝牙标志的请求信息在NdefMessage中 NdefMessage if SnepMessage response } if handoverManager.doHandoverUri(uris, } snepClient.put(msg); } return } } } |
在大数据量传送流程图中也说到,发送端要发送图片之前,需要创建标准的蓝牙请求信息,然后将信息封装在Ndefmessage中,发送给接收端,当收到接收端回应之后才能发送真正的图片数据。
下面我们来看看标准蓝牙请求数据的创建代码如下:
HandoverManager.createHandoverRequestMessage():
public if return } |
当然,需要设备有蓝牙的支持,否则面谈,接着调用两个接口创建两个NdefRecord,封装在NdefMessage中。
先看看HandoverManager.
createHandoverRequestRecord()方法,蓝牙请求数据:
NdefRecord NdefMessage createBluetoothAlternateCarrierRecord(false)); byte[] ByteBuffer payload.put((byte)0x12); // payload.put(nestedMessage.toByteArray()); byte[] payload.position(0); payload.get(payloadBytes); return payloadBytes); } |
接着看HandoverManager.
createBluetoothOobDataRecord(),创建当前设备蓝牙地址数据:
NdefRecord byte[] payload[0] payload[1] synchronized if mLocalBluetoothAddress } byte[] addressBytes = System.arraycopy(addressBytes, } return } |
上面两个方法,创建了两个NdefRecord,一个是蓝牙请求数据,另一个是当前蓝牙地址数据。好了,将量数据封装在NdefMessage中返回。我们回头继续看doSnepProtocol()方法。createHandoverRequestMessage()之后就到SnepClient.get(request)发送请求了:
public SnepMessenger synchronized messenger } synchronized try messenger.sendMessage(SnepMessage.getGetRequest(mAcceptableLength, return } throw } } } |
在发送数据之前,先调用SnepMessage.getGetRequest(mAcceptableLength,
msg)将NdefMessage数据转换成SnepMessage数据,然后调用SnepMessenger.sendMessage开始发送数据,SnepMessenger中包含了Socket的端口。
发送数据之后直接调用SnepMessenger.getMessage();获取另一设备的回应信息。这里有个疑问,一直不明白,发送完数据之后立刻获取回应,这样是怎么做到同步的呢?求解释…....
到此,我们继续回到P2pLinkManager.
doSnepProtocol()中,SnepClient.get()的get请求有了回应,回应的信息还是封装在SnepMessage中,接着调用SnepMessage的方法getNdefMessage()将回应的数据转换成NdefMessage数据。
有了回应,说明蓝牙可以匹配,调用HandoverManager.doHandoverUri(uris,
response),开始通过蓝牙发送Uri。
// public if BluetoothHandoverData if // getOrCreateHandoverTransfer(data.device.getAddress(), BluetoothOppHandover uris, handover.start();//开始发送数据 } } |
首先需要调用HandoverManager.parse()将回应数据解析为蓝牙数据,里面当然包含了接收设备的蓝牙地址,接着创建了一个BluetoothOppHandover()实例,这样,该实例就包含了接收设备的蓝牙地址,Uris数据,然后就调用其start()开始传送数据了。
下面就需要看看接收端是怎么回应蓝牙请求了的。
5.2.3 接收端回应蓝牙请求流程
5.2.3.1时序图
5.2.3.2 代码分析
SnepServer我们这里也不陌生了的,里面有ConnectionThread线程读取收到的数据,在run方法中调用SnepServer.handleRequest()处理请求数据:
static SnepMessage try request } …… } if …… } messenger.sendMessage(callback.doGet(request.getAcceptableLength(), request.getNdefMessage())); } if messenger.sendMessage(callback.doPut(request.getNdefMessage())); } …… } return } |
SnepMessenger.getMessage()这里也不陌生了,是用来读取收到的数据的,将数据保存在SnepMessage中。
要清楚的是,我们这里是要回应蓝牙的请求,所以这里我们满足了条件request.getField()
==
SnepMessage.REQUEST_GET,即get请求,意思是,接收到得到的数据是其他设备的请求信息,当前设备作为接收端,需要解析其请求数据,满足条件后,将发送回应信息的请求端。
callback.doGet()就是去处理请求的信息,然后返回回应的信息,通过SnepMessenger.
sendMessage()回应发送给请求端。
先来看看callback.doGet()。这个前面也见到过,Callback接口在P2pLinkManager被实现了:
final @Override public onReceiveComplete(msg); return } @Override public NdefMessage if onReceiveHandover(); return } return } } }; |
代码简单,我们只需要关注HandoverManager.tryHandoverRequest(),参数类型是NdefMessage:
public NdefRecord //判断数据是否是蓝牙请求数据 if if BluetoothHandoverData for if Arrays.equals(oob.getType(), bluetoothData break; } } synchronized(HandoverManager.this) if if return } } // HandoverTransfer bluetoothData.device.getAddress(), transfer.updateNotification();//发送通知准备接收图片数据,状态栏看到了进度条 } // whitelistOppDevice(bluetoothData.device);//将蓝牙请求端设备添加到列表中 // return } |
该方法的参数是NdefMessage,第一步需要判断数据是否是蓝牙请求数据。
第二步,符合标准之后,读取出蓝牙地址数据bluetoothData。
第三步,启用当前设备的蓝牙。
第四步,将获取到的蓝牙请求端的蓝牙地址数据,创建蓝牙转换器
第五步,发送通知准备接收图片数据,这时候状态栏那里就可以看到进图条了。
第六步,将蓝牙请求端设备添加到列表中。
第七步,创建并返回响应的数据。这里跟请求端创建请求数据类似,里面也包含了当前蓝牙设备的地址和回应数据。都封装在NdefMessage中。
Ok,接收端蓝牙就开始等待接收数据了。HandoverManager.tryHandoverRequest()方法,就是完成两件事情,第一件事情就是第一到第六步,完成接收端蓝牙的匹配工作,第二件事情就是第七步,创建响应信息,并返回创建的回应信息给到doGet()方法中,在doGet()中,将NdefMessage 转换成SnepMessage,然后返回到SnepServer中的handleRequest()方法中:
messenger.sendMessage(callback.doGet(request.getAcceptableLength(), request.getNdefMessage())); |
接着调用SnepMessenger.sendMessage(SnepMessage),发送响应数据给请求端。
请求端接收到相应数据后,就开始通过匹配蓝牙发送图片等大数据量的数据了。简单吧。
Tag设备读写流程
6.1
Tag设备读写流程图
6.2 时序图
6.2 代码分析
在4.2中,NFC的启动将调用NativeNfcManager.enableDiscovery(),然后将调用到JNI方法com_android_nfc_NfcManager_enableDiscovery()扫描tag及P2p设备。在该方法中,调用phLibNfc_RemoteDev_NtfRegister()方法注册回调函数nfc_jni_Discovery_notification_callback()。当扫面到tag或P2p的时候将回调该方法。下面看看JNI钟开始NFC设备扫描的方法com_android_nfc_NfcManager_enableDiscovery():
static NFCSTATUS struct CONCURRENCY_LOCK(); nat REENTRANCE_LOCK(); ret REENTRANCE_UNLOCK(); …… nfc_jni_start_discovery_locked(nat, clean_and_return: CONCURRENCY_UNLOCK(); } |
我们这里主要看到其注册回调函数,然后就开始扫描NFC设备了,当发现有NFC设备的时候,将会回调nfc_jni_Discovery_notification_callback()方法,代码如下:
static phLibNfc_RemoteDevList_t uint8_t { TRACE("Notify if((remDevInfo->RemDevType || { hLlcpHandle e->CallVoidMethod(nat->manager, …… } else { e->CallVoidMethod(nat->manager, …… } e->DeleteLocalRef(tag); } } |
前面也介绍过了,发现NFC设备分为两种,一种是Tag设备,即公交卡等卡类,另一种是手机、平板等设备,完成点对点(P2p)的数据交换。在以上方法中,分别对这两类型的NFC设备做不同的处理。
当发现P2P设备的时候,回调了java层的NativeNfcManager.notifyLlcpLinkActivation(),当发现一个新的tag的时候,回调了java层的NativeNfcManager.
notifyNdefMessageListeners().这里我们主要关注发现新的tag,消息是如何传送的呢?
NativeNfcManager.
notifyNdefMessageListeners()代码如下:
private mListener.onRemoteEndpointDiscovered(tag); } |
mListener其实就是NfcService的实例,调用了其onRemoteEndpointDiscovered()方法,代码如下:
@Override public sendMessage(NfcService.MSG_NDEF_TAG, } |
这里是发送了MSG_NDEF_TAG的Handler消息到NfcServiceHandler,是NfcService的子类。在其handleMessage()方法中处理:
case TagEndpoint playSound(SOUND_START); NdefMessage if tag.startPresenceChecking(); dispatchTagEndpoint(tag); } if tag.startPresenceChecking(); dispatchTagEndpoint(tag); } …… } } break; |
这里的tag,其实是NativeNfcTag的实例,调用了其findAndReadNdef()方法读取NDEF信息,在这个方法中,调用NativeNfcTag.
readNdef(),读取消息。接着调用JNI方法,doRead()从JNI中读取消息,然后返回给NdefMessage。因为这里发现的是TAG设备,所以,返回了ndefMgs=null。
接下来看tag消息的传送。
NfcServiceHandler.dispatchTagEndPoint():
private Tag tagEndpoint.getTechExtras(), registerTagObject(tagEndpoint); if unregisterObject(tagEndpoint.getHandle()); playSound(SOUND_ERROR); } playSound(SOUND_END); } } |
这里面将NativeNfcTag消息用于构造一个Tag,tagEndpoint.getTechList()取出该Tag设备支持的technology。mNfcDispatcher是NfcDispatcher的实例,用于向Activity派发消息的。然后调用mNfcDispatcher.dispatchTag派发Tag消息。代码如下:
public NdefMessage Ndef …… PendingIntent IntentFilter[] String[][] DispatchInfo synchronized overrideFilters overrideIntent overrideTechLists } …… if return } } |
这个方法已经不再陌生了,前面也看到过了的。因为我们这里发现的是Tag设备,所以将选择调用了NfcDispatcher.tryTech()方法,代码如下:
boolean dispatch.setTechIntent();//设置Intent的Action为ACTION_TECH_DISCOVERED String[] Arrays.sort(tagTechs); // ArrayList<ResolveInfo> List<ComponentInfo> // for // if isComponentEnabled(mPackageManager, // if matches.add(info.resolveInfo);//满足条件,添加到列表中 } } } if // ResolveInfo dispatch.intent.setClassName(info.activityInfo.packageName, if if return } dispatch.intent.setClassName((String)null, } // Intent intent.putExtra(Intent.EXTRA_INTENT, intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS, matches); if if return } } return } |
第一步,还是需要设置Intent的Action为ACTION_TECH_DISCOVERED。
第二步,获取该Tag设备支持的technology。
第三步,读取系统中Action为ACTION_TECH_DISCOVERED的Activity列表,并获取该Apk支持解析的technology列表,这个是在apk的Xml文件中定义的:
<?xml <resources <tech-list> <tech>android.nfc.tech.NfcA</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcB</tech> </tech-list> </resources> |
第四步,做一个匹配,将支持解析该Tag设备technology的Apk添加到matches列表中
第五步,如果只有一个Activity满足条件,直接启动该Activity
第六步,如果有多个Activity满足条件,发送Intent消息供用户选择启动哪里Activity。
到此,Tag消息就已经传送到Activity了。那么接下来就需要在Activity中,发起对Tag设备的读写了。
6.3
Activity中发起对Tag设备读写
6.3.1 三个Intent启动Activity
当设备扫描发现有tag或P2p设备的时候,将数据封装好后发送Intent启动Activity处理数据,有三类型Intent:
nACTION_NDEF_DISCOVERED :处理NDEF数据,包含MIME、URI数据类型
n ACTION_TECH_DISCOVERED :处理非NDEF数据或者NDEF数据但不能映射为MIME、URI数据类型
n ACTION_TAG_DISCOVERED :如果没有Activity相应上面两个Intent,就由该Intent处理
官网上调度图如下:
6.3.2 Activity中对Tag设备读写
经过前面一大串的分析,对于Tag设备,首先需要获取Tag信息,也说过,Tag信息包含了支持的technology类型。获取方式如下:
Tag |
我们以MifareUltralight的technology类型为例子,根据Tag构造MifareUltralight:
MifareUltralight.get(tagFromIntent); |
看看MifareUltralight.get()接口吧:
public if try return } return } } |
get()方法中,需要判断Tag设备是否支持MifareUltralight类型的technology,如果支持,那么就真正的构造它。
得到了MifareUltralight实例后,就可以开始完成读写操作了。
public MifareUltralight try ultralight.connect(); ultralight.writePage(4, ultralight.writePage(5, ultralight.writePage(6, ultralight.writePage(7, } Log.e(TAG, } try ultralight.close(); } Log.e(TAG, } } } public MifareUltralight try mifare.connect(); byte[] return } Log.e(TAG, message...", } if try mifare.close(); } catch Log.e(TAG, } } } return } |
不管是read还是write,都会调用到TagService中,然后是NativeNfcTag,最终到JNI中。详细代码就不分析了,感兴趣就自己看。
NFC framework的更多相关文章
- windows类书的学习心得
原文网址:http://www.blogjava.net/sound/archive/2008/08/21/40499.html 现在的计算机图书发展的可真快,很久没去书店,昨日去了一下,真是感叹万千 ...
- NXP NFC移植及学习笔记(原创)
NFC功能介绍 NFC 目前使用的三种功能: 1. P2P模式:基于LLCP协议的基础上,以NDEF数据交换格式来通信. 2. 读写模式:当作为读卡器,对NFC Tag的读写. 3. 卡模拟模式:模块 ...
- 【NFC】Android NFC API Reference中英文
0 Near Field Communication Near Field Communication (NFC) is a set of short-range wireless technol ...
- Android 2.3 NFC简介
Android 2.3加入了NFC(近场通讯)的支持.官网developer.android.com的英文介绍如下:Near Field Communications (NFC)Android 2.3 ...
- 移动支付之智能IC卡与Android手机进行NFC通信
本文来自http://blog.csdn.net/hellogv/ .引用必须注明出处. 眼下常见的智能IC卡执行着JavaCard虚拟机.智能IC卡上能够执行由精简后的Java语言编写 ...
- NFC协议学习分享
很多同学在学习NFC协议的时候,觉得NFC的规范从底层到上层的应有尽有,有点无处下手的感觉.这里就和大家分享下我曾经学习NFC规范的经验.如果有不对的地方,也请各位同学批评指正.NFC Forum中有 ...
- Android Framework 其中A记录
一个简短的引论 以往的研究太偏应用层的功能,实现了,原则上不进入非常理解,现在,研究人员framework该框架层. 创纪录的 1.下载源代码,文件夹例如以下: 2.Android系统的层次例如以下: ...
- Android Framework 学习和需要学习的内容
1. 之前的研究太偏向应用层功能实现了,很多原理不了解没有深究,现在研究framework面存一些资料待有空查看. 2.Android系统的层次如下: 3.项目目录简单分析如下: 4.telphony ...
- 高通 NXP NFC(PN547PN548) 移植流程 android6.0
一.驱动部分 首先向NXP 的 fae要android 6.0 bring up的代码,如:NFC_NCIHALx_AR0F.4.3.0_M_NoSE 结构目录如下: 1. 添加驱动文件 高通平台需使 ...
随机推荐
- [Nginx 1] Nginx简介
导读:现在项目中用到这个Nginx了,本来是想着把代码调通了就得了.想想还是花点时间总结总结,就利用门卫思维吧.今天主要是一个整体的介绍,然后在学习的过程中,接着总结Nginx的其他使用事宜. 一.什 ...
- Leetcode028. Implement strStr()
class Solution { public: int strStr(string haystack, string needle) { ; //needle empty ; //haystack ...
- OpenLDAP 安装及配置 笔记
首先下载 OpenLdap(Ldap服务器) 和 LdapAdmin(客户端) 两个软件 OpenLDAPforWindows_2.4.39.part1.rar OpenLDAPforWindows_ ...
- Unieap3.5-Grid编辑列中数字与下拉改变
Grid列表中字段改变事件 <cell label="单据金额" width='20%' name="NFEE_1" id="NFEE_1&qu ...
- 一步一步学习SignalR进行实时通信_1_简单介绍
一步一步学习SignalR进行实时通信\_1_简单介绍 SignalR 一步一步学习SignalR进行实时通信_1_简单介绍 前言 SignalR介绍 支持的平台 相关说明 OWIN 结束语 参考文献 ...
- IntelliJ IDEA 快捷键和设置
IntelliJ IDEA 使用总结 http://my.oschina.net/xianggao/blog/97539 IntelliJ IDEA 问题解决:1.乱码,主要是快捷键的字样显示乱码 中 ...
- 针对BootStrap中tabs控件的美化和完善
BootStrap中的tabs控件以其简单易用而很受广大开发者的欢迎.但是,它的样式比较单一,如何才能在其原有的基础上做出更加美观的效果呢,我一直在考虑这个问题.另外,Bootstrap中的tabs必 ...
- 兼容主流浏览器的CSS透明代码
透明往往能产生不错的网页视觉效果下面是兼容主流浏览器的CSS透明代码.transparent_class { filter:alpha(opacity=50); -moz-opacity:0.5; ...
- Winform登录、控制软件只运行一次、回车登录
Winform登录对很多程序猿来说都有些困惑,登录进入主窗体后要销毁登录窗体,而不是隐藏哦,怎么实现呢? 先贴一段Program.cs的代码 static void Main() { Mutex mu ...
- .Net 内存泄露
一.事件引起的内存泄露 1.不手动注销事件也不发生内存泄露的情况 我们经常会写EventHandler += AFunction; 如果没有手动注销这个Event handler类似:EventHan ...