NFC framework introduce(一)

  NFC framework introduce

 

1 NFC 简介

对于NFC,是googleandroid4.0上推出来的,简单介绍下近场通讯(NFC)是一系列短距离无线技术,一般需要4cm或者更短去初始化连接。近场通讯(NFC)允许你在NFC tagAndroid设备或者两个Android设备间共享小负载数据。优酷上有其应用的视频:http://v.youku.com/v_show/id_XMjM3ODE5ODMy.html

http://v.youku.com/v_show/id_XMzM1MTUyMzI4.html

2 总体框架

   对于NFC框架的设计,同样是android的标准的c/s架构,其框架图如下:



n        客户端:android提供了两个API包给apk,分别是android.nfc.techandroid.nfc,实现了NFC的应用接口,代码路径frameworks/base/core/java/android/nfc/techframeworks/base/core/java/android/nfc

n        服务端:packages/apps/Nfc是一个类似电话本的应用,这个程序在启动后自动运行,并一直运行,作为NFC的服务进程存在,是NFC的核心。

在这个程序代码中,有个JNI库,供NfcService调用,代码路径是packages/apps/Nfc/jni/

n        库文件:代码路径是external/libnfc-nxp,C编写的库文件,有两个库,分别是libnfc.solibnfc_ndef.solibnfc.so是一个主要的库,实现了NFC stack的大部分功能,主要提供NFC的服务进程调用。libnfc_ndef是一个很小的库,主要是实现NDEF消息的解析,供framework调用

2.1 总体类图关系

NFC framework introduce(一)_第1张图片

2.2 数据分类

NFC按照发展,分为两种,NFC basicsAdvanced NFC。从字面上理解,第一种是最先设计的,第二种是在原来基础上扩展的。

2.2.1 NFC basics

是一种点对点(P2P)数据交换的功能,传送的数据格式是NDEF,是Nfc Data Exchange Format的缩写,这个数据格式用于设备之间点对点数据的交换,例如网页地址、联系人、邮件、图片等。对于除图片以外的数据,数据量比较小,直接封装在类NdefMessage中,通过NFCNdefMessage类型数据发送到另一台设备,而对于图片这样数据量比较大的数据,需要构建一个标准的NdefMessage数据,发送给另外一台设备,等有回应之后,再通过蓝牙传送数据。

NdefMessage类是用于数据的封装,其包含有一个或多个NdefRecord类,NdefRecord才是存储数据的实体,将联系人、邮件、网页地址等转换才byte类型的数据存储在NdefRecord当中,并且包含了数据类型。举个例子吧:

NdefRecord uriRecord = newNdefRecord(

   NdefRecord.TNF_ABSOLUTE_URI ,

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

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

newNdefMessage(uriRecord);

 

以上是用NdefMessage对一个NdefRecord数据进行分装。

为了更好理解数据的传送方式,需要更细的分为三种:

n        在一个Apk中,用NdefMessage封装Apk的数据,在设置NdefRecord的数据类型,然后发送给其他设备。在接收设备的同样的APKAndroidManifest文件中设置接收数据的类型,这样通过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 <wbr>framework <wbr>introduce(一)


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

3初始化流程

3.1 时序图

NFC framework introduce(一)_第2张图片

3.2 代码分析

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

3.2.1 Server端初始化

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

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

public void onCreate(){

       super.onCreate();

       mNfcTagService = newTagService();

       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);  // doblocking boot tasks

   }

TagServiceNfcService的内部类,并继承于INfcTag.stub,因此客户端可以通过Binder通信获取到TagService的实例mNfcTagService。其主要的功能是完成tag的读写。

NfcAdapterService也是NfcService的内部类,并继承于INfcAdapter.stub,同样客户端可以通过Binder通信获取到NfcAdapterService的实例mNfcAdapterNfcAdapterService也是暴露给客户端的主要接口,主要完成对NFC的使能初始化,扫面读写tag,派发tag消息等。

NativeNfcManager类就像其名字一样,主要负责native JNI的管理。

NfcDispatcher主要负责tag消息处理,并派发Intent消息,启动Activity

3.2.2 NfcAdapter客户端初始化

ContextImpl类中,有一个静态模块,在这里创建了NfcManager的实例,并注册到服务中,代码如下:

Static{

registerService(NFC_SERVICE, newServiceFetcher() {

               public Object createService(ContextImpl ctx) {

                   return newNfcManager(ctx);

               }});

}

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

public static synchronizedNfcAdapter getNfcAdapter(Context context) {

       ……

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

       ……

           try {

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

           } catch (RemoteException e) {

           }

       ……

       NfcAdapter adapter = sNfcAdapters.get(context);

       if (adapter == null) {

           adapter =newNfcAdapter(context);

           sNfcAdapters.put(context, adapter);

       }

       return adapter;

}

private static INfcAdaptergetServiceInterface() {//获取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的实例。

4 启动NFC流程

4.1 时序图

NFC framework introduce(一)_第3张图片

4.2 代码分析

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

public boolean enable(){

       try {

           returnsService.enable();//调用了远程服务NfcAdapterServiceenable方法

       } catch (RemoteException e) {

       }

   }

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

    public boolean enable() throws RemoteException {

          ……

           newEnableDisableTask().execute(TASK_ENABLE);

           return true;

       }

EnableDisableTaskNfcService的一个内部类,继承于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硬件的初始化。

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

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

public voidenableDisable(boolean sendEnable, boolean receiveEnable){

           if (!mIsReceiveEnabled &&receiveEnable) {

               mDefaultSnepServer.start();

               mNdefPushServer.start();

               ……

           } else ……

   }

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

public void start(){

      mServerThread = new ServerThread();

      mServerThread.start();

      mServerRunning = true;

   }

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

  publicvoid 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;

                       }

                   LlcpSocketcommunicationSocket =serverSocket.accept();//创建Socket

                       if (communicationSocket != null) {

                           int miu = communicationSocket.getRemoteMiu();

                    newConnectionThread(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设备的初始化,下面就需要去扫描查询tagP2p设备。

本调用applyRouting(true)开始扫描tagP2p消息。

void applyRouting(booleanforce) {

        ……

           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中注册了回调函数,当扫描到tagp2p后,将回调Java层函数。如果发现Tag设备,将会回调NativeManager.notifyNdefMessageListeners()方法,如果发现P2p设备,将会回调NativeManager.notifyLlcpLinkActivation()方法。JNI代码我们就不分析了,我们就主要关注这两个方法就可以了:

  privatevoid notifyNdefMessageListeners(NativeNfcTag tag){             

       mListener.onRemoteEndpointDiscovered(tag);

}

 

 privatevoid notifyLlcpLinkActivation(NativeP2pDevice device){

       mListener.onLlcpLinkActivated(device);

   }

 

5 NDEF数据读写流程

5.1 小数据量的传送

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

5.1.1读写流程图



5.1.2 数据写流程
5.1.2.1时序图

NFC <wbr>framework <wbr>introduce(一)NFC framework introduce(一)_第4张图片

5.1.2.2代码分析

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

public voidsetNdefPushMessage(NdefMessage message, Activityactivity,)//

 

public voidsetNdefPushMessageCallback(CreateNdefMessageCallback callback,Activity activity,)

public interfaceCreateNdefMessageCallback {

       public NdefMessage createNdefMessage(NfcEvent event);

   }

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

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

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

  privatevoid notifyLlcpLinkActivation(NativeP2pDevice device) {

       mListener.onLlcpLinkActivated(device);

   }

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

  publicvoid onLlcpLinkActivated(NfcDepEndpoint device) {

       sendMessage(NfcService.MSG_LLCP_LINK_ACTIVATION,device);

   }

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

public voidonLlcpActivated() {

           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事件,mEventListenerP2pEventManager的实例,看看其代码:

public voidonP2pSendConfirmationRequested() {

       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()方法:

  publicvoid onSendConfirmed() {

       if (!mSending) {

           mSendUi.showStartSend();

           mCallback.onP2pSendConfirmed();

       }

   }

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

   public void onP2pSendConfirmed() {

      sendNdefMessage();

}

void sendNdefMessage(){

     synchronized (this) {

        cancelSendNdefMessage();

        mSendTask = newSendTask();

        mSendTask.execute();

       }

 }

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

public VoiddoInBackground(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 = newNdefPushClient().push(m);

               }else {

                   result = false;

               }

           }

         

       }

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

static intdoSnepProtocol(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 toNPP.

       }

   }

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

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

5.1.3 数据读流程
5.1.3.1 时序图
NFC framework introduce(一)_第5张图片

5.1.3.2 代码分析

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

ConnectionThread(LlcpSocketsocket, int fragmentLength) {

           super(TAG);

           mSock = socket;

           mMessager = newSnepMessenger(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()完成数据的处理,我们注意到有两个参数,第一个mMessagerSnepMessenger的实例,在ConnectionThread的构造函数被创建的。看看SnepServer.handlerRequest()方法吧:

static booleanhandleRequest(SnepMessenger messenger, Callback callback) throwsIOException {

       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()将数据传送到P2pLinkManagerCallback接口在P2pLinkManager被实现了:

  finalSnepServer.Callback mDefaultSnepCallback = newSnepServer.Callback() {

       @Override

       public SnepMessagedoPut(NdefMessagemsg) {

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

           returnSnepMessage.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 booleanhandleMessage(Message msg) {

     case MSG_RECEIVE_COMPLETE:

               NdefMessage m = (NdefMessage) msg.obj;

                   mEventListener.onP2pReceiveComplete(true);

                   NfcService.getInstance().sendMockNdefTag(m);

               }

               break;

}

调用了NfcServicesendMockDefTag()方法:

public voidsendMockNdefTag(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 });

                   booleandelivered = mNfcDispatcher.dispatchTag(tag);

                   break;

               }

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

public booleandispatchTag(Tag tag) {

       NdefMessage message = null;

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

       if (ndef != null) {

           message = ndef.getCachedNdefMessage();

       }

       ……

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

       ……

       if (tryNdef(dispatch, message)){

           return true;

       }……

       return false;

}

public DispatchInfo(Contextcontext, 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的实例,并将tagmessage封装到Intent中,供Activity读取。这里还将NdefMessage转换为urimime,这两个数据也是用来找Activity的一个参数。

然后调用NfcDispatcher.tryNdef()尝试发送NDEF消息启动Activity,这个能成功启动。代码如下:

 publicIntent setNdefIntent() {

           intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);

           if (ndefUri != null) {

               intent.setData(ndefUri);

               returnintent;

           } else if (ndefMimeType != null) {

               intent.setType(ndefMimeType);

               return intent;

           }

           return null;

}

boolean tryNdef(DispatchInfodispatch, NdefMessage message) {

       dispatch.setNdefIntent();//设置ActionACTION_NDEF_DISCOVERED

 

       // Try to start AAR activity with matchingfilter

       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 regularlaunch 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 applicationlaunch");

               return true;

           }

           // Find the package inMarket:

           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设置IntentActionACTION_NDEF_DISCOVERED,如果前面读取的ndefUrindefMimeType不为空,那么设置到Intent里。

下面的代码,有四种方式处理数据发送不同的Intent。我们需要注意的是,首先需要调用extractAarPackages()NdefMessage数据转换层系统合法的包名,下面看4种处理的方式:

n        第一种为AAR,为 Android ApplicationRecord的简写

如果作为P2p的发送端,调用NdefRecord.createApplicationRecord (String packageName)将包名封装到Ndefmessage中,意思是只允许包名为packageNameApk处理数据。那么现在我们分析的是接收端的代码,解析NdefMessage数据,将其转换成合法的包名。如果包名存在于当前的系统中,那么就启动该Apk来处理数据。所以对于AAR,接收数据的包名必须是packageNameActivityACTION必须包含ACTION_NDEF_DISCOVERED,数据类型必须满足ndefUrindefMimeType

n        以包名封装Intent

如果NdefMessage中能转换成合法的包名,且前面的Intent没有Activity响应,那么就需要一包名封装的Intent启动Activity。这样情况是把当前正在运行的apk的包名发送给其他设备,其他设备将启动该apk

n        在第二种Intent的情况下,如果接收设备没有该Apk,那么将通过Intent启动google play去下载该Apk

n        第四种为正常类型,通过ActionACTION_NDEF_DISCOVERED、及ndefUrindefMimeType去启动Activity。有可能多个Activity响应的。

你可能感兴趣的:(NFC framework introduce(一))