NFC framework

时间:2023-03-09 17:19:55
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架构,其框架图如下:

NFC framework

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 总体类图关系

NFC framework

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
uriRecord = new NdefRecord(

NdefRecord.TNF_ABSOLUTE_URI
,

"http://developer.android.com/index.html".getBytes(Charset.forName("US-ASCII")),

new
byte[0], new byte[0]);

new
NdefMessage(uriRecord);

以上是用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,如下图:

NFC framework

当tag设备与手机足够近的时候,手机设备首先收到了Tag信息,里面包含了当前Tag设备所支持的technology,然后将Tag信息发送到指定的Activity中。在Activity中,将读取Tag里面的数据,构造相应的technology,然后再以该technology的标准,对tag设备进行读写。

初始化流程

3.1 时序图

NFC framework

3.2 代码分析

初始化分两部分,第一是服务端的初始化,并将服务添加到ServiceManager中,第二是初始化NFC适配器NfcAdapter。

3.2.1 Server端初始化

NFC的服务端代码在packages/apps/Nfc中,并且还包含了JNI代码,前面也介绍过,NFC的服务端是一个应用程序,跟随系统启动并一直存在的一个服务进程。

NfcService继承于Application,当程序启动的时候,调用onCreate()方法,代码如下:

public
void onCreate() {

super.onCreate();

mNfcTagService
= new TagService();

mNfcAdapter
= new NfcAdapterService();

mExtrasService
= new NfcAdapterExtrasService();

……

mDeviceHost
= new NativeNfcManager(this, this);

mNfcDispatcher
= new NfcDispatcher(this, handoverManager);

mP2pLinkManager
= new P2pLinkManager(mContext, handoverManager);

……

ServiceManager.addService(SERVICE_NAME,
mNfcAdapter);//将mNfcAdapter添加到系统服务列表中。

…….

new
EnableDisableTask().execute(TASK_BOOT);  // do blocking
boot tasks

}

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,
new ServiceFetcher() {

public
Object createService(ContextImpl ctx) {

return
new NfcManager(ctx);

}});

}

在NfcManager的构造函数中,调用了NfcAdapter.getNfcAdapter(context),创建NFC
Adapter。

public
static synchronized NfcAdapter getNfcAdapter(Context context) {

……

sService
= getServiceInterface();//获取NFC服务接口

……

try
{

sTagService
= sService.getNfcTagInterface();//获取NFC
tag服务接口

}
catch (RemoteException e) {

}

……

NfcAdapter
adapter = sNfcAdapters.get(context);

if
(adapter == null) {

adapter
= new
NfcAdapter(context);

sNfcAdapters.put(context,
adapter);

}

return
adapter;

}

private
static INfcAdapter getServiceInterface() {//获取NFC服务接口

IBinder
b = ServiceManager.getService("nfc");

if
(b == null) {

return
null;

}

return
INfcAdapter.Stub.asInterface(b);

}

我们看看getServiceInterface()方法,在3.2.1我们也看到了,调用ServiceManager.addService()将NfcAdapterService的实例添加到系统的服务列表中,这里我们调用了ServiceManager.getService(“nfc”)获取到了服务端的NfcAdapterService对象的实例。

在NfcAdapterService类中提供了getNfcTagInterface接口,用于获取远程服务端的TagService对象的实例。

如果一切正常,那么将创建NfcAdapter的实例,在其构造函数中,创建了NfcActivityManager的实例。

启动NFC流程

4.1 时序图

NFC framework

4.2 代码分析

如果android设备有NFC硬件支持,那么将在设置应用的出现“无线和网络à更多àNFC”选项,点击将使能NFC功能。其实就是调用了NfcAdapter.enable()方法,代码如下:

public
boolean enable() {

try
{

return sService.enable();
//调用了远程服务NfcAdapterService的enable方法

}
catch (RemoteException e) {

}

}

在NfcAdapterService.enable()方法中,创建了一个Task任务来完成使能工作,代码如下:

public
boolean enable() throws RemoteException {

……

new
EnableDisableTask().execute(TASK_ENABLE);

return
true;

}

EnableDisableTask是NfcService的一个内部类,继承于AsyncTask,一个异步的任务线程,实际工作的doInBackground方法中。根据了TASK_ENABLE参数,选择调用到了EnableDisableTask.
enableInternal()完成NFC功能的使能,代码如下:

boolean
enableInternal() {

……

if
(!mDeviceHost.initialize())
{ //NFC硬件初始化

return
false;

}

synchronized(NfcService.this)
{

mObjectMap.clear();

mP2pLinkManager.enableDisable(mIsNdefPushEnabled,
true);//P2p功能的启动

updateState(NfcAdapter.STATE_ON);

}

initSoundPool();

applyRouting(true);
//开始扫描

return
true;

}

mDeviceHost其实是NativeNfcManager的实例,其继承于DeviceHost。调用了其initialize()方法,接着调用JNI方法doInitialize(),完成对NFC硬件的初始化。

硬件初始化完成之后,就需要初始化P2pLiskManager。P2p就是点对点传送的意思。这里初始化,需要创建读取数据线程,以及socket的创建。

下面看看P2pLinkManager.enableDisable(),启动P2p功能:

public
void enableDisable(boolean sendEnable, boolean receiveEnable) {

if
(!mIsReceiveEnabled && receiveEnable) {

mDefaultSnepServer.start();

mNdefPushServer.start();

……

}
else ……

}

这里启动了两个服务,分别是SnepServer和NdefPushServer,但是在实际使用过程中优先使用SnepServer服务,只有当其使用失败的时候,才会用到NdefPushServer服务。所以,我们这里就看SnepServer就可以了,NdefPushServer也比较相似。SnepServer.start():

public
void start() {

mServerThread
= new ServerThread();

mServerThread.start();

mServerRunning
= true;

}

代码非常的简单,ServerThread是继承与Thread的,且是SnepServer的内部类。看看其run()方法,为了方便理解,剪切了不少代码:

public
void run() {

while
(threadRunning) {

if
(DBG) Log.d(TAG, "about create LLCP service socket");

try
{

mServerSocket
= NfcService.getInstance().createLlcpServerSocket(mServiceSap,

mServiceName,
MIU, 1, 1024);//创建Socket

while
(threadRunning) {

LlcpServerSocket
serverSocket;

synchronized
(SnepServer.this) {

serverSocket
= mServerSocket;

}

LlcpSocket
communicationSocket = serverSocket.accept();//创建Socket

if
(communicationSocket != null) {

int
miu = communicationSocket.getRemoteMiu();

new
ConnectionThread(communicationSocket, fragmentLength).start();

}

}

}

}

这里主要是完成了Socket的创建,这个Socket是用于接收其他设备发送过来的数据的,ConnectionThread也是SnepServer的内部类,继承与Thread,看看其run()函数:

public
void run() {

try
{……

while
(running) {

if
(!handleRequest(mMessager,
mCallback)) {

break;

}

synchronized
(SnepServer.this) {

running
= mServerRunning;

}

}

}

}

这个是一个连接线程,与客户端的Socket连接,如果有接收到Socket发送的数据的时候,就用handlerRequest处理数据。

以上已经完成了P2p设备的初始化,下面就需要去扫描查询tag及P2p设备。

本调用applyRouting(true)开始扫描tag及P2p消息。

void
applyRouting(boolean force) {

……

try
{

……

//
configure NFC-EE routing

if
(mScreenState >= SCREEN_STATE_ON_LOCKED &&

mEeRoutingState
== ROUTE_ON_WHEN_SCREEN_ON) {

if
(force || !mNfceeRouteEnabled) {

mDeviceHost.doSelectSecureElement();

}

}
else {

if
(force ||  mNfceeRouteEnabled) {

mDeviceHost.doDeselectSecureElement();

}

}

//
configure NFC-C polling

if
(mScreenState >= POLLING_MODE) {

if
(force || !mNfcPollingEnabled) {

mDeviceHost.enableDiscovery();

}

}
else {

if
(force || mNfcPollingEnabled) {

mDeviceHost.disableDiscovery();

}

}

}
finally {

watchDog.cancel();

}

}

}

这里我们关注NativeNfcManager.enableDiscovery()方法,最终调用到JNI中,在JNI中注册了回调函数,当扫描到tag或p2p后,将回调Java层函数。如果发现Tag设备,将会回调NativeManager.notifyNdefMessageListeners()方法,如果发现P2p设备,将会回调NativeManager.notifyLlcpLinkActivation()方法。JNI代码我们就不分析了,我们就主要关注这两个方法就可以了:

private
void notifyNdefMessageListeners(NativeNfcTag
tag) {

mListener.onRemoteEndpointDiscovered(tag);

}

private
void notifyLlcpLinkActivation(NativeP2pDevice
device) {

mListener.onLlcpLinkActivated(device);

}

5
NDEF数据读写流程

5.1 小数据量的传送

小数据量的传送,指的是传送联系人、网页地址、邮件、包名等,数据量比较小,可以直接用。

5.1.1读写流程图

NFC framework

5.1.2 数据写流程
5.1.2.1时序图

NFC framework

5.1.2.2代码分析

NfcAdapter提供了两个接口给应用程序设置推送的数据:

public
void setNdefPushMessage(NdefMessage message, Activity
activity,);//

public
void setNdefPushMessageCallback(CreateNdefMessageCallback
callback, Activity activity,);

public
interface CreateNdefMessageCallback {

public
NdefMessage createNdefMessage(NfcEvent event);

}

第一种是直接在Apk中完成NdefMessage数据的封装,调用setNdefPushMessage()进行设置,第二种是通过注册回调的方式,创建NdefMessage数据。这两个方式都一样,都需要将创建好的数据存放在NfcActivityState.
ndefMessage变量中,等待着NfcService来取。NfcActivityState数据NfcActivityManager的内部类,每个Apk进行数据推送设置时,都会创建对应的NfcActivityState实例,该实例的ndefMessage变量就是用来存放封装好的NdefMessage数据的。

这里我需要说的是,当APK正在运行的时候,就已经完成了数据的封装,此时如果发现NFC设备,那么NfcService将取出数据进行推送。

前面介绍了NFC启动流程的时候,说到了在JNI中完成了回调函数的注册,当发现有P2p设备的时候,将会回调java层NativeNfcManager的notifyLlcpLinkActivation()方法:

private
void notifyLlcpLinkActivation(NativeP2pDevice device) {

mListener.onLlcpLinkActivated(device);

}

这里的mListener其实是NfcService的实例,构造NativeNfcManager的时候注册进来的,那么将调用NfcService.
onLlcpLinkActivated():

public
void onLlcpLinkActivated(NfcDepEndpoint device) {

sendMessage(NfcService.MSG_LLCP_LINK_ACTIVATION,
device);

}

发送Handler消息MSG_LLCP_LINK_ACTIVATION,那么将在NfcServiceHandler.handleMessage()中处理该消息,其是NfcService的内部类。接着调用了NfcServiceHandler.
llcpActivated().然后调用P2pLinkManager.onLlcpActivated(),我们看看:

public
void onLlcpActivated() {

switch
(mLinkState) {

case
LINK_STATE_DOWN:

mEventListener.onP2pInRange();

prepareMessageToSend();

if
(mMessageToSend != null ||

(mUrisToSend
!= null && mHandoverManager.isHandoverSupported())) {

mSendState
= SEND_STATE_NEED_CONFIRMATION;

mEventListener.onP2pSendConfirmationRequested();

}

break;

}

void prepareMessageToSend()
{

if
(mCallbackNdef != null) {

try
{

mMessageToSend
= mCallbackNdef.createMessage();//取出Apk中准备的数据

mUrisToSend
= mCallbackNdef.getUris();//大数据量的数据

return;

}
catch (RemoteException e) {

//
Ignore

}

}

List<RunningTaskInfo>
tasks = mActivityManager.getRunningTasks(1);

if
(tasks.size() > 0) {

String
pkg = tasks.get(0).baseActivity.getPackageName();

if
(beamDefaultDisabled(pkg)) {//判断当前运行的是否是Home程序

Log.d(TAG,
"Disabling default Beam behavior");

mMessageToSend
= null;

}
else {

mMessageToSend
= createDefaultNdef(pkg);//将当前运行的包名数据封装在NdefMessage中。

}

}
else {

mMessageToSend
= null;

}

}

}

这里我们需要关注prepareMessageToSend()方法,这个方法就是完成准备将要被发送的数据。这里面有三种数据需要取,对于小数据量,我们只关注其中两种。

n         第一种,在当前运行Apk中准备有数据,mCallbackNdef变量其实是NfcActivityManager的实例,是当前运行的Apk设置的,通过Binder通信调用了其createMessage()方法,取出了当前运行Apk设置在NfcActivityState.
ndefMessage变量中的数据。

n         第二种,是当前Apk没有准备有推送的数据,那么就将其包名作为数据,封装在NdefMessage中

数据准备好之后,暂时存放在P2pLinkManager.
mMessageToSend变量中。

数据准备好后,将调用mEventListener.onP2pSendConfirmationRequested();发送P2p事件,mEventListener是P2pEventManager的实例,看看其代码:

public
void onP2pSendConfirmationRequested() {

final
int uiModeType = mContext.getResources().getConfiguration().uiMode

&
Configuration.UI_MODE_TYPE_MASK;

if
(uiModeType == Configuration.UI_MODE_TYPE_APPLIANCE) {

mCallback.onP2pSendConfirmed();

}
else {

mSendUi.showPreSend();//缩小屏幕

}

}

根据模式的选择,调用到SendUi.showPreSend()方法,这个方法完成的功能是缩小屏幕供用户点击,当用户点击的时候才能推送数据,点击的时候,将回调P2pEventManager.onSendConfirmed()方法:

public
void onSendConfirmed() {

if
(!mSending) {

mSendUi.showStartSend();

mCallback.onP2pSendConfirmed();

}

}

mCallback其实是P2pLinkManager的实例,调用onP2pSendConfirmed():

public
void onP2pSendConfirmed() {

sendNdefMessage();

}

void sendNdefMessage()
{

synchronized
(this) {

cancelSendNdefMessage();

mSendTask
= new SendTask();

mSendTask.execute();

}

}

调用了sendNdefMessage(),在该方法中,创建了SendTask实例,其继承于Task,且是P2pLinkManager的内部类,看看SendTask的doInBackground()方法:

public
Void doInBackground(Void... args) {

NdefMessage
m;

Uri[]
uris;

boolean
result;

synchronized
(P2pLinkManager.this) {

m
= mMessageToSend;

uris
= mUrisToSend;

}

try
{

int
snepResult = doSnepProtocol(mHandoverManager,
m, uris);

}
catch (IOException e) {

if
(m != null) {

result
= new NdefPushClient().push(m);

}
else {

result
= false;

}

}

}

在异步在Task中,开始数据的推送。doSnepProtocol方法其实是通过SnepServer服务完成推送,而只有其出现异常的时候才会启用NdefPushServer完成推送。看看P2pLinkManagerdoSnepProtocol().:

static
int doSnepProtocol(HandoverManager handoverManager,

NdefMessage
msg, Uri[] uris) throws IOException {

SnepClient
snepClient = new SnepClient();

try
{

snepClient.connect();

}
catch (IOException e) {

}

try
{

if
(uris
!= null)
{//小数据量,uris为空

……

}
else if (msg != null) {

snepClient.put(msg);

}

return
SNEP_SUCCESS;

}
catch (IOException e) {

//
SNEP available but had errors, don't fall back to NPP.

}

}

在该方法中,构建了一个SnepClient的实例,变调用snepClient.connect(),其实就是创建了Socket的客户端,并使其连接起来,通过Socket将数据推送 。

对于小数据量,uris为空,mgs不为空。调用snepClient.put(msg)了开始数据的推送。

5.1.3 数据读流程
5.1.3.1 时序图
NFC framework
5.1.3.2 代码分析

在前面接收启动NFC流程的时候,提到了P2pLinkManager的初始化,在初始化中,启动了一个线程,用于接收数据的,我们看看SnepServer.
ConnectionThread线程的run函数:

ConnectionThread(LlcpSocket
socket, int fragmentLength) {

super(TAG);

mSock
= socket;

mMessager
= new SnepMessenger(false, socket, fragmentLength);

}

public
void run() {

if
(DBG) Log.d(TAG, "starting connection thread");

try
{

boolean
running;

synchronized
(SnepServer.this) {

running
= mServerRunning;

}

while
(running) {

if
(!handleRequest(mMessager, mCallback)) {//读取消息

break;

}

synchronized
(SnepServer.this) {

running
= mServerRunning;

}

}

}
catch (IOException e) {

}

}

开启线程接收数据,在handlerRequest()完成数据的处理,我们注意到有两个参数,第一个mMessager是SnepMessenger的实例,在ConnectionThread的构造函数被创建的。看看SnepServer.handlerRequest()方法吧:

static
boolean handleRequest(SnepMessenger messenger, Callback callback)
throws IOException {

SnepMessage
request;

try
{

request
= messenger.getMessage();//真正的读数据

}
catch (SnepException e) {

……

return
false;

}

if
(((request.getVersion() & 0xF0) >> 4) !=
SnepMessage.VERSION_MAJOR) {

……

}
else if (request.getField() == SnepMessage.REQUEST_GET) {

//在需要蓝牙传送大量数据的时候,用到的

……

}
else if (request.getField() == SnepMessage.REQUEST_PUT) {

messenger.sendMessage(callback.doPut(request.getNdefMessage()));//回调doput方法,传送数据

}
else {

……

}

return
true;

}

SnepMessenger类其实是将数据有封装了一层到SnepMessage,调用SnepMessenger. getMessage,通过Socket读取到数据,调用SnepMessage.getNdefMessage将读取到的数据转换成NdefMessage,然后调用callback.doPut()将数据传送到P2pLinkManager。Callback接口在P2pLinkManager被实现了:

final
SnepServer.Callback mDefaultSnepCallback = new
SnepServer.Callback() {

@Override

public
SnepMessage doPut(NdefMessage
msg) {

onReceiveComplete(msg);
//处理NdefMessage数据

return
SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS);

}

@Override

public
SnepMessage doGet(int acceptableLength, NdefMessage msg) {

NdefMessage
response = mHandoverManager.tryHandoverRequest(msg);

}

};

void onReceiveComplete(NdefMessage
msg) {

//
Make callbacks on UI thread

mHandler.obtainMessage(MSG_RECEIVE_COMPLETE,
msg).sendToTarget();

}

在onReceiveComplete(msg)中,通过发送Handler消息对MSG_RECEIVE_COMPLETE,将NdefMessage数据的继续往上传。在P2pLinkManager.
handleMessage()接收处理消息:

public
boolean handleMessage(Message msg) {

case
MSG_RECEIVE_COMPLETE:

NdefMessage
m = (NdefMessage) msg.obj;

mEventListener.onP2pReceiveComplete(true);

NfcService.getInstance().sendMockNdefTag(m);

}

break;

}

调用了NfcService的sendMockDefTag()方法:

public
void sendMockNdefTag(NdefMessage msg) {

sendMessage(MSG_MOCK_NDEF,
msg);

}

再一次发送Handler消息,将msg传到NfcServiceHandler中,代码如下:

case
MSG_MOCK_NDEF: {

NdefMessage
ndefMsg = (NdefMessage) msg.obj;

Bundle
extras = new Bundle();

extras.putParcelable(Ndef.EXTRA_NDEF_MSG,
ndefMsg);

…….

Tag
tag = Tag.createMockTag(new byte[] { 0x00 },

new
int[] { TagTechnology.NDEF },

new
Bundle[] { extras });

boolean
delivered = mNfcDispatcher.dispatchTag(tag);

break;

}

在这里,对NdefMessage数据进行了一次封装,将其封装到Tag里面,然后调用NfcDispatcher.dispatchTag()派发Tag数据。详细代码如下:

public
boolean dispatchTag(Tag tag) {

NdefMessage
message = null;

Ndef
ndef = Ndef.get(tag); //前面调用Tag.createMockTag创建Tag实例的时候

if
(ndef != null) {

message
= ndef.getCachedNdefMessage();

}

……

DispatchInfo
dispatch = new DispatchInfo(mContext, tag, message);

……

if
(tryNdef(dispatch,
message)) {

return
true;

}……

return
false;

}

public
DispatchInfo(Context context, Tag tag, NdefMessage message) {

intent
= new Intent();

intent.putExtra(NfcAdapter.EXTRA_TAG,
tag);

intent.putExtra(NfcAdapter.EXTRA_ID,
tag.getId());

if
(message != null) {

intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES,
new NdefMessage[] {message});

ndefUri
= message.getRecords()[0].toUri();

ndefMimeType
= message.getRecords()[0].toMimeType();

}
else {

ndefUri
= null;

ndefMimeType
= null;

}

}

前面调用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 setNdefIntent()
{

intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);

if
(ndefUri != null) {

intent.setData(ndefUri);

return
intent;

}
else if (ndefMimeType != null) {

intent.setType(ndefMimeType);

return
intent;

}

return
null;

}

boolean
tryNdef(DispatchInfo dispatch, NdefMessage message) {

dispatch.setNdefIntent();//设置Action为ACTION_NDEF_DISCOVERED

//
Try to start AAR activity with matching filter

List<String>
aarPackages = extractAarPackages(message);//将数据转换成合法的包名

for
(String pkg : aarPackages) {

dispatch.intent.setPackage(pkg);

if
(dispatch.tryStartActivity()) {

if
(DBG) Log.i(TAG, "matched AAR to NDEF");

return
true;

}

}

//
Try to perform regular launch of the first AAR

if
(aarPackages.size() > 0) {

String
firstPackage = aarPackages.get(0);

Intent
appLaunchIntent
= mPackageManager.getLaunchIntentForPackage(firstPackage);

if
(appLaunchIntent != null &&
dispatch.tryStartActivity(appLaunchIntent)) {

if
(DBG) Log.i(TAG, "matched AAR to application launch");

return
true;

}

//
Find the package in Market:

Intent
marketIntent = getAppSearchIntent(firstPackage);

if
(marketIntent != null &&
dispatch.tryStartActivity(marketIntent)) {

if
(DBG) Log.i(TAG, "matched AAR to market launch");

return
true;

}

}

//
regular launch

dispatch.intent.setPackage(null);

if
(dispatch.tryStartActivity()) {

if
(DBG) Log.i(TAG, "matched NDEF");

return
true;

}

return
false;

}

首先调用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 读写流程图

NFC framework

5.2.2 发送端发送蓝牙请求和发送数据流程
5.2.2.1时序图

NFC framework

大数据量的写操作跟小数据量的类似,我们这里主要关注差异的部分,我们从P2pLinkManager.doSenpProtocol()开始。前面部分的时序图,请查看5.1.2.1小数据量写操作的时序图.

5.2.2.2 代码分析

在看P2pLinkManager.doSenpProtocol()之前,我们先看看发送数据的Apk是如何设置数据的吧。

mNfcAdapter
= NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());

mNfcAdapter.setBeamPushUris(new
Uri[]{manager.getContentUri(path)},Activity);

以上代码是在Gallery2中设置图片资源的代码,将图片的路径封装在Uri数组中,并调用NfcAdapter.
setBeamPushUris()进行设置。这个跟小数据量的设置类似,是将数据保存在NfcActivityState.
Uris变量中。P2pLinkManager将回调NfcActivityManager.getUris()获取到该数据。我们看看代码吧:

void
prepareMessageToSend() {

……

if
(mCallbackNdef != null) {

try
{

mMessageToSend
= mCallbackNdef.createMessage();

mUrisToSend
= mCallbackNdef.getUris();

return;

}
catch (RemoteException e) {

}

}

}

}

P2pLinkManager.
prepareMessageToSend()方法相信已经不再陌生,前面也见到过,这里就是通过Binder回调了NfcActivityManager.getUris()方法,读取数据,并暂存在mUrisToSend变量中。

好了,经过简单的介绍,那么现在我们可以从P2pLinkManager.
doSenpProtocol()开始了,代码如下:

static
int doSnepProtocol(HandoverManager handoverManager,

NdefMessage
msg, Uri[] uris) throws IOException {

SnepClient
snepClient = new SnepClient();//创建新的客户端

try
{

snepClient.connect();//与socket连接

}
catch (IOException e) {

}

try
{

if
(uris != null) {//说明有大数据量要发送

NdefMessage
response = null;

//封装蓝牙标志的请求信息在NdefMessage中

NdefMessage
request = handoverManager.createHandoverRequestMessage();

if
(request != null) {

SnepMessage
snepResponse =
snepClient.get(request);//发送蓝牙请求,并读取另外设备回应数据保存在snepResponse中

response
= snepResponse.getNdefMessage();//将SnepMessage数据转换成NdefMessage

}
// else, handover not supported

if
(response != null) {//有相应,可以发送数据

handoverManager.doHandoverUri(uris,
response);//通过蓝牙发送数据

}
else if (msg != null) {

snepClient.put(msg);

}
else {

return
SNEP_HANDOVER_UNSUPPORTED;

}

}
……

}

在大数据量传送流程图中也说到,发送端要发送图片之前,需要创建标准的蓝牙请求信息,然后将信息封装在Ndefmessage中,发送给接收端,当收到接收端回应之后才能发送真正的图片数据。

下面我们来看看标准蓝牙请求数据的创建代码如下:

HandoverManager.createHandoverRequestMessage():

public
NdefMessage createHandoverRequestMessage() {

if
(mBluetoothAdapter == null) return null;//是否支持蓝牙设备

return
new NdefMessage(createHandoverRequestRecord(),
createBluetoothOobDataRecord());//将数据封装在NdefMessage中

}

当然,需要设备有蓝牙的支持,否则面谈,接着调用两个接口创建两个NdefRecord,封装在NdefMessage中。

先看看HandoverManager.
createHandoverRequestRecord()方法,蓝牙请求数据:

NdefRecord
createHandoverRequestRecord() {

NdefMessage
nestedMessage = new NdefMessage(createCollisionRecord(),

createBluetoothAlternateCarrierRecord(false));

byte[]
nestedPayload = nestedMessage.toByteArray();

ByteBuffer
payload = ByteBuffer.allocate(nestedPayload.length + 1);

payload.put((byte)0x12);  //
connection handover v1.2

payload.put(nestedMessage.toByteArray());

byte[]
payloadBytes = new byte[payload.position()];

payload.position(0);

payload.get(payloadBytes);

return
new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_HANDOVER_REQUEST,
null,

payloadBytes);

}

接着看HandoverManager.
createBluetoothOobDataRecord(),创建当前设备蓝牙地址数据:

NdefRecord
createBluetoothOobDataRecord() {

byte[]
payload = new byte[8];

payload[0]
= 0;

payload[1]
= (byte)payload.length;

synchronized
(HandoverManager.this) {

if
(mLocalBluetoothAddress == null) {

mLocalBluetoothAddress
= mBluetoothAdapter.getAddress();//获取当前设备蓝牙地址

}

byte[] addressBytes =
addressToReverseBytes(mLocalBluetoothAddress);

System.arraycopy(addressBytes,
0, payload,
2, 6);//地址数据拷贝

}

return
new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new
byte[]{'b'},payload);//将地址封装在NdefRecord中

}

上面两个方法,创建了两个NdefRecord,一个是蓝牙请求数据,另一个是当前蓝牙地址数据。好了,将量数据封装在NdefMessage中返回。我们回头继续看doSnepProtocol()方法。createHandoverRequestMessage()之后就到SnepClient.get(request)发送请求了:

public
SnepMessage get(NdefMessage msg) throws IOException {

SnepMessenger
messenger;

synchronized
(this) {

messenger
= mMessenger;

}

synchronized
(mTransmissionLock) {

try
{

messenger.sendMessage(SnepMessage.getGetRequest(mAcceptableLength,
msg));//发送请求

return
messenger.getMessage();//获取回应

}
catch (SnepException e) {

throw
new IOException(e);

}

}

}

在发送数据之前,先调用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。

//
This starts sending an Uri over BT

public
void doHandoverUri(Uri[] uris, NdefMessage m) {

if
(mBluetoothAdapter == null) return;

BluetoothHandoverData
data = parse(m);//解析回应出蓝也数据

if
(data != null && data.valid) {

//
Register a new handover transfer object

getOrCreateHandoverTransfer(data.device.getAddress(),
false, true);//创建蓝牙转换器

BluetoothOppHandover
handover = new BluetoothOppHandover(mContext, data.device,

uris,
mHandoverPowerManager, data.carrierActivating);

handover.start();//开始发送数据

}

}

首先需要调用HandoverManager.parse()将回应数据解析为蓝牙数据,里面当然包含了接收设备的蓝牙地址,接着创建了一个BluetoothOppHandover()实例,这样,该实例就包含了接收设备的蓝牙地址,Uris数据,然后就调用其start()开始传送数据了。

下面就需要看看接收端是怎么回应蓝牙请求了的。

5.2.3 接收端回应蓝牙请求流程
5.2.3.1时序图
NFC framework
5.2.3.2 代码分析

SnepServer我们这里也不陌生了的,里面有ConnectionThread线程读取收到的数据,在run方法中调用SnepServer.handleRequest()处理请求数据:

static
boolean handleRequest(SnepMessenger messenger, Callback callback)
throws IOException {

SnepMessage
request;

try
{

request
= messenger.getMessage();//读取收到的数据

}
catch (SnepException e) {

……

}

if
(((request.getVersion() & 0xF0) >> 4) !=
SnepMessage.VERSION_MAJOR) {

……

}
else if (request.getField() == SnepMessage.REQUEST_GET) {//get请求

messenger.sendMessage(callback.doGet(request.getAcceptableLength(),

request.getNdefMessage()));

}
else if (request.getField() == SnepMessage.REQUEST_PUT) {//put请求

if
(DBG) Log.d(TAG, "putting message " +
request.toString());

messenger.sendMessage(callback.doPut(request.getNdefMessage()));

}
else {

……

}

return
true;

}

SnepMessenger.getMessage()这里也不陌生了,是用来读取收到的数据的,将数据保存在SnepMessage中。

要清楚的是,我们这里是要回应蓝牙的请求,所以这里我们满足了条件request.getField()
==
SnepMessage.REQUEST_GET,即get请求,意思是,接收到得到的数据是其他设备的请求信息,当前设备作为接收端,需要解析其请求数据,满足条件后,将发送回应信息的请求端。

callback.doGet()就是去处理请求的信息,然后返回回应的信息,通过SnepMessenger.
sendMessage()回应发送给请求端。

先来看看callback.doGet()。这个前面也见到过,Callback接口在P2pLinkManager被实现了:

final
SnepServer.Callback mDefaultSnepCallback = new
SnepServer.Callback() {

@Override

public
SnepMessage doPut(NdefMessage msg) {

onReceiveComplete(msg);

return
SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS);

}

@Override

public
SnepMessage doGet(int acceptableLength,
NdefMessage msg) {//处理请求信息

NdefMessage
response
= mHandoverManager.tryHandoverRequest(msg);//尝试处理请求信息,并返回回应信息。

if
(response != null) {

onReceiveHandover();

return
SnepMessage.getSuccessResponse(response);返回响应信息给SnepServer

}
else {

return
SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_FOUND);

}

}

};

代码简单,我们只需要关注HandoverManager.tryHandoverRequest(),参数类型是NdefMessage:

public
NdefMessage tryHandoverRequest(NdefMessage m) {

NdefRecord
r = m.getRecords()[0];

//判断数据是否是蓝牙请求数据

if
(r.getTnf() != NdefRecord.TNF_WELL_KNOWN) return
null;

if
(!Arrays.equals(r.getType(), NdefRecord.RTD_HANDOVER_REQUEST))
return null;

BluetoothHandoverData
bluetoothData = null;

for
(NdefRecord oob : m.getRecords()) {

if
(oob.getTnf() == NdefRecord.TNF_MIME_MEDIA &&

Arrays.equals(oob.getType(),
TYPE_BT_OOB)) {

bluetoothData
=
parseBtOob(ByteBuffer.wrap(oob.getPayload()));//解析蓝牙数据,当然包含了发送端的蓝牙地址

break;

}

}

synchronized(HandoverManager.this)
{

if
(!mHandoverPowerManager.isBluetoothEnabled()) {

if
(!mHandoverPowerManager.enableBluetooth())
{//启用当前设备的蓝牙

return
null;

}

}

//
Create the initial transfer object

HandoverTransfer
transfer = getOrCreateHandoverTransfer(//创建蓝牙转换器

bluetoothData.device.getAddress(),
true, true);

transfer.updateNotification();//发送通知准备接收图片数据,状态栏看到了进度条

}

//
BT OOB found, whitelist it for incoming OPP data

whitelistOppDevice(bluetoothData.device);//将蓝牙请求端设备添加到列表中

//
return BT OOB record so they can perform handover

return
(createHandoverSelectMessage(bluetoothActivating));创建并返回响应的数据

}

该方法的参数是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设备读写流程图

NFC framework

6.2 时序图

NFC framework

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
void com_android_nfc_NfcManager_enableDiscovery(JNIEnv *e, jobject
o) {

NFCSTATUS
ret;

struct
nfc_jni_native_data *nat;

CONCURRENCY_LOCK();

nat
= nfc_jni_get_nat(e, o);

REENTRANCE_LOCK();

ret
= phLibNfc_RemoteDev_NtfRegister(&nat->registry_info,
nfc_jni_Discovery_notification_callback,
(void *)nat); //注册回调函数

REENTRANCE_UNLOCK();

……

nfc_jni_start_discovery_locked(nat,
false);//开始扫描

clean_and_return:

CONCURRENCY_UNLOCK();

}

我们这里主要看到其注册回调函数,然后就开始扫描NFC设备了,当发现有NFC设备的时候,将会回调nfc_jni_Discovery_notification_callback()方法,代码如下:

static
void nfc_jni_Discovery_notification_callback(void *pContext,

phLibNfc_RemoteDevList_t
*psRemoteDevList,

uint8_t
uNofRemoteDev, NFCSTATUS status)

{

TRACE("Notify
Nfc Service");

if((remDevInfo->RemDevType
== phNfc_eNfcIP1_Initiator)

||
(remDevInfo->RemDevType == phNfc_eNfcIP1_Target))

{

hLlcpHandle
= remDevHandle;

e->CallVoidMethod(nat->manager,
cached_NfcManager_notifyLlcpLinkActivation, tag);

……

}

else

{

e->CallVoidMethod(nat->manager,
cached_NfcManager_notifyNdefMessageListeners, tag);

……

}

e->DeleteLocalRef(tag);

}

}

前面也介绍过了,发现NFC设备分为两种,一种是Tag设备,即公交卡等卡类,另一种是手机、平板等设备,完成点对点(P2p)的数据交换。在以上方法中,分别对这两类型的NFC设备做不同的处理。

当发现P2P设备的时候,回调了java层的NativeNfcManager.notifyLlcpLinkActivation(),当发现一个新的tag的时候,回调了java层的NativeNfcManager.
notifyNdefMessageListeners().这里我们主要关注发现新的tag,消息是如何传送的呢?

NativeNfcManager.
notifyNdefMessageListeners()代码如下:

private
void notifyNdefMessageListeners(NativeNfcTag tag) {

mListener.onRemoteEndpointDiscovered(tag);

}

mListener其实就是NfcService的实例,调用了其onRemoteEndpointDiscovered()方法,代码如下:

@Override

public
void onRemoteEndpointDiscovered(TagEndpoint tag) {

sendMessage(NfcService.MSG_NDEF_TAG,
tag);

}

这里是发送了MSG_NDEF_TAG的Handler消息到NfcServiceHandler,是NfcService的子类。在其handleMessage()方法中处理:

case
MSG_NDEF_TAG:

TagEndpoint
tag = (TagEndpoint) msg.obj;

playSound(SOUND_START);

NdefMessage
ndefMsg = tag.findAndReadNdef();

if
(ndefMsg != null) {

tag.startPresenceChecking();

dispatchTagEndpoint(tag);

}
else {

if
(tag.reconnect()) {

tag.startPresenceChecking();

dispatchTagEndpoint(tag);

}
else {

……

}

}

break;

这里的tag,其实是NativeNfcTag的实例,调用了其findAndReadNdef()方法读取NDEF信息,在这个方法中,调用NativeNfcTag.
readNdef(),读取消息。接着调用JNI方法,doRead()从JNI中读取消息,然后返回给NdefMessage。因为这里发现的是TAG设备,所以,返回了ndefMgs=null。

接下来看tag消息的传送。

NfcServiceHandler.dispatchTagEndPoint():

private
void dispatchTagEndpoint(TagEndpoint tagEndpoint) {

Tag
tag = new Tag(tagEndpoint.getUid(), tagEndpoint.getTechList(),

tagEndpoint.getTechExtras(),
tagEndpoint.getHandle(), mNfcTagService);

registerTagObject(tagEndpoint);

if
(!mNfcDispatcher.dispatchTag(tag))
{

unregisterObject(tagEndpoint.getHandle());

playSound(SOUND_ERROR);

}
else {

playSound(SOUND_END);

}

}

这里面将NativeNfcTag消息用于构造一个Tag,tagEndpoint.getTechList()取出该Tag设备支持的technology。mNfcDispatcher是NfcDispatcher的实例,用于向Activity派发消息的。然后调用mNfcDispatcher.dispatchTag派发Tag消息。代码如下:

public
boolean dispatchTag(Tag tag) {

NdefMessage
message = null;

Ndef
ndef = Ndef.get(tag);

……

PendingIntent
overrideIntent;

IntentFilter[]
overrideFilters;

String[][]
overrideTechLists;

DispatchInfo
dispatch = new DispatchInfo(mContext,
tag, message);

synchronized
(this) {

overrideFilters
= mOverrideFilters;

overrideIntent
= mOverrideIntent;

overrideTechLists
= mOverrideTechLists;

}

……

if
(tryTech(dispatch, tag)) {

return
true;

}

}

这个方法已经不再陌生了,前面也看到过了的。因为我们这里发现的是Tag设备,所以将选择调用了NfcDispatcher.tryTech()方法,代码如下:

boolean
tryTech(DispatchInfo dispatch, Tag tag) {

dispatch.setTechIntent();//设置Intent的Action为ACTION_TECH_DISCOVERED

String[]
tagTechs = tag.getTechList();//获取Tag设备支持的technology

Arrays.sort(tagTechs);

//
Standard tech dispatch path

ArrayList<ResolveInfo>
matches = new ArrayList<ResolveInfo>();

List<ComponentInfo>
registered =
mTechListFilters.getComponents();//获取Action为ACTION_TECH_DISCOVERED的Activity列表

//
Check each registered activity to see if it matches

for
(ComponentInfo info : registered) {

//
Don't allow wild card matching

if
(filterMatch(tagTechs, info.techs) &&//该Activity支持解析该technology

isComponentEnabled(mPackageManager,
info.resolveInfo)) {

//
Add the activity as a match if it's not already in the list

if
(!matches.contains(info.resolveInfo)) {

matches.add(info.resolveInfo);//满足条件,添加到列表中

}

}

}

if
(matches.size() == 1) {//只有一个Activity满足条件

//
Single match, launch directly

ResolveInfo
info = matches.get(0);

dispatch.intent.setClassName(info.activityInfo.packageName,
info.activityInfo.name);

if
(dispatch.tryStartActivity()) {

if
(DBG) Log.i(TAG, "matched single TECH");

return
true;

}

dispatch.intent.setClassName((String)null,
null);

}
else if (matches.size() > 1) {//多个Activity满足条件

//
Multiple matches, show a custom activity chooser dialog

Intent
intent = new Intent(mContext, TechListChooserActivity.class);

intent.putExtra(Intent.EXTRA_INTENT,
dispatch.intent);

intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,

matches);

if
(dispatch.tryStartActivity(intent)) {

if
(DBG) Log.i(TAG, "matched multiple TECH");

return
true;

}

}

return
false;

}

第一步,还是需要设置Intent的Action为ACTION_TECH_DISCOVERED。

第二步,获取该Tag设备支持的technology。

第三步,读取系统中Action为ACTION_TECH_DISCOVERED的Activity列表,并获取该Apk支持解析的technology列表,这个是在apk的Xml文件中定义的:

<?xml
version="1.0" encoding="utf-8"?>

<resources
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">

<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处理

官网上调度图如下:

NFC framework

6.3.2 Activity中对Tag设备读写

经过前面一大串的分析,对于Tag设备,首先需要获取Tag信息,也说过,Tag信息包含了支持的technology类型。获取方式如下:

Tag
tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

我们以MifareUltralight的technology类型为例子,根据Tag构造MifareUltralight:

MifareUltralight.get(tagFromIntent);

看看MifareUltralight.get()接口吧:

public
static MifareUltralight get(Tag tag) {

if
(!tag.hasTech(TagTechnology.MIFARE_ULTRALIGHT)) return
null;//判断该Tag是否支持

try
{

return
new MifareUltralight(tag);

}
catch (RemoteException e) {

return
null;

}

}

get()方法中,需要判断Tag设备是否支持MifareUltralight类型的technology,如果支持,那么就真正的构造它。

得到了MifareUltralight实例后,就可以开始完成读写操作了。

public
void writeTag(Tag tag, String tagText) {

MifareUltralight
ultralight = MifareUltralight.get(tag);

try
{

ultralight.connect();

ultralight.writePage(4,
"abcd".getBytes(Charset.forName("US-ASCII")));

ultralight.writePage(5,
"efgh".getBytes(Charset.forName("US-ASCII")));

ultralight.writePage(6,
"ijkl".getBytes(Charset.forName("US-ASCII")));

ultralight.writePage(7,
"mnop".getBytes(Charset.forName("US-ASCII")));

}
catch (IOException e) {

Log.e(TAG,
"IOException while closing MifareUltralight...", e);

}
finally {

try
{

ultralight.close();

}
catch (IOException e) {

Log.e(TAG,
"IOException while closing MifareUltralight...", e);

}

}

}

public
String readTag(Tag tag) {

MifareUltralight
mifare = MifareUltralight.get(tag);

try
{

mifare.connect();

byte[]
payload = mifare.readPages(4);

return
new String(payload, Charset.forName("US-ASCII"));

}
catch (IOException e) {

Log.e(TAG,
"IOException while writing MifareUltralight

message...",
e);

}
finally {

if
(mifare != null) {

try
{

mifare.close();

}

catch
(IOException e) {

Log.e(TAG,
"Error closing tag...", e);

}

}

}

return
null;

}

不管是read还是write,都会调用到TagService中,然后是NativeNfcTag,最终到JNI中。详细代码就不分析了,感兴趣就自己看。