很多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值(起到保护的作用),如果鉴权成功,然后才可以读写该区数据。
执行效果: