前言:
由于開發的業務需求,涉及到了Android NFC開發,所以開始了NFC開發的研究探討。寫下這博客以分享經驗,“share your knowledge to the world!”.
NFC設備的普及率很低,并且Android 3.0 開始系統才支持,而且現在NFC功能的設備并不多,這樣的應用也并不多。
設備:需要一部Andriod帶NFC的設備,至少一張標簽紙 。
NFC:近場通信Near Field Communication
首先,了解NFC標簽紙的類型有分A類,B類,F類
所以你需要知道你的應用支持什么類型的標簽,設置你需要過濾的標簽。
我們創建一個NFCActivity.java ,也就是處理我們的邏輯類。
在android Minifest.xml 文件中,你需要聲明NFCActivity,使用singleTask
<activity
android:name=".NFCActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" >
重點1:聲明Action,順序相關
如果是有內容的標簽,則優先獲得android.nfc.action.NDEF_DISCOVERED 這個Action動作
<!-- NDEF_DISCOVERED 優先級最高的Action -->
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
如果未獲得上面的Action,就會獲取android.nfc.action.TECH_DISCOVERED 這個Action動作
<!-- TECH_DISCOVERED 優先級第二的Action -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
如果上面兩個都沒有,就是這個優先級最低的,也是沒有任何Action的情況下最終會獲取的android.nfc.action.TAG_DISCOVERED 這個Action 動作。
<!-- TAG_DISCOVERED 優先級最低的最終處理 Action -->
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
最后需要聲明你需要過濾的標簽類型
<!-- 技術標簽過濾的便簽卡類型 android:resource="@xml/nfc_tech_filter" -->
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
@xml/nfc_tech_filter,在res下面建立一個XML文件夾,然后在文件夾下面建立
Nfc_tech_filter.xml文件,名字隨便取,但是最好的見名知意的名稱。
重點2:Nfc_tech_filter.xml 文件過濾
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
這個是一個組,組中的Ndef是代表需要捕獲F類型標簽,即F類型的才是符合條件的
如果要同時支持F類型和A類型的標簽,則是與關系,可以寫成:
<tech-list>
<tech>android.nfc.tech.NdeA</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
如果我需要支持A類型,或者是F類型,則是:
<tech-list>
<tech>android.nfc.tech.NdeA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
或者關系。
然后是聲明權限:
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.GET_TASKS" />
重點3:Intent發布系統
在minifasts.xml中聲明的配置,都會在系統內部注冊,然后系統匹配哪些應用符合Action,然后檢測到這樣的Action時候就會在桌面彈出讓用戶選擇的應用列表,這就是系統的Intent發布系統,幸運的是,我們可以在代碼中截獲系統的Intent發布系統,這樣我們就可以指定特定的Action,跳轉之我們指定的應用的頁面,這樣就不需要系統彈出應用選擇的列表了,即我們在代碼中接管了系統的Intent發布系統。
獲取PendingIntent
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_CANCEL_CURRENT);
獲取intentFilter,
可以添加你想要的Action和mimeType:
IntentFilter ndef = new IntentFilter();
ndef.addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
ndef.addAction(NfcAdapter.ACTION_TECH_DISCOVERED);
try { //
ndef.addDataType("text/plain");
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
添加卡類型:
intentFiltersArray = new IntentFilter[]{ndef};
techListsArray = new String[][]{new String[]{
Ndef.class.getName(), NdefFormatable.class.getName(),
NfcA.class.getName(), MifareClassic.class.getName(),
MifareUltralight.class.getName(), NfcB.class.getName(),
IsoDep.class.getName(), NfcF.class.getName(), NfcV.class.getName(),
NfcBarcode.class.getName()}};
在onNewIntent方法中
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
重點4,獲取Intent前臺發布系統
/**
* 獲取Intent TAG前臺分發系統權限
*/
private void getForgroundDispatch() {
if (nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(NFCActivity.this, pendingIntent, null, null);//// 獲取前臺發布系統
hasforgrounddispatch = true;
}
}
nfcAdapter.enableForegroundDispatch(NFCActivity.this, pendingIntent, null, null);
由于我的Demo業務需求,首次通過系統Intent發布系統在應用列表中選擇了我的應用,然后應用獲取Intent發布系統。
由于獲取Intent發布系統第三個參數為null,即不指定Action,這個時候Action將會是NfcAdapter.ACTION_TAG_DISCOVERED,優先級最低的。
詳細看系統API介紹:
public void enableForegroundDispatch (Activity activity, PendingIntent intent, IntentFilter[] filters, String[][] techLists)
- Added in API level 10
- Enable foreground dispatch to the given Activity.
- This will give give priority to the foreground activity when dispatching a discovered Tag to an application.
- If any IntentFilters are provided to this method they are used to match dispatch Intents for both the ACTION_NDEF_DISCOVERED and ACTION_TAG_DISCOVERED. Since ACTION_TECH_DISCOVERED relies on meta data outside of the IntentFilter matching for that dispatch Intent is handled by passing in the tech lists separately. Each first level entry in the tech list represents an array of technologies that must all be present to match. If any of the first level sets match then the dispatch is routed through the given PendingIntent. In other words, the second level is ANDed together and the first level entries are ORed together.
- If you pass null for both the filters and techLists parameters that acts a wild card and will cause the foreground activity to receive all tags via the ACTION_TAG_DISCOVERED intent.
- This method must be called from the main thread, and only when the activity is in the foreground (resumed). Also, activities must call disableForegroundDispatch(Activity) before the completion of their onPause() callback to disable foreground dispatch after it has been enabled.
- Requires the NFC permission.
上述大概意思是:
- 至少api10以上的系統,即Android3.0
- 為Activity獲取前臺發布系統, 你需要給出一些Activity去匹配Tag的屬性值
- 你給予的任何的IntentFilter必須是匹配ACTION_NDEF_DISCOVERED 和ACTION_TAG_DISCOVERED.的,ACTION_TECH_DISCOVERED 是依賴于meta-data的列表說明,每一個tech list 代表一些技術的匹配,大概就是說匹配Tag標簽就是通過tech list的配置來的 。
- 重要的是這句話:
If you pass null for both the filters and techLists parameters that acts a wild card and will cause the foreground activity to receive all tags via the ACTION_TAG_DISCOVERED intent.
- 即第三個參數 IntentFilter[] filters 你設置為null的時候,標簽就會獲取 優先級最低的ACTION_TAG_DISCOVERED這個Action動作
這個方法在主線程中調用,你必須要在onPause() 方法中取消捕獲的Intent分發系統
nfcAdapter.disableForegroundDispatch(this);
核心是在OnResume中根據不同的Action去分發不同的邏輯,詳細看Demo簡單的邏輯處理
@Override
protected void onResume() {
.......
....
}
重點5 :擦除標簽內容
這個除標簽功能,在網上很難找到介紹方法,當初我也想過,寫入空信息就擦除了內容,然后自己試了試,沒有試對,然后繼續找資料,在GitHub找,然后找到了javaNFC項目,找到了方法,Java項目API跟AndroidAPI大同小異,方法介紹(“format NFC tag is just write a empty Record”)
呵呵呵呵呵呵....
傻眼了,當初自己思路是對的,原來是empty Record 構造錯了,心塞啊,又多花了一天時間找資料,(Google找資料才是王道,百度low了)
擦除數據的核心代碼:
// TODO,格式化NFC標簽,即寫入空信息即可
NdefRecord emptyrecord = new NdefRecord(NdefRecord.TNF_EMPTY, null, null, null);
NdefRecord[] emptyArray = {emptyrecord};
NdefMessage emptyMsg = new NdefMessage(emptyArray);
startWriteMsg(getIntent(), emptyMsg);
下面是一些核心操作,沒有什么難點的:
/**
* 創建一個封裝要寫入的文本的NdefRecord對象
*
* @param text
* @return
*/
public NdefRecord createTextRecord(String text) {
// 生成語言編碼的字節數組,中文編碼
byte[] langBytes = Locale.US.getLanguage().getBytes(Charset.forName("US-ASCII"));
// 將要寫入的文本以UTF_8格式進行編碼
Charset utfEncoding = Charset.forName("UTF-8");
// 由于已經確定文本的格式編碼為UTF_8,所以直接將payload的第1個字節的第7位設為0
byte[] textBytes = text.getBytes(utfEncoding);
int utfBit = 0;
// 定義和初始化狀態字節
char status = (char) (utfBit + langBytes.length);
// 創建存儲payload的字節數組
byte[] data = new byte[1 + langBytes.length + textBytes.length];
// 設置狀態字節
data[0] = (byte) status;
// 設置語言編碼
System.arraycopy(langBytes, 0, data, 1, langBytes.length);
// 設置實際要寫入的文本
System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
// 根據前面設置的payload創建NdefRecord對象
NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
return record;
}
/**
* 開始寫入信息
*
* @param intent
* @param ndefMessage
*/
private void startWriteMsg(Intent intent, NdefMessage ndefMessage) {
final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
if (!ndef.isWritable()) {
Tools.showTip(NFCActivity.this, "標簽是只讀!");
NFCActivity.this.onBackPressed();
NFCActivity.this.finish();
return;
}
int size = ndefMessage.toByteArray().length;
if (ndef.getMaxSize() < size) {
Tools.showTip(NFCActivity.this, "空間不足!");
NFCActivity.this.onBackPressed();
NFCActivity.this.finish();
return;
}
ndef.writeNdefMessage(ndefMessage);
Tools.showTip(NFCActivity.this, "寫入成功");// 寫入成功
// TODO 寫入成功,操作UI
return;
} else {
// 獲取可以格式化和向標簽寫入數據NdefFormatable對象
NdefFormatable format = NdefFormatable.get(tag);
// 向非NDEF格式或未格式化的標簽寫入NDEF格式數據
if (format != null) {
try {
// 允許對標簽進行IO操作
format.connect();
format.format(ndefMessage);
Tools.showTip(NFCActivity.this, "寫入成功");// 寫入成功
// TODO 寫入成功,操作UI
return;
} catch (Exception e) {
Tools.showTip(NFCActivity.this, "寫入失敗");
// TODO 寫入失敗 操作UI
return;
}
} else {
Tools.showTip(NFCActivity.this, "NFC標簽不支持NDEF格式");
// TODO NFC標簽不支持NDEF格式! 操作UI
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 到此,NFC功能完成,涉及到了空標簽如何識別,如何在觸發標簽,在應用前臺,非前臺如何觸發邏輯處理,如何修改標簽內容,如何擦除標簽內容,基本上所有的業務邏輯都囊括了,滿足了基本的開發需求,如果你涉及到更加復雜的需求開發,這個Demo也能很好的幫助你理解NFC邏輯跳轉,甚至可以直接搬過來使用,直接套上你的業務邏輯。當然,由于能力有限,可能有很多紕漏,望大神指點。
- 比如這個單是NFC的邏輯跳轉就看上去代碼比較混亂,再加上你的業務邏輯,可能就沒有那么清晰了,如果你有更好的方法,把NFC技術邏輯分離出去,然后再嵌入你的業務邏輯,可能就更加完美了,做到技術跟業務分離的。代碼的可維護性就更高了。
最后附上Demo下載地址http://download.csdn.net/detail/u011661372/8951595
Android 智能家居開發群:468191212,歡迎從事智能開發的朋友加入,當然,一樣歡迎非智能家居的Android開發者,智能家居,未來的一片天地。