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架構,其框架圖如下:

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中。詳細代碼就不分析了,感興趣就自己看。

 

 

 

 

 

 

 

 

 

參考:

http://www.eoeandroid.com/thread-173822-1-1.html

http://www.cnblogs.com/doandroid/archive/2011/11/29/2267404.html

http://blog.csdn.net/wchinaw/article/details/6542831

http://leave001.blog.163.com/blog/static/16269129320122283648327/

http://wiki.eoeandroid.com/index.php?title=NFC_Basics&diff=4589&oldid=4580

http://doandroid.info/android中nfc功能解析及代碼演示/

P2p設備http://blog.csdn.net/karen2lotus/article/details/7922948

NFC理解很好:http://www.360doc.com/content/11/0524/13/474846_119019554.shtml


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章