zoukankan      html  css  js  c++  java
  • NFC framework

    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架构,其框架图如下:

    NFC <wbr>framework <wbr>introduce(一)

    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 <wbr>framework <wbr>introduce(一)

    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 = 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的数据类型,然后发送给其他设备。在接收设备的同样的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 <wbr>framework <wbr>introduce(一)

    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

        }

    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, 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的实例。

    4 启动NFC流程

    4.1 时序图

    NFC <wbr>framework <wbr>introduce(一)

    4.2 代码分析

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

    public boolean enable() {

            try {

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

            } catch (RemoteException e) {

            }

        }

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

         public boolean enable() throws RemoteException {

               ……

                new EnableDisableTask().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 void enableDisable(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()方法,为了方便理解,剪切了不少代码:

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

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

    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中注册了回调函数,当扫描到tagp2p后,将回调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 <wbr>framework <wbr>introduce(一)

    5.1.2 数据写流程
    5.1.2.1时序图

    NFC <wbr>framework <wbr>introduce(一)

    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设备的时候,将会回调javaNativeNfcManagernotifyLlcpLinkActivation()方法:

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

    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的内部类,看看SendTaskdoInBackground()方法:

    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 <wbr>framework <wbr>introduce(一)
    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()完成数据的处理,我们注意到有两个参数,第一个mMessagerSnepMessenger的实例,在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()将数据传送到P2pLinkManagerCallback接口在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;

    }

    调用了NfcServicesendMockDefTag()方法:

    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的实例,并将tagmessage封装到Intent中,供Activity读取。这里还将NdefMessage转换为urimime,这两个数据也是用来找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();//设置ActionACTION_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设置IntentActionACTION_NDEF_DISCOVERED,如果前面读取的ndefUrindefMimeType不为空,那么设置到Intent里。

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

    n         第一种为AAR,为 Android Application Record的简写

    如果作为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响应的。


    5.2 
    大数据量的传送

    大数据量的传送,是指图片等数据量比较大的资源,需要通过NFC启动蓝牙的匹配,通过蓝牙来传送数据。

    5.2.1 读写流程图

    NFC <wbr>framework <wbr>introduce(二)

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

    NFC <wbr>framework <wbr>introduce(二)

       大数据量的写操作跟小数据量的类似,我们这里主要关注差异的部分,我们从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 <wbr>framework <wbr>introduce(二)
    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),发送响应数据给请求端。

    请求端接收到相应数据后,就开始通过匹配蓝牙发送图片等大数据量的数据了。简单吧。

     

    6 Tag设备读写流程

    6.1 Tag设备读写流程图

    NFC <wbr>framework <wbr>introduce(二)

    6.2 时序图

    NFC <wbr>framework <wbr>introduce(二)

    6.2 代码分析

    4.2中,NFC的启动将调用NativeNfcManager.enableDiscovery(),然后将调用到JNI方法com_android_nfc_NfcManager_enableDiscovery()扫描tagP2p设备。在该方法中,调用phLibNfc_RemoteDev_NtfRegister()方法注册回调函数nfc_jni_Discovery_notification_callback()。当扫面到tagP2p的时候将回调该方法。下面看看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_TAGHandler消息到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消息用于构造一个TagtagEndpoint.getTechList()取出该Tag设备支持的technologymNfcDispatcherNfcDispatcher的实例,用于向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();//设置IntentActionACTION_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();//获取ActionACTION_TECH_DISCOVEREDActivity列表

     

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

        }

    第一步,还是需要设置IntentActionACTION_TECH_DISCOVERED

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

    第三步,读取系统中ActionACTION_TECH_DISCOVEREDActivity列表,并获取该Apk支持解析的technology列表,这个是在apkXml文件中定义的:

    <?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设备technologyApk添加到matches列表中

    第五步,如果只有一个Activity满足条件,直接启动该Activity

    第六步,如果有多个Activity满足条件,发送Intent消息供用户选择启动哪里Activity

     

    到此,Tag消息就已经传送到Activity了。那么接下来就需要在Activity中,发起对Tag设备的读写了。

    6.3 Activity中发起对Tag设备读写

    6.3.1 三个Intent启动Activity

    当设备扫描发现有tagP2p设备的时候,将数据封装好后发送Intent启动Activity处理数据,有三类型Intent

    n         ACTION_NDEF_DISCOVERED :处理NDEF数据,包含MIMEURI数据类型

    n         ACTION_TECH_DISCOVERED :处理非NDEF数据或者NDEF数据但不能映射为MIMEURI数据类型

    n         ACTION_TAG_DISCOVERED :如果没有Activity相应上面两个Intent,就由该Intent处理

    官网上调度图如下:

    NFC <wbr>framework <wbr>introduce(二)

    6.3.2 Activity中对Tag设备读写

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

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

    我们以MifareUltralighttechnology类型为例子,根据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中。详细代码就不分析了,感兴趣就自己看。

     

     


  • 相关阅读:
    重定向管道
    系统安全
    Linux启动流程
    压缩解压
    Vim
    ssh
    sudo
    Raid
    rsync
    quota
  • 原文地址:https://www.cnblogs.com/oracleloyal/p/5633807.html
Copyright © 2011-2022 走看看