使用安卓手机的NFC功能进行数据读取操作
2017-04-24 14:53
423 查看
记录一下使用安卓手机的NFC功能来识别各类高频RFID标签或卡片的基本操作思路。因为我的项目中还存在有大量的业务逻辑代码,所以只能整理出当中的一些重要步骤,并贴上代码片段。
1、第一步:在AndroidManifest.xml配置文件里增加以下权限
需要注意的是,在sdk的版本是9的时候,仅仅只支持对ACTION_TAG_DISCOVERED操作。为了能够支持其它的数据类型操作,请把sdk版本保持在10以上。
2、第二步:在AndroidManifest.xml配置文件中增加对intent-filter的支持
配置了intent-filter支持的activity,当用户使用手机感应到NFC卡片的话,就会自动呼出该activit。
如果你的NFC卡片中的数据是纯文本,那么在mimeType属性中使用“text/plain”即可。另一种常见的使用场景是卡片中写有网址,在这种情况下,mimeType可以使用“http”或"https",这样配置的mimeType支持手机直接唤起默认浏览器,如下代码片段:
除了这些基本配置之外,还可以增加对各种不同的协议标准的支持。以下是一个相对完整的intent-filter配置:
在我的项目中,xml/nfc_tech_filter.xml文件的内容如下:
3、第三步:编写Activity代码:
首先,所有的NFC感应操作都是由NFC适配器来进行处理的:
在我的项目里,我还初始化了一个声音资源,目的就是当手机感应到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标签或卡片中的信息了。首先需要了解的是,对于不同的协议,android的数据获取方式是不同的。
a)首先是处理ACTION_TECH_DISCOVERED类型的标签或卡片:
这一步是获取标签的硬件编号。注意上面代码中出现的方法“bytesToHexString()”,该方法的实现如下:
除了读取标签的硬件编号外,还可以读取标签可读写区域内的数据内容。我设置了一个变量“tagInfo”,用来存储这些内容:
有了这个变量,就可以用下面的代码来读取标签中的数据内容:
b)以下是处理ACTION_NDEF_DISCOVERED:
对于硬件编号的获取方式是一样的:
而标签中的数据的获取方式如下:
到这里为止,已经完成了用于处理ACTION_TECH_DISCOVERED类型和ACTION_NDEF_DISCOVERED两种不同类型的数据了。
接下来只要根据NFC适配器感应到的不同的数据类型来做处理就可以了:
在我的项目里,我在正确获得到数据之后,调用了一下声音资源,它会让手机发出提示音:
上面是关键的处理思路和代码片段。最后要注意的是,由于安卓手机可以在不事先打开任何APP的情况下直接感应RFID芯片,并唤起你事先配置好的Activity,所以必须在onResume方法里调用你自己的代码,来处理NFC数据:
除了onResume方法外,还要重载安卓生命周期的onNewIntent方法:
以上就是使用安卓手机的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注意在这段配置中,有一段配置引用了xml/nfc_tech_filter.xml文件:
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>
<!-- 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; } }
相关文章推荐
- 使用JDBC的CachedRowSet实现将数据源中的数据读取到内存中进行离线操作
- 无废话Android之android下junit测试框架配置、保存文件到手机内存、android下文件访问的权限、保存文件到SD卡、获取SD卡大小、使用SharedPreferences进行数据存储、使用Pull解析器操作XML文件、android下操作sqlite数据库和事务(2)
- 封装android 通讯使用二进制进行数据交换 2个必要的读取,写入操作
- 使用python读取mysql数据库并进行数据的操作
- Silverlight 2 (beta1)数据操作(1)——使用ASP.NET Web Service进行数据CRUD操作(上)
- SQLSERVER 2005中使用sql语句对xml文件和其数据的进行操作(很全面)
- 使用SQL游标对数据进行遍历循环操作
- Silverlight ——使用LINQ to SQL进行数据CRUD操作(上)
- Silverlight 2 (beta1)数据操作(2)——使用ASP.NET Web Service进行数据CRUD操作(下)
- 在Windows下进行底层IO操作之CMOS数据的读取和显示
- JAVA操作XML一(读取):使用DOM读取XML数据的两种具体实现
- Android 使用SharedPreferences进行数据存储和读取数据
- [导入]Silverlight 2 (beta1)数据操作(5)——使用LINQ to SQL进行数据CRUD操作(上)
- [导入]Silverlight 2 (beta1)数据操作(3)——使用ADO.NET Data Service (Astoria)进行数据CRUD操作
- 在c++里面如何对读取出二进制数据进行操作
- 使用C#进行基于PI的开发(三)——应用PISDK和PIAPI从PI数据库读取数据
- Silverlight 2 (beta1)数据操作(6)——使用LINQ to SQL进行数据CRUD操作(下)
- Silverlight 2 (beta1)数据操作(5)——使用LINQ to SQL进行数据CRUD操作(上)
- 使用Repeater绑定数据,及提取显示数据进行操作的一点小方法!(模板列)
- C#不使用DataSet操作XML,XmlDocument读写xml所有节点及读取xml节点的数据总结