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 = 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的數據類型,然後發送給其他設備。在接收設備的同樣的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 = 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
}
|
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, 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 時序圖
4.2 代碼分析
如果android設備有NFC硬件支持,那麼將在設置應用的出現“無線和網絡à更多àNFC”選項,點擊將使能NFC功能。其實就是調用了NfcAdapter.enable()方法,代碼如下:
public boolean enable() {
try {
return sService.enable();
//調用了遠程服務NfcAdapterService的enable方法
} catch (RemoteException e) {
}
}
|
在NfcAdapterService.enable()方法中,創建了一個Task任務來完成使能工作,代碼如下:
public boolean enable() throws RemoteException {
……
new EnableDisableTask().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 void enableDisable(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()方法,爲了方便理解,剪切了不少代碼:
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設備的初始化,下面就需要去掃描查詢tag及P2p設備。
本調用applyRouting(true)開始掃描tag及P2p消息。
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中註冊了回調函數,當掃描到tag或p2p後,將回調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讀寫流程圖
5.1.2 數據寫流程
5.1.2.1時序圖
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設備的時候,將會回調java層NativeNfcManager的notifyLlcpLinkActivation()方法:
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事件,mEventListener是P2pEventManager的實例,看看其代碼:
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的內部類,看看SendTask的doInBackground()方法:
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 時序圖
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()完成數據的處理,我們注意到有兩個參數,第一個mMessager是SnepMessenger的實例,在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()將數據傳送到P2pLinkManager。Callback接口在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;
}
|
調用了NfcService的sendMockDefTag()方法:
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的實例,並將tag、message封裝到Intent中,供Activity讀取。這裏還將NdefMessage轉換爲uri和mime,這兩個數據也是用來找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();//設置Action爲ACTION_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設置Intent的Action爲ACTION_NDEF_DISCOVERED,如果前面讀取的ndefUri、ndefMimeType不爲空,那麼設置到Intent裏。
下面的代碼,有四種方式處理數據發送不同的Intent。我們需要注意的是,首先需要調用extractAarPackages()將NdefMessage數據轉換層系統合法的包名,下面看4種處理的方式:
n 第一種爲AAR,爲 Android
Application Record的簡寫
如果作爲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響應的。
5.2 大數據量的傳送
大數據量的傳送,是指圖片等數據量比較大的資源,需要通過NFC啓動藍牙的匹配,通過藍牙來傳送數據。
5.2.1 讀寫流程圖
5.2.2 發送端發送藍牙請求和發送數據流程
5.2.2.1時序圖
大數據量的寫操作跟小數據量的類似,我們這裏主要關注差異的部分,我們從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時序圖
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設備讀寫流程圖
6.2 時序圖
6.2 代碼分析
在4.2中,NFC的啓動將調用NativeNfcManager.enableDiscovery(),然後將調用到JNI方法com_android_nfc_NfcManager_enableDiscovery()掃描tag及P2p設備。在該方法中,調用phLibNfc_RemoteDev_NtfRegister()方法註冊回調函數nfc_jni_Discovery_notification_callback()。當掃面到tag或P2p的時候將回調該方法。下面看看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_TAG的Handler消息到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消息用於構造一個Tag,tagEndpoint.getTechList()取出該Tag設備支持的technology。mNfcDispatcher是NfcDispatcher的實例,用於向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();//設置Intent的Action爲ACTION_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();//獲取Action爲ACTION_TECH_DISCOVERED的Activity列表
// 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;
}
|
第一步,還是需要設置Intent的Action爲ACTION_TECH_DISCOVERED。
第二步,獲取該Tag設備支持的technology。
第三步,讀取系統中Action爲ACTION_TECH_DISCOVERED的Activity列表,並獲取該Apk支持解析的technology列表,這個是在apk的Xml文件中定義的:
<?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設備technology的Apk添加到matches列表中
第五步,如果只有一個Activity滿足條件,直接啓動該Activity
第六步,如果有多個Activity滿足條件,發送Intent消息供用戶選擇啓動哪裏Activity。
到此,Tag消息就已經傳送到Activity了。那麼接下來就需要在Activity中,發起對Tag設備的讀寫了。
6.3 Activity中發起對Tag設備讀寫
6.3.1 三個Intent啓動Activity
當設備掃描發現有tag或P2p設備的時候,將數據封裝好後發送Intent啓動Activity處理數據,有三類型Intent:
n ACTION_NDEF_DISCOVERED :處理NDEF數據,包含MIME、URI數據類型
n ACTION_TECH_DISCOVERED :處理非NDEF數據或者NDEF數據但不能映射爲MIME、URI數據類型
n ACTION_TAG_DISCOVERED :如果沒有Activity相應上面兩個Intent,就由該Intent處理
官網上調度圖如下:
6.3.2 Activity中對Tag設備讀寫
經過前面一大串的分析,對於Tag設備,首先需要獲取Tag信息,也說過,Tag信息包含了支持的technology類型。獲取方式如下:
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
我們以MifareUltralight的technology類型爲例子,根據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