NFC framework introduce
1 NFC 簡介
對於NFC,是google在android4.0上推出來的,簡單介紹下。近場通訊(NFC)是一系列短距離無線技術,一般需要4cm或者更短去初始化連接。近場通訊(NFC)允許你在NFC tag和Android設備或者兩個Android設備間共享小負載數據。優酷上有其應用的視頻:http://v.youku.com/v_show/id_XMjM3ODE5ODMy.html。
http://v.youku.com/v_show/id_XMzM1MTUyMzI4.html
2 總體框架
對於NFC框架的設計,同樣是android的標準的c/s架構,其框架圖如下:
n 客戶端:android提供了兩個API包給apk,分別是android.nfc.tech、android.nfc,實現了NFC的應用接口,代碼路徑frameworks/base/core/java/android/nfc/tech、frameworks/base/core/java/android/nfc。
n 服務端:packages/apps/Nfc是一個類似電話本的應用,這個程序在啓動後自動運行,並一直運行,作爲NFC的服務進程存在,是NFC的核心。
在這個程序代碼中,有個JNI庫,供NfcService調用,代碼路徑是packages/apps/Nfc/jni/
n 庫文件:代碼路徑是external/libnfc-nxp,用C編寫的庫文件,有兩個庫,分別是libnfc.so和libnfc_ndef.so。libnfc.so是一個主要的庫,實現了NFC stack的大部分功能,主要提供NFC的服務進程調用。libnfc_ndef是一個很小的庫,主要是實現NDEF消息的解析,供framework調用
2.1 總體類圖關係
2.2 數據分類
NFC按照發展,分爲兩種,NFC basics和Advanced NFC。從字面上理解,第一種是最先設計的,第二種是在原來基礎上擴展的。
2.2.1 NFC basics
是一種點對點(P2P)數據交換的功能,傳送的數據格式是NDEF,是Nfc Data Exchange Format的縮寫,這個數據格式用於設備之間點對點數據的交換,例如網頁地址、聯繫人、郵件、圖片等。對於除圖片以外的數據,數據量比較小,直接封裝在類NdefMessage中,通過NFC將NdefMessage類型數據發送到另一臺設備,而對於圖片這樣數據量比較大的數據,需要構建一個標準的NdefMessage數據,發送給另外一臺設備,等有迴應之後,再通過藍牙傳送數據。
NdefMessage類是用於數據的封裝,其包含有一個或多個NdefRecord類,NdefRecord纔是存儲數據的實體,將聯繫人、郵件、網頁地址等轉換才byte類型的數據存儲在NdefRecord當中,並且包含了數據類型。舉個例子吧:
NdefRecord uriRecord = 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的數據類型,然後發送給其他設備。在接收設備的同樣的APK的AndroidManifest文件中設置接收數據的類型,這樣通過Intent消息就能找到對應的Activity啓動。
n 直接將當前運行(除home程序外)的Apk的包名封裝到NdefMessage中,發送給其他設備。接收設備收到NdefMessage數據,轉換才成包名,根據包名構造Intent,啓動指定的Activity。如果包名不存在,那麼會啓動google play去下載安裝該Apk。
n 圖片爲數據量比較大的數據。需要封裝一個標準的NdefMessage數據發送給其他設備,當有迴應的時候,在將圖片數據通過藍牙發送給其他設備。
按照上面的分析,還可以將數據傳送,分爲小數據量的傳送和大數據量的傳送。小數據量是指聯繫人、郵件、網頁地址、包名等,而大數據量是指圖片等,需要通過藍牙傳送的。那麼爲什麼NFC的功能還要藍牙傳送呢?原因是NFC的設計本來就是爲了傳送小的數據量,同我們通過NFC啓動藍牙傳圖片,更方便的不需要手動進行藍牙的匹配,只需要將手機貼在一起就可以完成了藍牙的匹配動作。
2.2.2 Advanced NFC
對於該類型的NFC,也是在點對點數據交換功能上的一個擴充,我們日常接觸的有公交卡、飯卡,手機設備可以通過NFC功能讀取該卡內的數據,也有支持NFC設備往這類卡里面寫數據。所以,我們將這些卡類稱爲Tag。
需要直接通過Byte格式進行數據封裝,對TAG數據進行讀寫。市面上有很多的卡,估計沒個城市用的公交卡都不一樣,就是使用的標準不一樣,所以在 android.nfc.tech包下支持了多種technologies,如下圖:
當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 } |
TagService是NfcService的內部類,並繼承於INfcTag.stub,因此客戶端可以通過Binder通信獲取到TagService的實例mNfcTagService。其主要的功能是完成tag的讀寫。
NfcAdapterService也是NfcService的內部類,並繼承於INfcAdapter.stub,同樣客戶端可以通過Binder通信獲取到NfcAdapterService的實例mNfcAdapter。NfcAdapterService也是暴露給客戶端的主要接口,主要完成對NFC的使能初始化,掃面讀寫tag,派發tag消息等。
NativeNfcManager類就像其名字一樣,主要負責native JNI的管理。
NfcDispatcher主要負責tag消息處理,並派發Intent消息,啓動Activity。
3.2.2 NfcAdapter客戶端初始化
在ContextImpl類中,有一個靜態模塊,在這裏創建了NfcManager的實例,並註冊到服務中,代碼如下:
Static{ registerService(NFC_SERVICE, 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();//調用了遠程服務NfcAdapterService的enable方法 } catch (RemoteException e) { } } |
在NfcAdapterService.enable()方法中,創建了一個Task任務來完成使能工作,代碼如下:
public boolean enable() throws RemoteException { …… newEnableDisableTask().execute(TASK_ENABLE); return true; } |
EnableDisableTask是NfcService的一個內部類,繼承於AsyncTask,一個異步的任務線程,實際工作的doInBackground方法中。根據了TASK_ENABLE參數,選擇調用到了EnableDisableTask.enableInternal()完成NFC功能的使能,代碼如下:
boolean enableInternal() { …… if (!mDeviceHost.initialize()) {//NFC硬件初始化 return false; } synchronized(NfcService.this) { mObjectMap.clear(); mP2pLinkManager.enableDisable(mIsNdefPushEnabled,true);//P2p功能的啓動 updateState(NfcAdapter.STATE_ON); } initSoundPool();
applyRouting(true);//開始掃描 return true; } |
mDeviceHost其實是NativeNfcManager的實例,其繼承於DeviceHost。調用了其initialize()方法,接着調用JNI方法doInitialize(),完成對NFC硬件的初始化。
硬件初始化完成之後,就需要初始化P2pLiskManager。P2p就是點對點傳送的意思。這裏初始化,需要創建讀取數據線程,以及socket的創建。
下面看看P2pLinkManager.enableDisable(),啓動P2p功能:
public voidenableDisable(boolean sendEnable, boolean receiveEnable){ if (!mIsReceiveEnabled &&receiveEnable) { mDefaultSnepServer.start(); mNdefPushServer.start(); …… } else …… } |
這裏啓動了兩個服務,分別是SnepServer和NdefPushServer,但是在實際使用過程中優先使用SnepServer服務,只有當其使用失敗的時候,纔會用到NdefPushServer服務。所以,我們這裏就看SnepServer就可以了,NdefPushServer也比較相似。SnepServer.start():
public void start(){ mServerThread = new ServerThread(); mServerThread.start(); mServerRunning = true; } |
代碼非常的簡單,ServerThread是繼承與Thread的,且是SnepServer的內部類。看看其run()方法,爲了方便理解,剪切了不少代碼:
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設備的初始化,下面就需要去掃描查詢tag及P2p設備。
本調用applyRouting(true)開始掃描tag及P2p消息。
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中註冊了回調函數,當掃描到tag或p2p後,將回調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時序圖
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設備的時候,將會回調java層NativeNfcManager的notifyLlcpLinkActivation()方法:
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事件,mEventListener是P2pEventManager的實例,看看其代碼:
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的內部類,看看SendTask的doInBackground()方法:
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()完成數據的處理,我們注意到有兩個參數,第一個mMessager是SnepMessenger的實例,在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()將數據傳送到P2pLinkManager。Callback接口在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; } |
調用了NfcService的sendMockDefTag()方法:
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的實例,並將tag、message封裝到Intent中,供Activity讀取。這裏還將NdefMessage轉換爲uri和mime,這兩個數據也是用來找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();//設置Action爲ACTION_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設置Intent的Action爲ACTION_NDEF_DISCOVERED,如果前面讀取的ndefUri、ndefMimeType不爲空,那麼設置到Intent裏。
下面的代碼,有四種方式處理數據發送不同的Intent。我們需要注意的是,首先需要調用extractAarPackages()將NdefMessage數據轉換層系統合法的包名,下面看4種處理的方式:
n 第一種爲AAR,爲 Android ApplicationRecord的簡寫
如果作爲P2p的發送端,調用NdefRecord.createApplicationRecord (String packageName)將包名封裝到Ndefmessage中,意思是隻允許包名爲packageName的Apk處理數據。那麼現在我們分析的是接收端的代碼,解析NdefMessage數據,將其轉換成合法的包名。如果包名存在於當前的系統中,那麼就啓動該Apk來處理數據。所以對於AAR,接收數據的包名必須是packageName,Activity的ACTION必須包含ACTION_NDEF_DISCOVERED,數據類型必須滿足ndefUri、ndefMimeType。
n 以包名封裝Intent
如果NdefMessage中能轉換成合法的包名,且前面的Intent沒有Activity響應,那麼就需要一包名封裝的Intent啓動Activity。這樣情況是把當前正在運行的apk的包名發送給其他設備,其他設備將啓動該apk
n 在第二種Intent的情況下,如果接收設備沒有該Apk,那麼將通過Intent啓動google play去下載該Apk
n 第四種爲正常類型,通過Action爲ACTION_NDEF_DISCOVERED、及ndefUri、ndefMimeType去啓動Activity。有可能多個Activity響應的。