很多android裝置已經支援nfc(近距離無線通訊技術)了。本文就以執行個體的方式,為大家介紹如何在android系統中進行nfc開發。
android nfc開發環境
使用硬體:google nexus s,北京大學學生卡。(ps:筆者本想使用公交一卡通進行測試,發現手機不能正确識别)
手機作業系統:android ics 4.04。
開發時,筆者從google play store上下載下傳了nfc taginfo軟體進行對比學習。是以我們可以使用任意一張能被taginfo軟體正确識别的卡做測試。
在android nfc 應用中,android手機通常是作為通信中的發起者,也就是作為各種nfc卡的讀寫器。android對nfc的支援主要在 android.nfc 和android.nfc.tech 兩個包中。
android.nfc 包中主要類如下:
nfcmanager 可以用來管理android裝置中指出的所有nfcadapter,但由于大部分android裝置隻支援一個nfc adapter,是以一般直接調用getdefaultaapater來擷取手機中的adapter。
nfcadapter 相當于一個nfc擴充卡,類似于電腦裝了網絡擴充卡才能上網,手機裝了nfcadapter才能發起nfc通信。
ndef: nfc data exchange format,即nfc資料交換格式。
ndefmessage 和ndefrecord ndef 為nfc forum 定義的資料格式。
tag 代表一個被動式tag對象,可以代表一個标簽,卡片等。當android裝置檢測到一個tag時,會建立一個tag對象,将其放在intent對象,然後發送到相應的activity。
android.nfc.tech 中則定義了可以對tag進行的讀寫操作的類,這些類按照其使用的技術類型可以分成不同的類如:nfca, nfcb, nfcf,以及mifareclassic 等。其中mifareclassic比較常見。
在本次執行個體中,筆者使用北京大學學生卡進行資料讀取測試,學生卡的tag類型為mifareclassic。
nfc開發執行個體講解
xml/html代碼
<span style="font-size:16px;"><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.reno"
android:versioncode="1"
android:versionname="1.0" >
<uses-permission android:name="android.permission.nfc" />
<uses-sdk android:minsdkversion="14" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name="org.reno.beam"
android:label="@string/app_name"
android:launchmode="singletop" >
<intent-filter>
<action android:name="android.intent.action.main" />
<category android:name="android.intent.category.launcher" />
</intent-filter>
<action android:name="android.nfc.action.tech_discovered" />
<meta-data
android:name="android.nfc.action.tech_discovered"
android:resource="@xml/nfc_tech_filter" />
</activity>
</application>
</manifest>
</span>
res/xml/nfc_tech_filter.xml:
<resourcesxmlns:xliffresourcesxmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.mifareclassic</tech>
</tech-list>
</resources>
<uses-permission android:name="android.permission.nfc"/>
<uses-feature android:name="android.hardware.nfc" android:required="true"/>
表示會使用到硬體的nfc功能。并且當使用者在google play store中搜尋時,隻有帶有nfc功能的手機才能夠搜尋到本應用。
ndef_discovered, tech_discovered, tag_discovered
當android裝置檢測到有nfc tag靠近時,會根據action申明的順序給對應的activity 發送含nfc消息的 intent。
此處我們使用的intent-filter的action類型為tech_discovered進而可以處理所有類型為action_tech_discovered并且使用的技術為nfc_tech_filter.xml檔案中定義的類型的tag。
下圖為當手機檢測到一個tag時,啟用activity的比對過程。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuATLlxGctFGel1yYm5WLkl2byRmbh9CXkF2bsBXdvwFZp9mck5WYvwVbvNmLp12b1hmeppmL3d3dvw1LcpDc0RHaiojIsJye.gif)
res/layout/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<scrollview
android:id="@+id/scrollview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:drawable/edit_text" >
<textview
android:id="@+id/promt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:singleline="false"
android:text="@string/info" />
</scrollview>
</linearlayout>
res/values/strings.xml:
<resources>
<string name="app_name">nfc測試</string>
<string name="info">掃描中。。。</string>
</resources>
src/org/reno/beam.java:
java代碼
package org.reno;
import android.app.activity;
import android.content.intent;
import android.nfc.nfcadapter;
import android.nfc.tag;
import android.nfc.tech.mifareclassic;
import android.os.bundle;
import android.widget.textview;
public class beam extends activity {
nfcadapter nfcadapter;
textview promt;
@override
public void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.main);
promt = (textview) findviewbyid(r.id.promt);
// 擷取預設的nfc控制器
nfcadapter = nfcadapter.getdefaultadapter(this);
if (nfcadapter == null) {
promt.settext("裝置不支援nfc!");
finish();
return;
}
if (!nfcadapter.isenabled()) {
promt.settext("請在系統設定中先啟用nfc功能!");
}
protected void onresume() {
super.onresume();
//得到是否檢測到action_tech_discovered觸發
if (nfcadapter.action_tech_discovered.equals(getintent().getaction())) {
//處理該intent
processintent(getintent());
//字元序列轉換為16進制字元串
private string bytestohexstring(byte[] src) {
stringbuilder stringbuilder = new stringbuilder("0x");
if (src == null || src.length <= 0) {
return null;
char[] buffer = new char[2];
for (int i = 0; i < src.length; i++) {
buffer[0] = character.fordigit((src[i] >>> 4) & 0x0f, 16);
buffer[1] = character.fordigit(src[i] & 0x0f, 16);
system.out.println(buffer);
stringbuilder.append(buffer);
return stringbuilder.tostring();
/**
* parses the ndef message from the intent and prints to the textview
*/
private void processintent(intent intent) {
//取出封裝在intent中的tag
tag tagfromintent = intent.getparcelableextra(nfcadapter.extra_tag);
for (string tech : tagfromintent.gettechlist()) {
system.out.println(tech);
boolean auth = false;
//讀取tag
mifareclassic mfc = mifareclassic.get(tagfromintent);
try {
string metainfo = "";
//enable i/o operations to the tag from this tagtechnology object.
mfc.connect();
int type = mfc.gettype();//擷取tag的類型
int sectorcount = mfc.getsectorcount();//擷取tag中包含的扇區數
string types = "";
switch (type) {
case mifareclassic.type_classic:
types = "type_classic";
break;
case mifareclassic.type_plus:
types = "type_plus";
case mifareclassic.type_pro:
types = "type_pro";
case mifareclassic.type_unknown:
types = "type_unknown";
}
metainfo += "卡片類型:" + types + "\n共" + sectorcount + "個扇區\n共"
+ mfc.getblockcount() + "個塊\n存儲空間: " + mfc.getsize() + "b\n";
for (int j = 0; j < sectorcount; j++) {
//authenticate a sector with key a.
auth = mfc.authenticatesectorwithkeya(j,
mifareclassic.key_default);
int bcount;
int bindex;
if (auth) {
metainfo += "sector " + j + ":驗證成功\n";
// 讀取扇區中的塊
bcount = mfc.getblockcountinsector(j);
bindex = mfc.sectortoblock(j);
for (int i = 0; i < bcount; i++) {
byte[] data = mfc.readblock(bindex);
metainfo += "block " + bindex + " : "
+ bytestohexstring(data) + "\n";
bindex++;
}
} else {
metainfo += "sector " + j + ":驗證失敗\n";
}
promt.settext(metainfo);
} catch (exception e) {
e.printstacktrace();
}
關于mifareclassic卡的背景介紹:資料分為16個區(sector) ,每個區有4個塊(block) ,每個塊可以存放16位元組的資料。
每個區最後一個塊稱為trailer ,主要用來存放讀寫該區block資料的key ,可以有a,b兩個key,每個key 長度為6個位元組,預設的key值一般為全ff或是0。由mifareclassic.key_default 定義。
是以讀寫mifare tag 首先需要有正确的key值(起到保護的作用),如果鑒權成功,然後才可以讀寫該區資料。
執行效果: