您的位置:首页 > 其它

使用安卓手机的NFC功能进行数据读取操作

2017-04-24 14:53 423 查看
记录一下使用安卓手机的NFC功能来识别各类高频RFID标签或卡片的基本操作思路。因为我的项目中还存在有大量的业务逻辑代码,所以只能整理出当中的一些重要步骤,并贴上代码片段。

1、第一步:在AndroidManifest.xml配置文件里增加以下权限

<uses-permission android:name="android.permission.NFC"/>
<uses-feature android:name="android.hardware.nfc" android:required="true"/>
<uses-sdk android:minSdkVersion="10"/>


需要注意的是,在sdk的版本是9的时候,仅仅只支持对ACTION_TAG_DISCOVERED操作。为了能够支持其它的数据类型操作,请把sdk版本保持在10以上。

2、第二步:在AndroidManifest.xml配置文件中增加对intent-filter的支持

<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>


配置了intent-filter支持的activity,当用户使用手机感应到NFC卡片的话,就会自动呼出该activit。

如果你的NFC卡片中的数据是纯文本,那么在mimeType属性中使用“text/plain”即可。另一种常见的使用场景是卡片中写有网址,在这种情况下,mimeType可以使用“http”或"https",这样配置的mimeType支持手机直接唤起默认浏览器,如下代码片段:

<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />

<category android:name="android.intent.category.DEFAULT" />

<!-- 以下是常规的识别NFC芯片的配置 -->
<!-- <data android:mimeType="text/plain"/> -->

<!-- 以下是用来识别写入了网址的NFC芯片的配置,这两项是Android 4.X原生系统自带浏览器的配置 -->
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>


除了这些基本配置之外,还可以增加对各种不同的协议标准的支持。以下是一个相对完整的intent-filter配置:

<activity
android:name=".NfcScannerActivity"
android:label=""
android:screenOrientation="portrait"
android:theme="@style/AppTheme">

<!-- 定义NDEF类型的NFC -->
<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> <!-- 以下是常规的识别NFC芯片的配置 --> <!-- <data android:mimeType="text/plain"/> --> <!-- 以下是用来识别写入了网址的NFC芯片的配置,这两项是Android 4.X原生系统自带浏览器的配置 --> <data android:scheme="http" /> <data android:scheme="https" /> </intent-filter>

<!-- 定义TECH类型的NFC -->
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>

<!-- TECH类型的NFC的扩展定义,位于xml/nfc_tech_filter.xml中 --> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />

<!-- 定义tag类型的NFC -->
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
</intent-filter>
</activity>
注意在这段配置中,有一段配置引用了xml/nfc_tech_filter.xml文件:

<!-- TECH类型的NFC的扩展定义,位于xml/nfc_tech_filter.xml中 -->
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />


在我的项目中,xml/nfc_tech_filter.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.IsoDep</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcF</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NdefFormatable</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareClassic</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
注意我是把每一个tech单独包括在tech-list里的,所以我的这份配置文件中含有很多个tech-list。你也可以试着将所有的tech都包含在一个tech-list中,这两种方式是有区别的,你可以自己试一下。

3、第三步:编写Activity代码:

首先,所有的NFC感应操作都是由NFC适配器来进行处理的:

/*
* 高频NFC适配器
*/
private NfcAdapter nfcAdapter;

在我的项目里,我还初始化了一个声音资源,目的就是当手机感应到NFC卡片的时候,能够发出提示音

private SoundPool soundPool;

/*
*初始化声音资源
*/
soundPool = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5);
soundPool.load(this, R.raw.beep_nfc, 1);


注意,声音文件“beep_nfc.wav”需要放在res/raw/目录下

目前仍然有手机不带NFC硬件模块,所以在代码中我们必须判断一下手机是否支持NFC功能。其实就是判断系统是否能够获取到NfcAdapter对象:

/*
* 判断NFC设备准备情况
*/
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
//提醒用户手机不支持NFC功能
} else if (nfcAdapter.isEnabled() == false) {
//提醒用户手机的NFC功能没有开启
}


接下来就是使用代码来获取NFC标签或卡片中的信息了。首先需要了解的是,对于不同的协议,android的数据获取方式是不同的。

a)首先是处理ACTION_TECH_DISCOVERED类型的标签或卡片:

//取出封装在intent中的TAG
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] uidBytes = tagFromIntent.getId();
this.uid = this.bytesToHexString(uidBytes);


这一步是获取标签的硬件编号。注意上面代码中出现的方法“bytesToHexString()”,该方法的实现如下:

//字节数组转换为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();
}


除了读取标签的硬件编号外,还可以读取标签可读写区域内的数据内容。我设置了一个变量“tagInfo”,用来存储这些内容:

/*
*存储在标签可读写区域内部的信息
*/
private String tagInfo;


有了这个变量,就可以用下面的代码来读取标签中的数据内容:

//读取TAG
MifareClassic mfc = MifareClassic.get(tagFromIntent);
try {
if (null != mfc) {
mfc.connect();
int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数

boolean auth = false;
for (int j = 0; j < sectorCount; j++) {
/*
*使用KeyA验证扇区
*/
auth = mfc.authenticateSectorWithKeyA(j, MifareClassic.KEY_DEFAULT);
int bIndex;
if (auth) {
// 读取扇区中的块
bIndex = mfc.sectorToBlock(j);
byte[] data = mfc.readBlock(bIndex);

String s = this.bytesToHexString(data);
if (null != s && !"".equals(s) && !"null".equals(s)) {
this.tagInfo += s;
}
} else {
tagInfo += "扇区" + j + "验证失败";
}
}
}
} catch (IOException e) {
e.printStackTrace();
}


b)以下是处理ACTION_NDEF_DISCOVERED:

对于硬件编号的获取方式是一样的:

Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] uidBytes = tag.getId();
this.uid = this.bytesToHexString(uidBytes);


而标签中的数据的获取方式如下:

Parcelable[] data = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

if (data != null) {
try {
for (int i = 0; i < data.length; i++) {
NdefRecord[] recs = ((NdefMessage) data[i]).getRecords();
for (int j = 0; j < recs.length; j++) {
if ((
recs[j].getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(recs[j].getType(), NdefRecord.RTD_TEXT))
|| (recs[j].getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(recs[j].getType(), NdefRecord.RTD_URI))
) {
/*
*读取普通的文本(即NdefRecord.RTD_TEXT)
*或者是网址(即NdefRecord.RTD_URI)
*/
byte[] payload = recs[j].getPayload();
String textEncoding = "UTF-16";
if ((payload[0] & 0200) == 0) {
textEncoding = "UTF-8";
}

int langCodeLen = payload[0] & 0077;

String s = new String(payload, langCodeLen, payload.length - langCodeLen, textEncoding);
if (!"".equals(s) && !"null".equals(s)) {
this.tagInfo += s;
}
}
}
}
} catch (Exception e) {
Log.e("TagDispatch", e.toString());
}
}


到这里为止,已经完成了用于处理ACTION_TECH_DISCOVERED类型和ACTION_NDEF_DISCOVERED两种不同类型的数据了。

接下来只要根据NFC适配器感应到的不同的数据类型来做处理就可以了:

String action = this.getIntent().getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
//调用方法,处理ACTION_TECH_DISCOVERED类型的数据
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
//调用方法,处理ACTION_NDEF_DISCOVERED类型的数据
}


在我的项目里,我在正确获得到数据之后,调用了一下声音资源,它会让手机发出提示音:

//读到标签,发出提示音
soundPool.play(1, 1, 1, 0, 0, 1);


上面是关键的处理思路和代码片段。最后要注意的是,由于安卓手机可以在不事先打开任何APP的情况下直接感应RFID芯片,并唤起你事先配置好的Activity,所以必须在onResume方法里调用你自己的代码,来处理NFC数据:

/**
* 识别当前NFC读取的标签的类别,调用不同的处理方法
*/
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();

/*
*读取标签内容,并根据不同的内容进行不同的处理
*/
this.readTagAndExecute();
}
注意上面代码片段中的最后一行,方法“readTagAndExecute()”是我自己定义的一个方法,这个方法里封装了上面所有的处理逻辑。你可以自己编写自己的处理逻辑。

除了onResume方法外,还要重载安卓生命周期的onNewIntent方法:

@Override
protected void onNewIntent(Intent intent) {
// TODO Auto-generated method stub
super.onNewIntent(intent);
this.setIntent(intent);
}


以上就是使用安卓手机的NFC功能读取RFID芯片的主要思路和关键代码片段。

下面再贴几个用于写数据的方法,首先是写普通文本标签的两个方法:

/**
* 创建一个NdefRecord对象,用于向标签写入普通的文本数据
*
* @param text
* @return
*/
public NdefRecord createTextRecord(String text) {
//生成语言编码的字节数组,中文编码
byte[] langBytes = Locale.CHINA.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 messageText 要写入标签的文本字符串
* @param tag         要写入的标签
* @return
*/
boolean writeTextTag(String messageText, Tag tag) {

//创建NdefMessage对象和NdefRecord对象
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createTextRecord(messageText)});
int size = ndefMessage.toByteArray().length;

try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
//允许对标签进行IO操作
ndef.connect();
if (!ndef.isWritable()) {
Toast.makeText(this, "NFC Tag是只读的!", Toast.LENGTH_LONG).show();
return false;

}
if (ndef.getMaxSize() < size) {
Toast.makeText(this, "NFC Tag的空间不足!", Toast.LENGTH_LONG).show();
return false;
}

//向标签写入数据
ndef.writeNdefMessage(ndefMessage);
Toast.makeText(this, "已成功写入数据!", Toast.LENGTH_LONG).show();
return true;

} else {
//获取可以格式化和向标签写入数据NdefFormatable对象
NdefFormatable format = NdefFormatable.get(tag);
//向非NDEF格式或未格式化的标签写入NDEF格式数据
if (format != null) {
try {
//允许对标签进行IO操作
format.connect();
format.format(ndefMessage);
Toast.makeText(this, "已成功写入数据!", Toast.LENGTH_LONG).show();
return true;

} catch (Exception e) {
Toast.makeText(this, "写入NDEF格式数据失败!", Toast.LENGTH_LONG).show();
return false;
}
} else {
Toast.makeText(this, "NFC标签不支持NDEF格式!", Toast.LENGTH_LONG).show();
return false;

}
}
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (FormatException e) {
e.printStackTrace();
return false;
}
}


以下是写入网址的两个方法:

/**
* 创建一个NdefRecord对象,用于向标签写入网址数据
*
* @param text
* @return
*/
public NdefRecord createUriRecord(String text) {
NdefRecord record = NdefRecord.createUri(text);
return record;
}

/**
* 将网址字符串写入标签中
*
* @param uri 要写入标签的网址
* @param tag 要写入的标签
* @return
*/
boolean writeUriTag(String uri, Tag tag) {

//创建NdefMessage对象和NdefRecord对象
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createUriRecord(uri)});
int size = ndefMessage.toByteArray().length;

try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
//允许对标签进行IO操作
ndef.connect();

if (!ndef.isWritable()) {
Toast.makeText(this, "NFC Tag是只读的!", Toast.LENGTH_LONG).show();
return false;

}
if (ndef.getMaxSize() < size) {
Toast.makeText(this, "NFC Tag的空间不足!", Toast.LENGTH_LONG).show();
return false;
}

//向标签写入数据
ndef.writeNdefMessage(ndefMessage);
Toast.makeText(this, "已成功写入数据!", Toast.LENGTH_LONG).show();

return true;

} else {
//获取可以格式化和向标签写入数据NdefFormatable对象
NdefFormatable format = NdefFormatable.get(tag);
//向非NDEF格式或未格式化的标签写入NDEF格式数据
if (format != null) {
try {
//允许对标签进行IO操作
format.connect();
format.format(ndefMessage);
Toast.makeText(this, "已成功写入数据!", Toast.LENGTH_LONG).show();
return true;

} catch (Exception e) {
Toast.makeText(this, "写入NDEF格式数据失败!", Toast.LENGTH_LONG).show();
return false;
}
} else {
Toast.makeText(this, "NFC标签不支持NDEF格式!", Toast.LENGTH_LONG).show();
return false;

}
}
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (FormatException e) {
e.printStackTrace();
return false;
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐