NFC framework introduce(一)

  NFC framework introduce

 

1 NFC 簡介

對於NFC,是googleandroid4.0上推出來的,簡單介紹下近場通訊(NFC)是一系列短距離無線技術,一般需要4cm或者更短去初始化連接。近場通訊(NFC)允許你在NFC tagAndroid設備或者兩個Android設備間共享小負載數據。優酷上有其應用的視頻:http://v.youku.com/v_show/id_XMjM3ODE5ODMy.html

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

2 總體框架

   對於NFC框架的設計,同樣是android的標準的c/s架構,其框架圖如下:



n        客戶端:android提供了兩個API包給apk,分別是android.nfc.techandroid.nfc,實現了NFC的應用接口,代碼路徑frameworks/base/core/java/android/nfc/techframeworks/base/core/java/android/nfc

n        服務端:packages/apps/Nfc是一個類似電話本的應用,這個程序在啓動後自動運行,並一直運行,作爲NFC的服務進程存在,是NFC的核心。

在這個程序代碼中,有個JNI庫,供NfcService調用,代碼路徑是packages/apps/Nfc/jni/

n        庫文件:代碼路徑是external/libnfc-nxp,C編寫的庫文件,有兩個庫,分別是libnfc.solibnfc_ndef.solibnfc.so是一個主要的庫,實現了NFC stack的大部分功能,主要提供NFC的服務進程調用。libnfc_ndef是一個很小的庫,主要是實現NDEF消息的解析,供framework調用

2.1 總體類圖關係



2.2 數據分類

NFC按照發展,分爲兩種,NFC basicsAdvanced NFC。從字面上理解,第一種是最先設計的,第二種是在原來基礎上擴展的。

2.2.1 NFC basics

是一種點對點(P2P)數據交換的功能,傳送的數據格式是NDEF,是Nfc Data Exchange Format的縮寫,這個數據格式用於設備之間點對點數據的交換,例如網頁地址、聯繫人、郵件、圖片等。對於除圖片以外的數據,數據量比較小,直接封裝在類NdefMessage中,通過NFCNdefMessage類型數據發送到另一臺設備,而對於圖片這樣數據量比較大的數據,需要構建一個標準的NdefMessage數據,發送給另外一臺設備,等有迴應之後,再通過藍牙傳送數據。

NdefMessage類是用於數據的封裝,其包含有一個或多個NdefRecord類,NdefRecord纔是存儲數據的實體,將聯繫人、郵件、網頁地址等轉換才byte類型的數據存儲在NdefRecord當中,並且包含了數據類型。舉個例子吧:

NdefRecord uriRecord = newNdefRecord(

   NdefRecord.TNF_ABSOLUTE_URI ,

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

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

newNdefMessage(uriRecord);

 

以上是用NdefMessage對一個NdefRecord數據進行分裝。

爲了更好理解數據的傳送方式,需要更細的分爲三種:

n        在一個Apk中,用NdefMessage封裝Apk的數據,在設置NdefRecord的數據類型,然後發送給其他設備。在接收設備的同樣的APKAndroidManifest文件中設置接收數據的類型,這樣通過Intent消息就能找到對應的Activity啓動。

n        直接將當前運行(home程序外)Apk的包名封裝到NdefMessage中,發送給其他設備。接收設備收到NdefMessage數據,轉換才成包名,根據包名構造Intent,啓動指定的Activity。如果包名不存在,那麼會啓動google play去下載安裝該Apk

n        圖片爲數據量比較大的數據。需要封裝一個標準的NdefMessage數據發送給其他設備,當有迴應的時候,在將圖片數據通過藍牙發送給其他設備。

 

按照上面的分析,還可以將數據傳送,分爲小數據量的傳送和大數據量的傳送。小數據量是指聯繫人、郵件、網頁地址、包名等,而大數據量是指圖片等,需要通過藍牙傳送的。那麼爲什麼NFC的功能還要藍牙傳送呢?原因是NFC的設計本來就是爲了傳送小的數據量,同我們通過NFC啓動藍牙傳圖片,更方便的不需要手動進行藍牙的匹配,只需要將手機貼在一起就可以完成了藍牙的匹配動作。

2.2.2 Advanced NFC

對於該類型的NFC,也是在點對點數據交換功能上的一個擴充,我們日常接觸的有公交卡、飯卡,手機設備可以通過NFC功能讀取該卡內的數據,也有支持NFC設備往這類卡里面寫數據。所以,我們將這些卡類稱爲Tag

   需要直接通過Byte格式進行數據封裝,對TAG數據進行讀寫。市面上有很多的卡,估計沒個城市用的公交卡都不一樣,就是使用的標準不一樣,所以在 android.nfc.tech包下支持了多種technologies,如下圖:

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


   tag設備與手機足夠近的時候,手機設備首先收到了Tag信息,裏面包含了當前Tag設備所支持的technology,然後將Tag信息發送到指定的Activity中。在Activity中,將讀取Tag裏面的數據,構造相應的technology,然後再以該technology的標準,對tag設備進行讀寫。

3初始化流程

3.1 時序圖


3.2 代碼分析

    初始化分兩部分,第一是服務端的初始化,並將服務添加到ServiceManager中,第二是初始化NFC適配器NfcAdapter

3.2.1 Server端初始化

NFC的服務端代碼在packages/apps/Nfc中,並且還包含了JNI代碼,前面也介紹過,NFC的服務端是一個應用程序,跟隨系統啓動並一直存在的一個服務進程。

NfcService繼承於Application,當程序啓動的時候,調用onCreate()方法,代碼如下:

public void onCreate(){

       super.onCreate();

       mNfcTagService = newTagService();

       mNfcAdapter = new NfcAdapterService();

       mExtrasService = new NfcAdapterExtrasService();

       ……

       mDeviceHost = new NativeNfcManager(this, this);

       mNfcDispatcher = new NfcDispatcher(this,handoverManager);

       mP2pLinkManager = new P2pLinkManager(mContext,handoverManager);

       ……

       ServiceManager.addService(SERVICE_NAME,mNfcAdapter);//mNfcAdapter添加到系統服務列表中。

       …….

       new EnableDisableTask().execute(TASK_BOOT);  // doblocking boot tasks

   }

TagServiceNfcService的內部類,並繼承於INfcTag.stub,因此客戶端可以通過Binder通信獲取到TagService的實例mNfcTagService。其主要的功能是完成tag的讀寫。

NfcAdapterService也是NfcService的內部類,並繼承於INfcAdapter.stub,同樣客戶端可以通過Binder通信獲取到NfcAdapterService的實例mNfcAdapterNfcAdapterService也是暴露給客戶端的主要接口,主要完成對NFC的使能初始化,掃面讀寫tag,派發tag消息等。

NativeNfcManager類就像其名字一樣,主要負責native JNI的管理。

NfcDispatcher主要負責tag消息處理,並派發Intent消息,啓動Activity

3.2.2 NfcAdapter客戶端初始化

ContextImpl類中,有一個靜態模塊,在這裏創建了NfcManager的實例,並註冊到服務中,代碼如下:

Static{

registerService(NFC_SERVICE, newServiceFetcher() {

               public Object createService(ContextImpl ctx) {

                   return newNfcManager(ctx);

               }});

}

   NfcManager的構造函數中,調用了NfcAdapter.getNfcAdapter(context),創建NFC Adapter

public static synchronizedNfcAdapter getNfcAdapter(Context context) {

       ……

           sService =getServiceInterface();//獲取NFC服務接口

       ……

           try {

               sTagService= sService.getNfcTagInterface();//獲取NFC tag服務接口

           } catch (RemoteException e) {

           }

       ……

       NfcAdapter adapter = sNfcAdapters.get(context);

       if (adapter == null) {

           adapter =newNfcAdapter(context);

           sNfcAdapters.put(context, adapter);

       }

       return adapter;

}

private static INfcAdaptergetServiceInterface() {//獲取NFC服務接口

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

       if (b == null) {

           return null;

       }

       return INfcAdapter.Stub.asInterface(b);

}

我們看看getServiceInterface()方法,在3.2.1我們也看到了,調用ServiceManager.addService()NfcAdapterService的實例添加到系統的服務列表中,這裏我們調用了ServiceManager.getService(“nfc”)獲取到了服務端的NfcAdapterService對象的實例。

NfcAdapterService類中提供了getNfcTagInterface接口,用於獲取遠程服務端的TagService對象的實例。

如果一切正常,那麼將創建NfcAdapter的實例,在其構造函數中,創建了NfcActivityManager的實例。

4 啓動NFC流程

4.1 時序圖


4.2 代碼分析

如果android設備有NFC硬件支持,那麼將在設置應用的出現“無線和網絡à更多àNFC”選項,點擊將使能NFC功能。其實就是調用了NfcAdapter.enable()方法,代碼如下:

public boolean enable(){

       try {

           returnsService.enable();//調用了遠程服務NfcAdapterServiceenable方法

       } catch (RemoteException e) {

       }

   }

NfcAdapterService.enable()方法中,創建了一個Task任務來完成使能工作,代碼如下:

    public boolean enable() throws RemoteException {

          ……

           newEnableDisableTask().execute(TASK_ENABLE);

           return true;

       }

EnableDisableTaskNfcService的一個內部類,繼承於AsyncTask,一個異步的任務線程,實際工作的doInBackground方法中。根據了TASK_ENABLE參數,選擇調用到了EnableDisableTask.enableInternal()完成NFC功能的使能,代碼如下:

      boolean enableInternal() {

          ……

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

               return false;

           }

           synchronized(NfcService.this) {

               mObjectMap.clear();

               mP2pLinkManager.enableDisable(mIsNdefPushEnabled,true);//P2p功能的啓動

               updateState(NfcAdapter.STATE_ON);

           }

           initSoundPool();

           

           applyRouting(true);//開始掃描

           return true;

       }

mDeviceHost其實是NativeNfcManager的實例,其繼承於DeviceHost。調用了其initialize()方法,接着調用JNI方法doInitialize(),完成對NFC硬件的初始化。

硬件初始化完成之後,就需要初始化P2pLiskManagerP2p就是點對點傳送的意思。這裏初始化,需要創建讀取數據線程,以及socket的創建。

下面看看P2pLinkManager.enableDisable(),啓動P2p功能:

public voidenableDisable(boolean sendEnable, boolean receiveEnable){

           if (!mIsReceiveEnabled &&receiveEnable) {

               mDefaultSnepServer.start();

               mNdefPushServer.start();

               ……

           } else ……

   }

這裏啓動了兩個服務,分別是SnepServerNdefPushServer,但是在實際使用過程中優先使用SnepServer服務,只有當其使用失敗的時候,纔會用到NdefPushServer服務。所以,我們這裏就看SnepServer就可以了,NdefPushServer也比較相似。SnepServer.start()

public void start(){

      mServerThread = new ServerThread();

      mServerThread.start();

      mServerRunning = true;

   }

代碼非常的簡單,ServerThread是繼承與Thread的,且是SnepServer的內部類。看看其run()方法,爲了方便理解,剪切了不少代碼:

  publicvoid run() {

           while (threadRunning) {

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

               try {

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

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

                   while (threadRunning) {

                       LlcpServerSocket serverSocket;

                       synchronized (SnepServer.this) {

                           serverSocket= mServerSocket;

                       }

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

                       if (communicationSocket != null) {

                           int miu = communicationSocket.getRemoteMiu();

                    newConnectionThread(communicationSocket,fragmentLength).start();

                       }

                   }

           }

       }

這裏主要是完成了Socket的創建,這個Socket是用於接收其他設備發送過來的數據的,ConnectionThread也是SnepServer的內部類,繼承與Thread,看看其run()函數:

public void run(){

           try {……

            while (running) {

                   if (!handleRequest(mMessager,mCallback)) {

                       break;

                   }

                   synchronized (SnepServer.this) {

                       running = mServerRunning;

                   }

               }

           }

       }

這個是一個連接線程,與客戶端的Socket連接,如果有接收到Socket發送的數據的時候,就用handlerRequest處理數據。

以上已經完成了P2p設備的初始化,下面就需要去掃描查詢tagP2p設備。

本調用applyRouting(true)開始掃描tagP2p消息。

void applyRouting(booleanforce) {

        ……

           try {

              ……

               // configure NFC-EE routing

               if (mScreenState >= SCREEN_STATE_ON_LOCKED&&

                       mEeRoutingState == ROUTE_ON_WHEN_SCREEN_ON) {

                   if (force || !mNfceeRouteEnabled) {

                       mDeviceHost.doSelectSecureElement();

                   }

               } else {

                   if (force ||  mNfceeRouteEnabled) {

                       mDeviceHost.doDeselectSecureElement();

                   }

               }

               // configure NFC-C polling

               if (mScreenState >= POLLING_MODE) {

                   if (force || !mNfcPollingEnabled) {

                       mDeviceHost.enableDiscovery();

                   }

               } else {

                   if (force || mNfcPollingEnabled) {

                       mDeviceHost.disableDiscovery();

                   }

               }

           } finally {

               watchDog.cancel();

           }

       }

   }

這裏我們關注NativeNfcManager.enableDiscovery()方法,最終調用到JNI中,在JNI中註冊了回調函數,當掃描到tagp2p後,將回調Java層函數。如果發現Tag設備,將會回調NativeManager.notifyNdefMessageListeners()方法,如果發現P2p設備,將會回調NativeManager.notifyLlcpLinkActivation()方法。JNI代碼我們就不分析了,我們就主要關注這兩個方法就可以了:

  privatevoid notifyNdefMessageListeners(NativeNfcTag tag){             

       mListener.onRemoteEndpointDiscovered(tag);

}

 

 privatevoid notifyLlcpLinkActivation(NativeP2pDevice device){

       mListener.onLlcpLinkActivated(device);

   }

 

5 NDEF數據讀寫流程

5.1 小數據量的傳送

小數據量的傳送,指的是傳送聯繫人、網頁地址、郵件、包名等,數據量比較小,可以直接用。

5.1.1讀寫流程圖



5.1.2 數據寫流程
5.1.2.1時序圖

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

5.1.2.2代碼分析

NfcAdapter提供了兩個接口給應用程序設置推送的數據:

public voidsetNdefPushMessage(NdefMessage message, Activityactivity,)//

 

public voidsetNdefPushMessageCallback(CreateNdefMessageCallback callback,Activity activity,)

public interfaceCreateNdefMessageCallback {

       public NdefMessage createNdefMessage(NfcEvent event);

   }

第一種是直接在Apk中完成NdefMessage數據的封裝,調用setNdefPushMessage()進行設置,第二種是通過註冊回調的方式,創建NdefMessage數據。這兩個方式都一樣,都需要將創建好的數據存放在NfcActivityState. ndefMessage變量中,等待着NfcService來取。NfcActivityState數據NfcActivityManager的內部類,每個Apk進行數據推送設置時,都會創建對應的NfcActivityState實例,該實例的ndefMessage變量就是用來存放封裝好的NdefMessage數據的。

這裏我需要說的是,當APK正在運行的時候,就已經完成了數據的封裝,此時如果發現NFC設備,那麼NfcService將取出數據進行推送。

前面介紹了NFC啓動流程的時候,說到了在JNI中完成了回調函數的註冊,當發現有P2p設備的時候,將會回調javaNativeNfcManagernotifyLlcpLinkActivation()方法:

  privatevoid notifyLlcpLinkActivation(NativeP2pDevice device) {

       mListener.onLlcpLinkActivated(device);

   }

這裏的mListener其實是NfcService的實例,構造NativeNfcManager的時候註冊進來的,那麼將調用NfcService. onLlcpLinkActivated():

  publicvoid onLlcpLinkActivated(NfcDepEndpoint device) {

       sendMessage(NfcService.MSG_LLCP_LINK_ACTIVATION,device);

   }

發送Handler消息MSG_LLCP_LINK_ACTIVATION,那麼將在NfcServiceHandler.handleMessage()中處理該消息,其是NfcService的內部類。接着調用了NfcServiceHandler.llcpActivated().然後調用P2pLinkManager.onLlcpActivated(),我們看看:

public voidonLlcpActivated() {

           switch (mLinkState){

               case LINK_STATE_DOWN:

                   mEventListener.onP2pInRange();

                   prepareMessageToSend();

                   if (mMessageToSend != null ||

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

                       mSendState = SEND_STATE_NEED_CONFIRMATION;

                       mEventListener.onP2pSendConfirmationRequested();

                   }

                   break;

 }

void prepareMessageToSend() {

           if (mCallbackNdef != null) {

               try {

                   mMessageToSend =mCallbackNdef.createMessage();//取出Apk中準備的數據

                   mUrisToSend = mCallbackNdef.getUris();//大數據量的數據

                   return;

               }catch (RemoteException e) {

                   // Ignore

               }

           }

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

           if (tasks.size() > 0) {

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

               if(beamDefaultDisabled(pkg)) {//判斷當前運行的是否是Home程序

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

                   mMessageToSend = null;

               } else {

                   mMessageToSend =createDefaultNdef(pkg);//將當前運行的包名數據封裝在NdefMessage中。

               }

           } else {

               mMessageToSend = null;

           }

       }

   }

這裏我們需要關注prepareMessageToSend()方法,這個方法就是完成準備將要被髮送的數據。這裏面有三種數據需要取,對於小數據量,我們只關注其中兩種。

n        第一種,在當前運行Apk中準備有數據,mCallbackNdef變量其實是NfcActivityManager的實例,是當前運行的Apk設置的,通過Binder通信調用了其createMessage()方法,取出了當前運行Apk設置在NfcActivityState. ndefMessage變量中的數據。

n        第二種,是當前Apk沒有準備有推送的數據,那麼就將其包名作爲數據,封裝在NdefMessage

數據準備好之後,暫時存放在P2pLinkManager. mMessageToSend變量中。

數據準備好後,將調用mEventListener.onP2pSendConfirmationRequested();發送P2p事件,mEventListenerP2pEventManager的實例,看看其代碼:

public voidonP2pSendConfirmationRequested() {

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

               & Configuration.UI_MODE_TYPE_MASK;

       if (uiModeType == Configuration.UI_MODE_TYPE_APPLIANCE){

           mCallback.onP2pSendConfirmed();

       } else {

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

       }

   }

根據模式的選擇,調用到SendUi.showPreSend()方法,這個方法完成的功能是縮小屏幕供用戶點擊,當用戶點擊的時候才能推送數據,點擊的時候,將回調P2pEventManager.onSendConfirmed()方法:

  publicvoid onSendConfirmed() {

       if (!mSending) {

           mSendUi.showStartSend();

           mCallback.onP2pSendConfirmed();

       }

   }

mCallback其實是P2pLinkManager的實例,調用onP2pSendConfirmed():

   public void onP2pSendConfirmed() {

      sendNdefMessage();

}

void sendNdefMessage(){

     synchronized (this) {

        cancelSendNdefMessage();

        mSendTask = newSendTask();

        mSendTask.execute();

       }

 }

調用了sendNdefMessage(),在該方法中,創建了SendTask實例,其繼承於Task,且是P2pLinkManager的內部類,看看SendTaskdoInBackground()方法:

public VoiddoInBackground(Void... args) {

           NdefMessage m;

           Uri[] uris;

           boolean result;

           synchronized (P2pLinkManager.this) {

               m = mMessageToSend;

               uris = mUrisToSend;

           }

           try {

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

           } catch (IOException e) {

               if (m != null) {

                   result = newNdefPushClient().push(m);

               }else {

                   result = false;

               }

           }

         

       }

在異步在Task中,開始數據的推送。doSnepProtocol方法其實是通過SnepServer服務完成推送,而只有其出現異常的時候纔會啓用NdefPushServer完成推送。看看P2pLinkManagerdoSnepProtocol().

static intdoSnepProtocol(HandoverManager handoverManager,

           NdefMessage msg, Uri[] uris) throws IOException {

       SnepClient snepClient =new SnepClient();

       try {

           snepClient.connect();

       } catch (IOException e) {

       }

 

       try {

           if (uris != null){//小數據量,uris爲空

             ……

           } else if (msg != null) {

               snepClient.put(msg);

           }

           return SNEP_SUCCESS;

       } catch (IOException e) {

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

       }

   }

在該方法中,構建了一個SnepClient的實例,變調用snepClient.connect()其實就是創建了Socket的客戶端,並使其連接起來,通過Socket將數據推送 

對於小數據量,uris爲空,mgs不爲空。調用snepClient.put(msg)了開始數據的推送。                                                                                                                                                                                                                                      

5.1.3 數據讀流程
5.1.3.1 時序圖


5.1.3.2 代碼分析

在前面接收啓動NFC流程的時候,提到了P2pLinkManager的初始化,在初始化中,啓動了一個線程,用於接收數據的,我們看看SnepServer. ConnectionThread線程的run函數:

ConnectionThread(LlcpSocketsocket, int fragmentLength) {

           super(TAG);

           mSock = socket;

           mMessager = newSnepMessenger(false, socket, fragmentLength);

       }

public void run(){

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

           try {

               boolean running;

               synchronized (SnepServer.this) {

                   running = mServerRunning;

               }

               while (running) {

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

                       break;

                   }

                   synchronized (SnepServer.this) {

                       running = mServerRunning;

                   }

               }

           } catch (IOException e) {

           }

       }

開啓線程接收數據,在handlerRequest()完成數據的處理,我們注意到有兩個參數,第一個mMessagerSnepMessenger的實例,在ConnectionThread的構造函數被創建的。看看SnepServer.handlerRequest()方法吧:

static booleanhandleRequest(SnepMessenger messenger, Callback callback) throwsIOException {

       SnepMessage request;

       try {

           request =messenger.getMessage();//真正的讀數據

       } catch (SnepException e) {

           ……

           return false;

       }

 

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

        ……

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

         //在需要藍牙傳送大量數據的時候,用到的

         ……

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

           messenger.sendMessage(callback.doPut(request.getNdefMessage()));//回調doput方法,傳送數據

       } else {

           ……

       }

       return true;

   }

SnepMessenger類其實是將數據有封裝了一層到SnepMessage,調用SnepMessenger.getMessage,通過Socket讀取到數據,調用SnepMessage.getNdefMessage將讀取到的數據轉換成NdefMessage,然後調用callback.doPut()將數據傳送到P2pLinkManagerCallback接口在P2pLinkManager被實現了:

  finalSnepServer.Callback mDefaultSnepCallback = newSnepServer.Callback() {

       @Override

       public SnepMessagedoPut(NdefMessagemsg) {

           onReceiveComplete(msg);//處理NdefMessage數據

           returnSnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS);

       }

 

       @Override

       public SnepMessage doGet(int acceptableLength, NdefMessage msg){

           NdefMessage response =mHandoverManager.tryHandoverRequest(msg);

       }

};

void onReceiveComplete(NdefMessage msg) {

       // Make callbacks on UI thread

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

   }

onReceiveComplete(msg)中,通過發送Handler消息對MSG_RECEIVE_COMPLETE,將NdefMessage數據的繼續往上傳。在P2pLinkManager. handleMessage()接收處理消息:

public booleanhandleMessage(Message msg) {

     case MSG_RECEIVE_COMPLETE:

               NdefMessage m = (NdefMessage) msg.obj;

                   mEventListener.onP2pReceiveComplete(true);

                   NfcService.getInstance().sendMockNdefTag(m);

               }

               break;

}

調用了NfcServicesendMockDefTag()方法:

public voidsendMockNdefTag(NdefMessage msg) {

       sendMessage(MSG_MOCK_NDEF, msg);

}

再一次發送Handler消息,將msg傳到NfcServiceHandler中,代碼如下:

case MSG_MOCK_NDEF:{

                   NdefMessage ndefMsg = (NdefMessage) msg.obj;

                   Bundle extras = new Bundle();

                   extras.putParcelable(Ndef.EXTRA_NDEF_MSG,ndefMsg);

                   …….

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

                           new int[] { TagTechnology.NDEF },

                           new Bundle[] {extras });

                   booleandelivered = mNfcDispatcher.dispatchTag(tag);

                   break;

               }

在這裏,對NdefMessage數據進行了一次封裝,將其封裝到Tag裏面,然後調用NfcDispatcher.dispatchTag()派發Tag數據。詳細代碼如下:

public booleandispatchTag(Tag tag) {

       NdefMessage message = null;

       Ndef ndef = Ndef.get(tag);//前面調用Tag.createMockTag創建Tag實例的時候

       if (ndef != null) {

           message = ndef.getCachedNdefMessage();

       }

       ……

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

       ……

       if (tryNdef(dispatch, message)){

           return true;

       }……

       return false;

}

public DispatchInfo(Contextcontext, Tag tag, NdefMessage message) {

           intent = new Intent();

           intent.putExtra(NfcAdapter.EXTRA_TAG, tag);

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

           if (message != null) {

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

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

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

           } else {

               ndefUri= null;

               ndefMimeType = null;

           }

 

}

前面調用Tag.createMockTag創建Tag實例的時候,帶有TagTechnology.NDEF參數,已經說明了Tag支持的數據類型是NDEF,所以這裏調用Ndef.get(tag)返回的ndef不爲空,message也不爲空,我之前讀取的NdefMessage數據。

接着夠着了一個DispatchInfo的實例,在構造函數中,創建了Intent的實例,並將tagmessage封裝到Intent中,供Activity讀取。這裏還將NdefMessage轉換爲urimime,這兩個數據也是用來找Activity的一個參數。

然後調用NfcDispatcher.tryNdef()嘗試發送NDEF消息啓動Activity,這個能成功啓動。代碼如下:

 publicIntent setNdefIntent() {

           intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);

           if (ndefUri != null) {

               intent.setData(ndefUri);

               returnintent;

           } else if (ndefMimeType != null) {

               intent.setType(ndefMimeType);

               return intent;

           }

           return null;

}

boolean tryNdef(DispatchInfodispatch, NdefMessage message) {

       dispatch.setNdefIntent();//設置ActionACTION_NDEF_DISCOVERED

 

       // Try to start AAR activity with matchingfilter

       List<String> aarPackages =extractAarPackages(message);//將數據轉換成合法的包名

       for (String pkg : aarPackages) {

           dispatch.intent.setPackage(pkg);

           if (dispatch.tryStartActivity()) {

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

               return true;

           }

       }

 

       // Try to perform regularlaunch of the first AAR

       if (aarPackages.size() > 0) {

           String firstPackage = aarPackages.get(0);

           Intent appLaunchIntent =mPackageManager.getLaunchIntentForPackage(firstPackage);

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

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

               return true;

           }

           // Find the package inMarket:

           Intent marketIntent = getAppSearchIntent(firstPackage);

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

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

               return true;

           }

       }

 

       //regular launch

       dispatch.intent.setPackage(null);

       if (dispatch.tryStartActivity()) {

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

           return true;

       }

 

       return false;

}

首先調用setNdefIntent設置IntentActionACTION_NDEF_DISCOVERED,如果前面讀取的ndefUrindefMimeType不爲空,那麼設置到Intent裏。

下面的代碼,有四種方式處理數據發送不同的Intent。我們需要注意的是,首先需要調用extractAarPackages()NdefMessage數據轉換層系統合法的包名,下面看4種處理的方式:

n        第一種爲AAR,爲 Android ApplicationRecord的簡寫

如果作爲P2p的發送端,調用NdefRecord.createApplicationRecord (String packageName)將包名封裝到Ndefmessage中,意思是隻允許包名爲packageNameApk處理數據。那麼現在我們分析的是接收端的代碼,解析NdefMessage數據,將其轉換成合法的包名。如果包名存在於當前的系統中,那麼就啓動該Apk來處理數據。所以對於AAR,接收數據的包名必須是packageNameActivityACTION必須包含ACTION_NDEF_DISCOVERED,數據類型必須滿足ndefUrindefMimeType

n        以包名封裝Intent

如果NdefMessage中能轉換成合法的包名,且前面的Intent沒有Activity響應,那麼就需要一包名封裝的Intent啓動Activity。這樣情況是把當前正在運行的apk的包名發送給其他設備,其他設備將啓動該apk

n        在第二種Intent的情況下,如果接收設備沒有該Apk,那麼將通過Intent啓動google play去下載該Apk

n        第四種爲正常類型,通過ActionACTION_NDEF_DISCOVERED、及ndefUrindefMimeType去啓動Activity。有可能多個Activity響應的。

發佈了36 篇原創文章 · 獲贊 6 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章