本節開始時介紹,Android平臺中,NFC系統模塊運行在com.android.nfc進程中,該進程對應的應用程序文件名為Nfc.apk。NFC系統模塊包含的組件非常多,所以通過以下幾條分析路線來介紹。
* NFC系統模塊的核心NfcService和一些重要成員的作用及之間的關系。
* R/W模式下NFC Tag的處理。
* Android Beam的實現。
* CE模式相關的處理。
**1、NfcService介紹**
Nfc.apk源碼中包含一個NfcApplication類。當該應用啟動時,NfcApplication的onCreate函數將被調用。正是在這個onCreate函數中,NFC系統模塊的核心成員NfcService得以創建。我們直接來看NfcService的構造函數。
**NfcService.java::NfcService**
~~~
public NfcService(Application nfcApplication) {
// NFC系統模塊重要成員
mNfcTagService = new TagService();// TagService用于和NFC Tag交互
// NfcAdapterService用于和Android系統中其他使用NfcService的客戶端交互
mNfcAdapter = new NfcAdapterService();
// NfcAdapterExtrasService用于和Android系統中使用Card Emulation模式的客戶端交互
mExtrasService = new NfcAdapterExtrasService();
sService = this; mContext = nfcApplication;
// NativeNfcManager由dhimpl模塊實現,用于和具體芯片廠商提供的NFC模塊交互
mDeviceHost = new NativeNfcManager(mContext, this);
// HandoverManager處理Connection Handover工作
HandoverManager handoverManager = new HandoverManager(mContext);
// NfcDispatcher用于向客戶端派發NFC Tag相關的通知
mNfcDispatcher = new NfcDispatcher(mContext, handoverManager);
// P2pLinkManager用于處理LLCP相關的工作
mP2pLinkManager = new P2pLinkManager(mContext, handoverManager,
mDeviceHost.getDefaultLlcpMiu(),
mDeviceHost.getDefaultLlcpRwSize());
// NativeNfcSecureElement用于和SE交互,它也由dhimpl模塊實現
mSecureElement = new NativeNfcSecureElement(mContext);
mEeRoutingState = ROUTE_OFF;
/*
NfceeAccessControl用于判斷哪些應用程序有權限操作NFCEE。它將讀取/etc/nfcee_access.xml文件的
內容。nfcee_access.xml內容比較簡單,請參考Nfc目錄下的etc/sample_nfcee_access.xml來學習。
*/
mNfceeAccessControl = new NfceeAccessControl(mContext);
......
// 向系統注冊一個“nfc”服務。注意,SERVICE_NAME的值為“nfc”。該服務對應的對象為mNfcAdapter
ServiceManager.addService(SERVICE_NAME, mNfcAdapter);
/*
注冊廣播事件監聽對象。NfcService對屏幕狀態、應用程序安裝和卸載等廣播事件感興趣。這部分內容請讀者
自行研究。
*/
......
// EnableDisableTask為一個AsyncTask,TASK_BOOT用于NfcService其他初始化工作
new EnableDisableTask().execute(TASK_BOOT);
}
~~~
由上述代碼可知,NfcService在其構造函數中,首先創建了NFC系統模塊的幾個核心成員。下文將詳細介紹它們的作用及之間的關系。NfcService向Binder系統添加了一個名為"nfc"的服務,該服務對應的Binder對象為mNfcAdapter,類型為NfcAdapter。通過一個AysncTask(代碼中的EnableDisableTask)完成NfcService其他初始化工作。
下面馬上來看NFC系統模塊核心成員。
**①、NfcService核心成員**
圖8-35所示為NfcAdapter、TagService等相關成員的類信息。
:-: 
圖8-35 NfcAdapter、TagService及相關類成員結構圖
圖8-35中,NfcAdapter包含一個類型為INfcAdapter的sService成員變量,該變量通過Android Binder機制來和NfcService內部類NfcAdapter的實例(即上述代碼中的mAdapter)交互。NfcService內部的NfcAdapter對象即是注冊到Android系統服務中的那個名為"nfc"的Binder服務端對象。
NfcAdapter還包含一個類型為INfcTag的sTagService成員變量,該變量通過Android Binder機制來和NfcService內部類TagService的實例(即上述代碼中的mNfcTagService)交互。INfcTag接口封裝了對Tag操作相關的函數。注意,前面所示的Nfc客戶端示例中并沒有直接使用INfcTag接口的地方,但表8-12所列的各種Tech類內部需要通過ITag接口來操作NFC Tag。
NfcAdapterExtras包含一個類型為INfcAdapterExtras的sService成員變量,也通過Android Binder機制來和NfcService內部類NfcAdapterService的實例(即上述代碼中的mExtrasService)交互。
接著來看NfcService和NativeNfcManager,它們的類家族如圖8-36所示。
:-: 
圖8-36 NativeNfcManager和NfcService類家族
Android NFC系統模塊通過接口類DeviceHost和其內部的接口類LlcpServerSocket、DeviceHostListener、LlcpSocket、LlcpConnectionlessSocket、NfcDepEndpoint、TagEndpoint將NFC系統模塊中和NFC芯片無關的處理邏輯,以及和芯片相關的處理邏輯進行了有效解耦。圖8-36中以"Native"開頭的類均定義在packages/app/Nfc/nxp目錄下,所以它們和NXP公司的NFC芯片相關。
DeviceHost接口中,DeviceHost類用于和底層NFC芯片交互,TagEndpoint用于和NFC Tag交互,NfcDepEndpoint用于和P2P對端設備交互,LlcpSocket和LlcpServerSocket分別用于LLCP中有鏈接數據傳輸服務的客戶端和服務器端,LlcpConnectionlessSocket用于LLCP中無連接數據傳輸服務。另外,DeviceHostListener也非常重要,它用于NativeNfcManager往NfcService傳遞NFC相關的通知事件。例如其中的onRemoteEndpointDiscovered代表搜索到一個NFC Tag、onLlcpActivited代表本機和對端NFC設備進入Link Activation(鏈路激活)階段。
NativeNfcManager實現了DeviceHost接口,以NXP公司的NativeNfcManager為例,它將通過libnfc_jni及libnfc和NXP公司的NFC芯片交互。
NativeNfcSecureElement用來和Secure Element交互。
接下來要出場的是HandoverManager以及P2pLinkManager,它們的家族關系如圖8-37所示。
:-: 
圖8-37 P2pLinkManager家族關系圖
圖8-37所示的P2pLinkManager家族關系非常復雜,圖中的各成員說明如下。
* P2pLinkManager包含三個和傳輸相關的Server,分別是SnepServer、NdefPushServer以及HandoverServer。每一個Server都定義了相關的回調接口類(如SnepServer.callback),這些回調接口類的實現均由P2pLinkManager的內部類來實現。
* HandoverServer負責處理NFC Connection Handover協議,而具體的數據傳輸則由HandoverManager來實現。由圖中HandoverMangar的mBluetoothAdapter可知,Android默認使用藍牙來傳輸數據(提示:這部分內容請讀者學完本章后自行研究)。
* P2pEventManager用于處理NFC P2P中和用戶交互相關的邏輯。例如當搜索到一個P2P設備時,手機將會震動并發出提示音。
SendUi實現了類似圖8-29左圖的界面及用戶觸摸事件處理。在Android平臺中,當兩個設備LLCP鏈路被激活時,SendUi將顯示出來。其界面組成部分包括兩個部分,一個是SendUi界面顯示之前手機的截屏圖像,另外一個是“觸摸即可發送”的文本提示信息。當用戶觸摸SendUi界面時,數據將被發送出去。
**②、enableInternal函數**
介紹完NfcService的幾個核心成員后,馬上來看NfcService構造函數中最后創建的EnableDisableTask,由于設置了參數為TASK_BOOT,故最終被執行的函數為enableInternal。
**NfcService.java::EnableDisableTask:enableInternal**
~~~
boolean enableInternal() {
......
// 啟動一個WatchDog線程用來監視NFC底層操作是否超時
WatchDogThread watchDog = new WatchDogThread("enableInternal",
INIT_WATCHDOG_MS);
watchDog.start();
try {
mRoutingWakeLock.acquire();
try {
// 初始化NFC底層模塊,這部分內容請讀者自行閱讀
if (!mDeviceHost.initialize()) {......}
} finally {
mRoutingWakeLock.release();
}
} finally {
watchDog.cancel();
}
synchronized(NfcService.this) {
mObjectMap.clear();
// mIsNdefPushEnabled判斷是否啟用NPP協議,可在Settings中設置
mP2pLinkManager.enableDisable(mIsNdefPushEnabled, true);
updateState(NfcAdapter.STATE_ON);
}
initSoundPool();// 創建SoundPool,用于播放NFC相關事件的通知音
applyRouting(true);// 啟動NFC Polling流程,一旦搜索到周圍的NFC設備,相關回調將被調用
return true;
}
~~~
我們重點關注上面代碼中和P2pLinkManager相關的enableDisable函數,其代碼如下所示。
**P2pLinkManager.java::enableDisable**
~~~
public void enableDisable(boolean sendEnable, boolean receiveEnable) {
synchronized (this) {// 假設參數sendEnable和receiveEnable為true
if (!mIsReceiveEnabled && receiveEnable) {
/*
啟動SnepServer、NdefPushServer和HandoverServer。
下面這三個成員變量均在P2pLinkManager的構造函數中被創建,這部分內容請讀者自行閱讀
本章將只分析SnepServer。
*/
mDefaultSnepServer.start();
mNdefPushServer.start();
mHandoverServer.start();
if (mEchoServer != null) // EchoServer用于測試,以后代碼分析將忽略它
mHandler.sendEmptyMessage(MSG_START_ECHOSERVER);
}......
mIsSendEnabled = sendEnable;
mIsReceiveEnabled = receiveEnable;
}
}
~~~
**③、SnepServer的start函數**
SnepServer的start函數將創建一個ServerThread線程對象,其run函數代碼如下所示。
**SnepServer.java::ServerThread:run**
~~~
public void run() {// 注意:為了方便閱讀,此處代碼省略了synchronized和try/catch等一些代碼邏輯
boolean threadRunning;
threadRunning = mThreadRunning;
while (threadRunning) {
// 創建一個LlcpServerSocket,其中mServiceSap值為0x04,mServiceName為“urn:nfc:sn:sne”
// mMiu和mRwSize為本機NFC LLCP層的MIU和RW大小。1024為內部緩沖區大小,單位為字節
mServerSocket = NfcService.getInstance().createLlcpServerSocket(mServiceSap,
mServiceName, mMiu, mRwSize, 1024);
LlcpServerSocket serverSocket;
serverSocket = mServerSocket;
// 等待客戶端的鏈接
LlcpSocket communicationSocket = serverSocket.accept();
if (communicationSocket != null) {
// 獲取客戶端設備的MIU
int miu = communicationSocket.getRemoteMiu();
/*
判斷分片大小。mFragmentLength默認為-1。MIU非常重要。例如本機的MIU為1024,而
對端設備的MIU為512,那么本機在向對端發送數據時,每次發送的數據不能超過對端
MIU即512字節。
*/
int fragmentLength = (mFragmentLength == -1) ?miu : Math.min(miu, mFragmentLength);
// 每一個連接成功的客戶端對應一個ConnectionThread,其內容留待下文詳細分析
new ConnectionThread(communicationSocket, fragmentLength).start();
}
}
mServerSocket.close();
}
~~~
NfcService初始化完畢后,手機中的NFC模塊就進入工作狀態,一旦有Tag或其他設備進入其有效距離,NFC模塊即可開展相關工作。
下面先來分析NFC Tag的處理流程。
**2、NFC Tag處理流程分析**
**①、notifyNdefMessageListeners流程**
當NFC設備檢測到一個NFC Tag時,NativeNfcManager的notifyNdefMessageListeners函數將被調用(由libnfc_jni在JNI層調用),其代碼如下所示。
**NativeNfcManager.java::notifyNdefMessageListeners**
~~~
private void notifyNdefMessageListeners(NativeNfcTag tag) {
/*
mListener指向NfcService,它實現了DeviceHostListener接口。
注意,notifyNdefMessageListeners的參數類型為NativeNfcTag,tag對象由jni層直接創建
并返回給Java層。
*/
mListener.onRemoteEndpointDiscovered(tag);
}
~~~
上述代碼中,mListener指向NfcService,它的onRemoteEndPointDiscovered函數代碼如下所示。
**NfcService.java::onRemoteEndpointDiscovered**
~~~
public void onRemoteEndpointDiscovered(TagEndpoint tag) {
// 注意,onRemoteEndpointDiscovered的參數類型是TagEndpoint
// 由圖8-36可知,NativeNfcTag實現了該接口
sendMessage(NfcService.MSG_NDEF_TAG, tag);// 發送一個MSG_NDEF_TAG消息
}
~~~
NfcService的onRemoteEndpointDiscovered將給自己發送一個MSG_NDEF_TAG消息。
NfcService內部有一個NfcServiceHandler專門用來處理這些消息。其處理函數如下所示。
**NfcService.java::NfcServiceHandler:handleMessage**
~~~
final class NfcServiceHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
......// 其他消息處理
case MSG_NDEF_TAG:
TagEndpoint tag = (TagEndpoint) msg.obj;
playSound(SOUND_START);// 播放一個通知音
/*
從該NFC Tag中讀取NDEF消息,NativeNfcTag的findAndReadNdef比較復雜,
其大體工作流程是嘗試用該Tag支持的Technology來讀取其中的內容。
*/
NdefMessage ndefMsg = tag.findAndReadNdef();
// 注意下面這段代碼,無論ndefMsg是否為空,dispatchTagEndpoint都會被調用
if (ndefMsg != null) {
tag.startPresenceChecking();// 檢測目標Tag是否還在有效距離內
dispatchTagEndpoint(tag);// 重要函數,詳情見下節
} else {
if (tag.reconnect()) {// 重新鏈接到此NFC Tag
tag.startPresenceChecking();
dispatchTagEndpoint(tag);
} ......
}
break;
......
}
}
}
~~~
由上述代碼可知,NfcService先調用TagEndpoint的findAndReadNdef函數來讀取Tag中的數據,然后NfcService將調用dispatchTagEndpoint做進一步處理。
>[info] 提示 findAndReadNdef的實現和具體的NFC芯片有關,而NXP公司的實現函數在NativeNfcTag類中,內容比較復雜,感興趣的讀者可以閱讀。
**②、dispatchTagEndpoint流程**
代碼如下。
**NfcService.java::NfcServiceHandler.dispatchTagEndpoint**
~~~
private void dispatchTagEndpoint(TagEndpoint tagEndpoint) {
// 構造一個Tag對象。前面的示例中已經見過Tag。對客戶端來說,它代表目標NFC Tag
Tag tag = new Tag(tagEndpoint.getUid(), tagEndpoint.getTechList(),
tagEndpoint.getTechExtras(), tagEndpoint.getHandle(), mNfcTagService);
registerTagObject(tagEndpoint);// 保存此tagEndpoint對象
// mNfcDispatcher的類型是NfcDispather,調用它的dispatchTag函數來分發Tag
if (!mNfcDispatcher.dispatchTag(tag)) {......}
}
~~~
**NfcDispatcher.java::dispatchTag**
~~~
public boolean dispatchTag(Tag tag) {
NdefMessage message = null;
Ndef ndef = Ndef.get(tag);// 構造一個Ndef對象,Ndef屬于Tag Technology的一種
/*
從Ndef獲取目標Tag中的NDEF消息。如果目標Tag中保存的是系統支持的NDEF消息,則message不為空。
特別注意:在前面代碼中見到的findAndReadNdef函數內部已經根據表8-11進行了相關處理。
*/
if (ndef != null) message = ndef.getCachedNdefMessage();
PendingIntent overrideIntent;
IntentFilter[] overrideFilters;
String[][] overrideTechLists;
// ①構造一個DispatchInfo對象,該對象內部有一個用來觸發Activity的Intent
DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
synchronized (this) {
// 下面三個變量由前臺分發系統相關的NfcAdapter enableForegroundDispatch函數設置
overrideFilters = mOverrideFilters;
overrideIntent = mOverrideIntent;
overrideTechLists = mOverrideTechLists;
}
// 恢復App Switch,詳情可參考《深入理解Android:卷Ⅱ》6.3.3節關于resume/stopAppSwitches的介紹
resumeAppSwitches();
// 如果前臺Activity啟用了前臺分發功能,則只需要處理前臺分發相關工作即可
if (tryOverrides(dispatch, tag, message, overrideIntent,
overrideFilters, overrideTechLists)) return true;
// 處理Handover事件
if (mHandoverManager.tryHandover(message)) return true;
// ②下面是Tag分發系統的處理,首先處理ACTION_NDEF_DISCOVERED
if (tryNdef(dispatch, message)) return true;
// 如果tryNdef處理失敗,則接著處理ACTION_TECH_DISCOVERED
if (tryTech(dispatch, tag)) return true;
// 如圖tryTech處理失敗,則處理ACTION_TAG_DISCOVERED
// 設置DispatchInfo對象的內部Intent對應的ACTION為ACTION_TAG_DISCOVERED
dispatch.setTagIntent();
// 首先從PackageManagerService查詢對ACTION_TAG_DICOVERED感興趣的Activity,如果有則啟動它
if (dispatch.tryStartActivity()) return true;
return false;
}
~~~
上述代碼中有①②兩個重要函數,我們先來看第一個。
**NfcDispatcher.java::DispatchInfo構造函數**
~~~
public DispatchInfo(Context context, Tag tag, NdefMessage message) {
// 這個Intent的內容將派發給NFC的客戶端
intent = new Intent();
/*
不論最終Intent的Action是什么,NFC系統模塊都會將tag對象和tag的ID包含在Intent中傳遞
給客戶端。
*/
intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId());
// 如果NDEF消息不為空,則把它也保存在Intent中
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;
}
/*
rootIntent用來啟動目標Activity。NfcRootActivity是Nfc.apk中定義的一個Activity。
目標Activity啟動的過程如下。NFC系統模塊先啟動NfcRootActivity,然后再由NfcRootActivity
啟動目標Activity。由于NfcRootActivity設置了啟動標志(FLAG_ACTIVITY_NEW_TASK和
FLAG_ACTIVITY_CLEAR_TASK),所以目標Activity將單獨運行在一個Task中。關于ActivityManager
這部內容,感興趣的讀者可閱讀《深入理解Android:卷Ⅱ》第6章。
*/
rootIntent = new Intent(context, NfcRootActivity.class);
// 將Intent信息保存到rootIntent中。
rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent);
rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
this.context = context;
packageManager = context.getPackageManager();
}
~~~
接著來看第二個關鍵函數tryNdef,代碼如下所示。
**NfcDispatcher.java::tryNdef**
~~~
boolean tryNdef(DispatchInfo dispatch, NdefMessage message) {
if (message == null) return false;
/* setNdefIntent:
設置Dispatcher內部intent對象的Action為ACTION_NDEF_DISCOVERED。
如果Dispatch對象的ndefUri和ndefMimeType都為null,則函數返回null。
*/
Intent intent = dispatch.setNdefIntent();
if (intent == null) return false;
// 如果message中包含了AAR信息,則取出它們。AAR信息就是應用程序的包名
List<String> aarPackages = extractAarPackages(message);
for (String pkg : aarPackages) {
dispatch.intent.setPackage(pkg);
/*
tryStartActivity先檢查目標Activity是否存在以及目標Activity的IntenFiltert
是否匹配intent。注意,下面這個tryStartActivity沒有參數。
*/
if (dispatch.tryStartActivity()) return true;
}
// 上面代碼對目標Activity進行了精確匹配,如果沒有找到,則嘗試啟動AAR指定的應用程序
if (aarPackages.size() > 0) {
String firstPackage = aarPackages.get(0);
PackageManager pm;
/*
下面這段代碼用于啟動目標應用程序的某個Activity,由于AAR只是指定了應用程序的包名而沒有指定
Activity,所以getLaunchIntentForPackage將先檢查目標應用程序中是否有Activity的Category
為CATEGORY_INFO或CATEGORY_LAUNCHER,如果有則啟動它。
*/
UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
pm = mContext.createPackageContextAsUser("android", 0,currentUser).getPackageManager();
Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage);
if (appLaunchIntent != null && dispatch.tryStartActivity(appLaunchIntent))
return true;
/*
如果上述處理失敗,則獲得能啟動應用市場去下載某個應用程序的的Intent,該Intent的
Data字段取值為“market:// details?id=應用程序包名”。
*/
Intent marketIntent = getAppSearchIntent(firstPackage);
if (marketIntent != null && dispatch.tryStartActivity(marketIntent))
return true;
}
// 處理沒有AAR的NDEF消息
dispatch.intent.setPackage(null);
if (dispatch.tryStartActivity())
return true;
return false;
}
~~~
**③、NFC Tag處理流程總結**
NFC Tag的處理流程還算簡單,下面總結其中涉及的重要函數調用,如圖8-38所示。
:-: 
圖8-38 NFC Tag處理流程
NFC Tag的處理流程比較簡單,其中有的代碼邏輯比較復雜,這部分內容主要集中在NativeNfcTag的findAndReadNdef函數中。它和具體NFC芯片廠商的實現有關,故把它留給感興趣的讀者自己來研究。
下面我們將研究Android Beam的工作流程。
**3、Android Beam工作流程分析**
當本機檢測到某個NFC設備進入有效距離并且能處理LLCP協議后,將通過notifyLlcpLinkActivation通知我們。本節就從這個函數開始分析。
**①、notifyLlcpLinkActivation流程**
notifyLlcpLinkActivation代碼如下所示。
**NativeNfcManager.java::notifyLlcpLinkActivation**
~~~
private void notifyLlcpLinkActivation(NativeP2pDevice device) {
// mListener指向NfcService
// 它的onLlcpLinkActivated函數將發送一個MSG_LLCP_LINK_ACTIVATION消息
mListener.onLlcpLinkActivated(device);
}
~~~
MSG_LLCP_LINK_ACTIVATION消息由NfcService的內部類NfcServiceHandler處理,它將調用llcpActivated函數,代碼如下所示。
**NfcService.java::NfcServiceHandler:llcpActivated**
~~~
private boolean llcpActivated(NfcDepEndpoint device) {
/*
NfcDepEndpoint代表對端設備,其真實類型是NativeP2pDevice。不論對端設備是Target還是
Initiator,P2pLinkManager的onLlcpActivated函數都將被調用。
*/
if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) {
if (device.connect()) {// 如果對端是Target,則需要連接上它
if (mDeviceHost.doCheckLlcp()) {
if (mDeviceHost.doActivateLlcp()) {
synchronized (NfcService.this) {
mObjectMap.put(device.getHandle(), device);
}
mP2pLinkManager.onLlcpActivated();
return true;
}......
} else if (device.getMode() == NfcDepEndpoint.MODE_P2P_INITIATOR) {
if (mDeviceHost.doCheckLlcp()) {
if (mDeviceHost.doActivateLlcp()) {
synchronized (NfcService.this) {
mObjectMap.put(device.getHandle(), device);
}
mP2pLinkManager.onLlcpActivated();
return true;
}......
}
return false;
}
}
}
}
~~~
P2pLinkManager的onLlcpActivated函數代碼如下所示。
**P2pLinkManager.java::onLlcpActivated**
~~~
public void onLlcpActivated() {
synchronized (P2pLinkManager.this) {
.....
switch (mLinkState) {
case LINK_STATE_DOWN:// 如果之前沒有LLCP相關的活動,則mLinkState為LINK_STATE_DOWN
mLinkState = LINK_STATE_UP;
mSendState = SEND_STATE_NOTHING_TO_SEND;
/*
mEventListener指向P2pEventManager,它的onP2pInRange函數中將播放
通知音以提醒用戶,同時它還會通過SendUi截屏。請讀者自行閱讀該函數。
*/
mEventListener.onP2pInRange();
prepareMessageToSend();// ①準備發送數據
if (mMessageToSend != null ||
(mUrisToSend != null && mHandoverManager.isHandoverSupported())) {
mSendState = SEND_STATE_NEED_CONFIRMATION;
// ②顯示提示界面,讀者可參考圖8-29的左圖
mEventListener.onP2pSendConfirmationRequested();
}
break;
......
}
}
}
~~~
onLlcpActivated有兩個關鍵函數,我們先來看第一個函數prepareMessageToSend,其代碼如下所示。
**P2pLinkManager.java::prepareMessageToSend**
~~~
void prepareMessageToSend() {
synchronized (P2pLinkManager.this) {
......
// 還記得NFC P2P模式示例程序嗎?可通過setNdefPushMessageCallback設置回調函數
if (mCallbackNdef != null) {
try {
mMessageToSend = mCallbackNdef.createMessage();// 從回調函數那獲取要發送的數據
/*
getUris和NfcAdapter的setBeamPushUrisCallback函數有關,它用于發送file或
content類型的數據。由于這些數據需要借助Handover技術,請讀者自己來分析。
*/
mUrisToSend = mCallbackNdef.getUris();
return;
} ......
}
// 如果沒有設置回調函數,則系統會嘗試獲取前臺應用進程的信息
List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
if (tasks.size() > 0) {
// 獲取前臺應用進程的包名
String pkg = tasks.get(0).baseActivity.getPackageName();
/*
應用程序可以在其AndroidManifest中設置“android.nfc.disable_beam_default”
標簽為false,以阻止系統通過Android Beam來傳遞與該應用相關的信息。
*/
if (beamDefaultDisabled(pkg)) mMessageToSend = null;
else {
/*
createDefaultNdef將創建一個NDEF消息,該消息包含兩個NFC Record。
第一個NFC Record包含URI類型的數據,其內容為“http://play.google.com/store/apps
/details?id=應用程序包名&feature=beam”。
第二個NFC Record為AAR類型,其內容為應用程序的包名。
*/
mMessageToSend = createDefaultNdef(pkg);// 包名為pkg的應用程序
}
}else
mMessageToSend = null;
}
}
~~~
prepareMessageToSend很有意思,其主要工作如下。
* 如果設置回調對象,系統將從回調對象中獲取要發送的數據。
* 如果沒有回調對象,系統會獲取前臺應用程序的包名。如果前臺應用程序禁止通過
Android Beam分享信息,則prepareMessageToSend直接返回,否則它將創建一個包含了兩個NFC Record的NDEF消息。
接下來看第二個關鍵函數onP2pSendConfirmationRequested,其代碼如下所示。
**P2pEventManager.java::onP2pSendConfirmationRequested**
~~~
public void onP2pSendConfirmationRequested() {
// 對于擁有顯示屏幕的Android設備來說,mSendUi不為空
if (mSendUi != null)
mSendUi.showPreSend();// 顯示類似圖8-29左圖所示的界面以提醒用戶
else
mCallback.onP2pSendConfirmed();
/*
對于那些沒有顯示屏幕的Android設備來說,直接調用onP2pSendConfirmed。mCallback指向
P2pLinkManager。待會將分析這個函數。
*/
}
~~~
在onP2pSendConfirmationRequested函數中,SendUi的showPreSend函數將繪制一個通知界面,如圖8-29所示。至此,notifyLlcpLinkActivation的工作完畢。在繼續分析Android Beam之前,先來總結notifyLlcpLinkActivation的工作流程,如圖8-39所示。
:-: 
圖8-39 notifyLlcpLinkActivation流程
**②、Beam數據發送流程**
SendUi界面將提醒用戶觸摸屏幕以發送數據,觸摸屏幕這一動作將導致SendUi的onTouch函數被調用,其代碼如下所示。
**SendUi.java::onTouch**
~~~
public boolean onTouch(View v, MotionEvent event) {
......
mCallback.onSendConfirmed();// mCallback指向P2pEventManager
return true;
}
~~~
**P2pEventManager.java::onSendConfirmed**
~~~
public void onSendConfirmed() {
if (!mSending) {
if (mSendUi != null)
mSendUi.showStartSend();// 顯示數據發送動畫
mCallback.onP2pSendConfirmed();// mCallback指向P2pLinkManager
}
mSending = true;
}
~~~
**P2pLinkManager.java::onP2pSendConfirmed**
~~~
public void onP2pSendConfirmed() {
synchronized (this) {
......
mSendState = SEND_STATE_SENDING;
if (mLinkState == LINK_STATE_UP)
sendNdefMessage();// 關鍵函數
}
}
~~~
sendNefMessage將創建一個類型為SendTask的AsyncTask實例以處理數據發送相關的工作,我們來看這個SendTask,相關代碼如下所示。
**P2pLinkManager.java::SendTask:doInBackground**
~~~
final class SendTask extends AsyncTask<Void, Void, Void> {
public Void doInBackground(Void... args) {
NdefMessage m; Uri[] uris; boolean result;
m = mMessageToSend; uris = mUrisToSend; // 設置要發送的數據
long time = SystemClock.elapsedRealtime();
try {
int snepResult = doSnepProtocol(mHandoverManager, m, uris,
mDefaultMiu, mDefaultRwSize);
......
} catch (IOException e) {
// 如果使用SNEP發送失敗,則將利用NPP協議再次嘗試發送
if (m != null) // 請讀者自行研究和NPP相關的代碼
result = new NdefPushClient().push(m);
......
}
time = SystemClock.elapsedRealtime() - time;
if (result) onSendComplete(m, time);// 發送完畢。請讀者自行閱讀該函數
return null;
}
}
~~~
上述代碼中的doSnepProtocol函數內容如下。
**P2pLinkManager.java::SendTask:doSnepProtocol**
~~~
static int doSnepProtocol(HandoverManager handoverManager,
NdefMessage msg, Uri[] uris, int miu, int rwSize) throws IOException {
// 創建一個SnepClient客戶端
SnepClient snepClient = new SnepClient(miu, rwSize);
try {
snepClient.connect(); // ①連接遠端設備的SnepServer
} ......
try {
if (uris != null) {// 如果uris不為空,需要使用HandoverManager,這部分內容請讀者自行閱讀
......
} else if (msg != null) {
snepClient.put(msg);// ②利用SNEP的PUT命令發送數據
}
return SNEP_SUCCESS;
} catch ......
finally {
snepClient.close();
}
return SNEP_FAILURE;
}
~~~
重點介紹SnepClient的connect函數以及put函數。connect函數的代碼如下所示。
**SnepClient.java::connect**
~~~
public void connect() throws IOException {
......
LlcpSocket socket = null;
SnepMessenger messenger; // SnepMessenger用于處理數據發送和接收
try {
socket = NfcService.getInstance().createLlcpSocket(0, mMiu, mRwSize, 1024);
if (mPort == -1)
socket.connectToService(mServiceName);// 通過服務名來連接服務端
else
socket.connectToSap(mPort); // 通過SAP連接服務端
// 獲取遠端設備的MIU
int miu = socket.getRemoteMiu();
int fragmentLength = (mFragmentLength == -1) ? miu : Math.min(miu, mFragmentLength);
messenger = new SnepMessenger(true, socket, fragmentLength);
} ......
......
}
~~~
put函數代碼如下所示。
**SnepClient.java::put**
~~~
public void put(NdefMessage msg) throws IOException {
SnepMessenger messenger;
messenger = mMessenger;
synchronized (mTransmissionLock) {
try {
/*
獲取SNEP PUT命令對應的數據包,然后發送出去。SnepMessenger的sendMessage
函數將完成具體的發送工作。
*/
messenger.sendMessage(SnepMessage.getPutRequest(msg));
messenger.getMessage();
}.....
}
}
~~~
圖8-40總結了本節所述的Beam數據發送流程。
:-: 
圖8-40 Beam數據發送流程
接著來看Beam數據接收流程。
**③、Beam數據接收流程**
8.3.2節中曾介紹,SnepServer每接收一個客戶端的連接后均會創建一個ConnectionThread,它的代碼如下所示。
**SnepServer.java::ConnectionThread**
~~~
private class ConnectionThread extends Thread {
private final LlcpSocket mSock;
private final SnepMessenger mMessager;
ConnectionThread(LlcpSocket socket, int fragmentLength) {
super(TAG);
mSock = socket;
// 也創建一個SnepMessenger用來處理具體的數據接收
mMessager = new SnepMessenger(false, socket, fragmentLength);
}
public void run() {
......// 省略一些try/catch邏輯
while (running) {
if (!handleRequest(mMessager, mCallback))
break;
......
}
mSock.close();
}
}
~~~
**SnepServer.java::handleRequest**
~~~
static boolean handleRequest(SnepMessenger messenger,
Callback callback) throws IOException {
SnepMessage request;
request = messenger.getMessage();
......
if (((request.getVersion() & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) {
messenger.sendMessage(SnepMessage.getMessage(
SnepMessage.RESPONSE_UNSUPPORTED_VERSION));
} else if (request.getField() == SnepMessage.REQUEST_GET) {
// 處理GET命令,callback類型為SnepServer的內部類Callback
messenger.sendMessage(callback.doGet(request.getAcceptableLength(),
request.getNdefMessage()));
} else if (request.getField() == SnepMessage.REQUEST_PUT) {
// 處理PUT命令
messenger.sendMessage(callback.doPut(request.getNdefMessage()));
} .....
return true;
}
~~~
來看SnepServer.callback的doPut函數,它由P2pLinkManager的內部類實現,代碼如下所示。
**P2pLinkManager.java::SnepServer.Callback**
~~~
final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() {
public SnepMessage doPut(NdefMessage msg) {
onReceiveComplete(msg);
return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS);
}
}
~~~
onReceiveComplete的代碼如下所示。
**P2pLinkManager.java::onReceiveComplete**
~~~
void onReceiveComplete(NdefMessage msg) {
// 發送一個MSG_RECEIVE_COMPLETE消息
mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget();
}
~~~
MSG_RECEIVE_COMPLETE消息由P2pLinkManager的handleMessge處理,相關代碼邏輯如下所示。
**P2pLinkManager.java::**
~~~
public boolean handleMessage(Message msg) {
switch (msg.what) {
......
case MSG_RECEIVE_COMPLETE:
NdefMessage m = (NdefMessage) msg.obj;
synchronized (this) {
......
mSendState = SEND_STATE_NOTHING_TO_SEND;
mEventListener.onP2pReceiveComplete(true);// 取消本機顯示的SendUi界面
// sendMockNdefTag將發送一個MSG_MOCK_NDEF消息給NfcService的NfcServiceHandler
NfcService.getInstance().sendMockNdefTag(m);
}
break;
.....
}
}
~~~
**NfcService.java::NfcServiceHandler:handleMessage**
~~~
final class NfcServiceHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MOCK_NDEF: {
NdefMessage ndefMsg = (NdefMessage) msg.obj;
Bundle extras = new Bundle();
extras.putParcelable(Ndef.EXTRA_NDEF_MSG, ndefMsg);
extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, 0);
extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, Ndef.NDEF_MODE_READ_ONLY);
extras.putInt(Ndef.EXTRA_NDEF_TYPE, Ndef.TYPE_OTHER);
// 創建一個模擬Tag對象
Tag tag = Tag.createMockTag(new byte[] { 0x00 },
new int[] { TagTechnology.NDEF },new Bundle[] { extras });
// 直接通過分發系統分發這個Tag
boolean delivered = mNfcDispatcher.dispatchTag(tag);
......
break;
}
......
}
}
}
~~~
Beam接收端的處理流程比較巧妙,系統將創建一個模擬的Tag對象,然后利用分發系統來處理它。圖8-41總結了Beam接收端的處理流程。
:-: 
圖8-41 Beam數據接收流程
**4、CE模式**
NFC系統模塊對CE模式的支持集中在以下兩點。
* 通過NfcAdapterExtras為CE模式的客戶端提供INfcAdapterExtras功能實現。這部分內容所涉及的調用流程比較簡單,請讀者自行研讀。當然,我們略過了和芯片相關的部分。
* 當NFC設備進入CE模式后,和它交互的另一端是諸如NFC Reader這樣的設備。此時對端發起一些操作,而NativeNfcManagement的一些回調函數將被觸發。下面代碼展示了和CE相關的這些回調函數。
**NativeNfcManager.java::CE相關回調函數**
~~~
// 根據NFCIP-1協議,該函數表示對端設備發送了DESELECT命令給我們。它屬于deactivation階段的命令
private void notifyTargetDeselected() {// mListern指向NfcService
mListener.onCardEmulationDeselected();
}
/*
用于通知SE上某個應用程序開始和對端設備進行交互。AID為Application ID的縮寫,它代表SE上
的某個應用程序(稱為Applet)。
*/
private void notifyTransactionListeners(byte[] aid) {
mListener.onCardEmulationAidSelected(aid);
}
// 用于通知SE模塊被激活
private void notifySeFieldActivated() {
mListener.onRemoteFieldActivated();
}
// 用于通知SE模塊被禁止
private void notifySeFieldDeactivated() {
mListener.onRemoteFieldDeactivated();
}
// 收到對端設備的APDU命令
private void notifySeApduReceived(byte[] apdu) {
mListener.onSeApduReceived(apdu);
}
/*
EMV是EMV標準是由國際三大銀行卡組織Europay(歐陸卡,已被萬事達收購)、MasterCard(萬事達卡)和
Visa(維薩)共同發起制定的銀行卡從磁條卡向智能IC卡轉移的技術標準,是基于IC卡的金融支付標準,
目前已成為公認的全球統一標準。下面這個函數用于通知EMV Card進入Removal階段。
該函數只適用于NXP公司的相關芯片。
*/
private void notifySeEmvCardRemoval() {
mListener.onSeEmvCardRemoval();// NfcService如何處理它呢
}
// 用于通知MIFARE SMX Emulation被外部設備訪問。該函數只適用于NXP公司的相關芯片
private void notifySeMifareAccess(byte[] block) {
mListener.onSeMifareAccess(block);
}
~~~
NfcService是如何處理這些回調通知的呢?以notifySeEmvCardRemoval中的onSeEmvCardRemoval為例,NfcService將發送一個MSG_SE_EMV_CARD_REMOVAL消息,而這個消息的處理函數代碼如下所示。
**NfcService.java::NfcServiceHandler:handleMessage**
~~~
......
case MSG_SE_EMV_CARD_REMOVAL: // CE相關的消息全是類型的處理方式
Intent cardRemovalIntent = new Intent();
cardRemovalIntent.setAction(ACTION_EMV_CARD_REMOVAL);
sendSeBroadcast(cardRemovalIntent); // 發送廣播
......
~~~
由上述代碼的注釋可知,NfcService對CE相關的處理非常簡單,就是接收來自底層的通知事件,然后將其轉化為廣播事件發送給系統中感興趣的應用程序。
**5、Android中的NFC總結**
本節介紹了Android平臺中NFC系統模塊NfcService及其他重要組件。從整體上來說,NfcService以及與底層芯片無關的模塊難度不大,而與底層芯片相關的模塊則難度較大(位于com.android.nfc.dhimpl包中)。本章沒有討論dhimpl包的具體代碼,希望感興趣的讀者能結合芯片手冊自行研究它們。
另外,本節還對NFC R/W模式及P2P模塊的工作流程進行了相關介紹,這部分難度不大,相信讀者能輕松掌握。同時,作為課后作業,請讀者在本節基礎上自行學習Handover相關的處理流程。
最后,本節簡單介紹了NFC CE模式的處理流程,NfcService本身對CE相關的處理比較簡單,它僅根據CE相關的操作向系統發送不同的廣播,而這些廣播則會由感興趣的應用程序來處理,例如Google Wallet。
>[info] 提示 NFC CE模式其實內容相當復雜,涉及很多規范。筆者會在博客上繼續介紹NFC CE相關的知識,敬請讀者關注。
- 前言
- 第1章 準備工作
- 1.1 Android系統架構
- 1.2 工具使用
- 1.2.1 Source Insight的使用
- 1.2.2 Eclipse的使用
- 1.2.3 BusyBox的使用
- 1.3 本書資源下載說明
- 第2章 深入理解Netd
- 2.1 概述
- 2.2 Netd工作流程
- 2.2.1 main函數分析
- 2.2.2 NetlinkManager分析
- 2.2.3 CommandListener分析
- 2.2.4 DnsProxyListener分析
- 2.2.5 MDnsSdListener分析
- 2.3 CommandListener中的命令
- 2.3.1 iptables、tc和ip命令
- 2.3.2 CommandListener構造函數和測試工具ndc
- 2.3.3 InterfaceCmd命令
- 2.3.4 IpFwd和FirewallCmd命令
- 2.3.5 ListTtysCmd和PppdCmd命令
- 2.3.6 BandwidthControlCmd和IdletimerControlCmd命令
- 2.3.7 NatCmd命令
- 2.3.8 TetherCmd和SoftapCmd命令
- 2.3.9 ResolverCmd命令
- 2.4 NetworkManagementService介紹
- 2.4.1 create函數詳解
- 2.4.2 systemReady函數詳解
- 2.5 本章總結和參考資料說明
- 2.5.1 本章總結
- 2.5.2 參考資料說明
- 第3章 Wi-Fi基礎知識
- 3.1 概述
- 3.2 無線電頻譜和802.11協議的發展歷程
- 3.2.1 無線電頻譜知識
- 3.2.2 IEEE 802.11發展歷程
- 3.3 802.11無線網絡技術
- 3.3.1 OSI基本參考模型及相關基本概念
- 3.3.2 802.11知識點導讀
- 3.3.3 802.11組件
- 3.3.4 802.11 Service介紹
- 3.3.5 802.11 MAC服務和幀
- 3.3.6 802.11 MAC管理實體
- 3.3.7 無線網絡安全技術知識點
- 3.4 Linux Wi-Fi編程API介紹
- 3.4.1 Linux Wireless Extensions介紹
- 3.4.2 nl80211介紹
- 3.5 本章總結和參考資料說明
- 3.5.1 本章總結
- 3.5.2 參考資料說明
- 第4章 深入理解wpa_supplicant
- 4.1 概述
- 4.2 初識wpa_supplicant
- 4.2.1 wpa_supplicant架構
- 4.2.2 wpa_supplicant編譯配置
- 4.2.3 wpa_supplicant命令和控制API
- 4.2.4 git的使用
- 4.3 wpa_supplicant初始化流程
- 4.3.1 main函數分析
- 4.3.2 wpa_supplicant_init函數分析
- 4.3.3 wpa_supplicant_add_iface函數分析
- 4.3.4 wpa_supplicant_init_iface函數分析
- 4.4 EAP和EAPOL模塊
- 4.4.1 EAP模塊分析
- 4.4.2 EAPOL模塊分析
- 4.5 wpa_supplicant連接無線網絡分析
- 4.5.1 ADD_NETWORK命令處理
- 4.5.2 SET_NETWORK命令處理
- 4.5.3 ENABLE_NETWORK命令處理
- 4.6 本章總結和參考資料說明
- 4.6.1 本章總結
- 4.6.2 參考資料說明
- 第5章 深入理解WifiService
- 5.1 概述
- 5.2 WifiService的創建及初始化
- 5.2.1 HSM和AsyncChannel介紹
- 5.2.2 WifiService構造函數分析
- 5.2.3 WifiStateMachine介紹
- 5.3 加入無線網絡分析
- 5.3.1 Settings操作Wi-Fi分析
- 5.3.2 WifiService操作Wi-Fi分析
- 5.4 WifiWatchdogStateMachine介紹
- 5.5 Captive Portal Check介紹
- 5.6 本章總結和參考資料說明
- 5.6.1 本章總結
- 5.6.2 參考資料說明
- 第6章 深入理解Wi-Fi Simple Configuration
- 6.1 概述
- 6.2 WSC基礎知識
- 6.2.1 WSC應用場景
- 6.2.2 WSC核心組件及接口
- 6.3 Registration Protocol詳解
- 6.3.1 WSC IE和Attribute介紹
- 6.3.2 802.11管理幀WSC IE設置
- 6.3.3 EAP-WSC介紹
- 6.4 WSC代碼分析
- 6.4.1 Settings中的WSC處理
- 6.4.2 WifiStateMachine的處理
- 6.4.3 wpa_supplicant中的WSC處理
- 6.4.4 EAP-WSC處理流程分析
- 6.5 本章總結和參考資料說明
- 6.5.1 本章總結
- 6.5.2 參考資料說明
- 第7章 深入理解Wi-Fi P2P
- 7.1 概述
- 7.2 P2P基礎知識
- 7.2.1 P2P架構
- 7.2.2 P2P Discovery技術
- 7.2.3 P2P工作流程
- 7.3 WifiP2pSettings和WifiP2pService介紹
- 7.3.1 WifiP2pSettings工作流程
- 7.3.2 WifiP2pService工作流程
- 7.4 wpa_supplicant中的P2P
- 7.4.1 P2P模塊初始化
- 7.4.2 P2P Device Discovery流程分析
- 7.4.3 Provision Discovery流程分析
- 7.4.4 GO Negotiation流程分析
- 7.5 本章總結和參考資料說明
- 7.5.1 本章總結
- 7.5.2 參考資料說明
- 第8章 深入理解NFC
- 8.1 概述
- 8.2 NFC基礎知識
- 8.2.1 NFC概述
- 8.2.2 NFC R/W運行模式
- 8.2.3 NFC P2P運行模式
- 8.2.4 NFC CE運行模式
- 8.2.5 NCI原理
- 8.2.6 NFC相關規范
- 8.3 Android中的NFC
- 8.3.1 NFC應用示例
- 8.3.2 NFC系統模塊
- 8.4 NFC HAL層討論
- 8.5 本章總結和參考資料說明
- 8.5.1 本章總結
- 8.5.2 參考資料說明
- 第9章 深入理解GPS
- 9.1 概述
- 9.2 GPS基礎知識
- 9.2.1 衛星導航基本原理
- 9.2.2 GPS系統組成及原理
- 9.2.3 OMA-SUPL協議
- 9.3 Android中的位置管理
- 9.3.1 LocationManager架構
- 9.3.2 LocationManager應用示例
- 9.3.3 LocationManager系統模塊
- 9.4 本章總結和參考資料說明
- 9.4.1 本章總結
- 9.4.2 參考資料說明
- 附錄