有几天没有更新博客了不过本篇却准备了许久希望能带给每一位开发者最简单高效的学习方式。废话到此为止下面开始正文。
NFCNear Field Communication近场通信是一种数据传输技术。与Wi-Fi、蓝牙、红外线等数据传输技术的一个主要差异就是有效距离一般不能超过4厘米。但是NFC传输速度要比红外快。目前NFC已经出现了一些应用例如电子标签识别、刷手机、点对点付款、身份识别、信息记录等本篇文章的目的是为大家揭开NFC标签的面纱。
下面我们先从NFC的工作模式开始阐述NFC开发NFC必先了解NFC。
1.NFC的工作模式
NFC支持如下3种工作模式读卡器模式Reader/writer mode、仿真卡模式(Card Emulation Mode)、点对点模式P2P mode。
下来分别看一下这三种模式
1读卡器模式
数据在NFC芯片中可以简单理解成“刷标签”。本质上就是通过支持NFC的手机或其它电子设备从带有NFC芯片的标签、贴纸、名片等媒介中读写信息。通常NFC标签是不需要外部供电的。当支持NFC的外设向NFC读写数据时它会发送某种磁场而这个磁场会自动的向NFC标签供电。
2仿真卡模式
数据在支持NFC的手机或其它电子设备中可以简单理解成“刷手机”。本质上就是将支持NFC的手机或其它电子设备当成借记卡、公交卡、门禁卡等IC卡使用。基本原理是将相应IC卡中的信息凭证封装成数据包存储在支持NFC的外设中 。
在使用时还需要一个NFC射频器相当于刷卡器。将手机靠近NFC射频器手机就会接收到NFC射频器发过来的信号在通过一系列复杂的验证后将IC卡的相应信息传入NFC射频器最后这些IC卡数据会传入NFC射频器连接的电脑并进行相应的处理如电子转帐、开门等操作。
3点对点模式
该模式与蓝牙、红外差不多用于不同NFC设备之间进行数据交换不过这个模式已经没有有“刷”的感觉了。其有效距离一般不能超过4厘米但传输建立速度要比红外和蓝牙技术快很多传输速度比红外块得多如过双方都使用Android4.2NFC会直接利用蓝牙传输。这种技术被称为Android Beam。所以使用Android Beam传输数据的两部设备不再限于4厘米之内。
点对点模式的典型应用是两部支持NFC的手机或平板电脑实现数据的点对点传输例如交换图片或同步设备联系人。因此通过NFC多个设备如数字相机计算机手机之间都可以快速连接并交换资料或者服务。
下面看一下NFC、蓝牙和红外之间的差异
对比项 | NFC | 蓝牙 | 红外 |
---|---|---|---|
网络类型 | 点对点 | 单点对多点 | 点对点 |
有效距离 | <=0.1m | <=10m最新的蓝牙4.0有效距离可达100m | 一般在1m以内热技术连接不稳定 |
传输速度 | 最大424kbps | 最大24Mbps | 慢速115.2kbps快速4Mbps |
建立时间 | <0.1s | 6s | 0.5s |
安全性 | 安全硬件实现 | 安全软件实现 | 不安全使用IRFM时除外 |
通信模式 | 主动-主动/被动 | 主动-主动 | 主动-主动 |
成本 | 低 | 中 | 低 |
2.Android对NFC的支持
不同的NFC标签之间差异很大有的只支持简单的读写操作有时还会采用支持一次性写入的芯片将NFC标签设计成只读的。当然也存在一些复杂的NFC标签例如有一些NFC标签可以通过硬件加密的方式限制对某一区域的访问。还有一些标签自带操作环境允许NFC设备与这些标签进行更复杂的交互。这些标签中的数据也会采用不同的格式。但Android SDK API主要支持NFC论坛标准Forum Standard这种标准被称为NDEFNFC Data Exchange FormatNFC数据交换格式。
NDEF格式其实就类似于硬盘的NTFS下面我们看一下NDEF数据
1NDEF数据的操作
Android SDK API支持如下3种NDEF数据的操作
1从NFC标签读取NDEF格式的数据。
2向NFC标签写入NDEF格式的数据。
3通过Android Beam技术将NDEF数据发送到另一部NFC设备。
用于描述NDEF格式数据的两个类
1NdefMessage描述NDEF格式的信息实际上我们写入NFC标签的就是NdefMessage对象。
2NdefRecord描述NDEF信息的一个信息段一个NdefMessage可能包含一个或者多个NdefRecord。
NdefMessage和NdefRecord是Android NFC技术的核心类无论读写NDEF格式的NFC标签还是通过Android Beam技术传递Ndef格式的数据都需要这两个类。
2非NDEF数据的操作
对于某些特殊需求可能要存任意的数据对于这些数据我们就需要自定义格式。这些数据格式实际上就是普通的字节流至于字节流中的数据代表什么就由开发人员自己定义了。
3编写NFC程序的基本步骤
1设置权限限制Android版本、安装的设备
<uses-sdk android:minSdkVersion="14"/> <uses-permission android:name="android.permission.NFC" /> <!-- 要求当前设备必须要有NFC芯片 --> <uses-feature android:name="android.hardware.nfc" android:required="true" />
2定义可接收Tag的Activity
Activity清单需要配置一下launchMode属性
<activity android:name=".TagTextActivity" android:launchMode="singleTop"/>
而Activity中我们也抽取了一个通用的BaseNfcActivity如下后面的Activity实现都继承于BaseNfcActivity
/** * 1.子类需要在onCreate方法中做Activity初始化。 * 2.子类需要在onNewIntent方法中进行NFC标签相关操作。 * 当launchMode设置为singleTop时第一次运行调用onCreate方法 * 第二次运行将不会创建新的Activity实例将调用onNewIntent方法 * 所以我们获取intent传递过来的Tag数据操作放在onNewIntent方法中执行 * 如果在栈中已经有该Activity的实例就重用该实例(会调用实例的onNewIntent()) * 只要NFC标签靠近就执行 */ public class BaseNfcActivity extends AppCompatActivity { private NfcAdapter mNfcAdapter; private PendingIntent mPendingIntent; /** * 启动Activity界面可见时 */ @Override protected void onStart() { super.onStart(); mNfcAdapter = NfcAdapter.getDefaultAdapter(this); //一旦截获NFC消息就会通过PendingIntent调用窗口 mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); } /** * 获得焦点按钮可以点击 */ @Override public void onResume() { super.onResume(); //设置处理优于所有其他NFC的处理 if (mNfcAdapter != null) mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); } /** * 暂停Activity界面获取焦点按钮可以点击 */ @Override public void onPause() { super.onPause(); //恢复默认状态 if (mNfcAdapter != null) mNfcAdapter.disableForegroundDispatch(this); } }
注意通常来说所有处理NFC的Activity都要设置launchMode属性为singleTop或者singleTask保证了无论NFC标签靠近手机多少次Activity实例只有一个。
接下来看几个具体的NFC标签应用实例通过情景学习快速掌握NFC技术
3.两个NFC标签的简单实例
1.利用NFC标签让Android自动运行程序
场景是这样的现将应用程序的包写到NFC程序上然后我们将NFC标签靠近Android手机手机就会自动运行包所对应的程序这个是NFC比较基本的一个应用。下面以贴近标签自动运行Android自带的“短信”为例。
向NFC标签写入数据一般分为三步
1获取Tag对象
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
2判断NFC标签的数据类型通过Ndef.get方法
Ndef ndef = Ndef.get(tag);
3写入数据
ndef.writeNdefMessage(ndefMessage);
详细实现代码如下
public class RunAppActivity extends BaseNfcActivity{ private String mPackageName = "com.android.mms";//短信 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onNewIntent(Intent intent) { if (mPackageName == null) return; //1.获取Tag对象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); writeNFCTag(detectedTag); } /** * 往标签写数据的方法 * * @param tag */ public void writeNFCTag(Tag tag) { if (tag == null) { return; } NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createApplicationRecord(mPackageName)}); //转换成字节获得大小 int size = ndefMessage.toByteArray().length; try { //2.判断NFC标签的数据类型通过Ndef.get方法 Ndef ndef = Ndef.get(tag); //判断是否为NDEF标签 if (ndef != null) { ndef.connect(); //判断是否支持可写 if (!ndef.isWritable()) { return; } //判断标签的容量是否够用 if (ndef.getMaxSize() < size) { return; } //3.写入数据 ndef.writeNdefMessage(ndefMessage); Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show(); } else { //当我们买回来的NFC标签是没有格式化的或者没有分区的执行此步 //Ndef格式类 NdefFormatable format = NdefFormatable.get(tag); //判断是否获得了NdefFormatable对象有一些标签是只读的或者不允许格式化的 if (format != null) { //连接 format.connect(); //格式化并将信息写入标签 format.format(ndefMessage); Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "写入失败", Toast.LENGTH_SHORT).show(); } } } catch (Exception e) { } } }
注意设置 RunAppActivity 的 launchMode 属性为 singleTop。
现在看一下效果图
将NFC标签贴近手机背面自动写入数据此时退出所有程序返回桌面然后再将NFC标签贴近手机背面将会看到自动打开了“短信”。
下来再看一个有趣的例子
2.利用NFC标签让Android自动打开网页
如何让NFC标签贴近手机手机可以自动打开一个网页呢
首先我们创建一个NdefRecordAndroid已经为我们提供好了这样的方法
//直接接受一个Uri public NdefRecord createUri(String uriString); //接受一个Uri的对象 public NdefRecord createUri(Uri uri);
实现代码对比“3.利用NFC标签让Android自动运行程序”部分只是修改了writeNFCTag方法中
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createApplicationRecord(mPackageName)});
为
NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord .createUri(Uri.parse(http://www.baidu.com))});
其余不变。
上面这个功能还是比较有用的例如我们往某些商品上贴上NFC标签里面写入该商品的详细介绍网页Uri当用户贴近商品时就会自动打开该商品的详情介绍。
通过上面这两个案例的学习相信很多人已经对NFC感起了兴趣那么下来渗透式的分析一下NDEF文本格式看看NDEF到底是个什么东西。
4.NDEF文本格式深度解析
获取NFC标签中的数据要通过 NdefRecord.getPayload 方法完成。当然在处理这些数据之前最好判断一下NdefRecord对象中存储的是不是NDEF文本格式数据。
1判断数据是否为NDEF格式
1TNF类型名格式Type Name Format必须是NdefRecord.TNF_WELL_KNOWN。
2可变的长度类型必须是NdefRecord.RTD_TEXT。
如果这两个标准同时满足那么就为NDEF格式。
2NDEF文本格式规范
不管什么格式的数据本质上都是由一些字节组成的。对于NDEF文本格式来说这些数据的第1个字节描述了数据的状态然后若干个字节描述文本的语言编码最后剩余字节表示文本数据。这些数据格式由NFC Forum的相关规范定义可以通过 http://members.nfc-forum.org/specs/spec_dashboard 下载相关的规范。
下面这两张表是规范中 3.2节 相对重要的翻译部分
NDEF文本数据格式
偏移量 | 长度bytes | 描述 |
---|---|---|
0 | 1 | 状态字节见下表状态字节编码格式 |
1 | n | ISO/IANA语言编码。例如”en-US””fr-CA”。编码格式是US-ASCII长度n由状态字节的后6位指定。 |
n+1 | m | 文本数据。编码格式是UTF-8或UTF-16。编码格式由状态字节的前3位指定。 |
状态字节编码格式
字节位0是最低位7是最高位 | 含义 |
---|---|
7 | 0:文本编码为UTF-81:文本编码为UTF-16 |
6 | 必须设为0 |
5..0 | 语言编码的长度占用的字节个数 |
下面我们动手实现NFC标签中的文本数据的读写操作
1.读NFC标签文本数据
public class ReadTextActivity extends BaseNfcActivity { private TextView mNfcText; private String mTagText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_read_text); mNfcText = (TextView) findViewById(R.id.tv_nfctext); } @Override public void onNewIntent(Intent intent) { //1.获取Tag对象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //2.获取Ndef的实例 Ndef ndef = Ndef.get(detectedTag); mTagText = ndef.getType() + "\nmaxsize:" + ndef.getMaxSize() + "bytes\n\n"; readNfcTag(intent); mNfcText.setText(mTagText); } /** * 读取NFC标签文本数据 */ private void readNfcTag(Intent intent) { if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) { Parcelable[] rawMsgs = intent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES); NdefMessage msgs[] = null; int contentSize = 0; if (rawMsgs != null) { msgs = new NdefMessage[rawMsgs.length]; for (int i = 0; i < rawMsgs.length; i++) { msgs[i] = (NdefMessage) rawMsgs[i]; contentSize += msgs[i].toByteArray().length; } } try { if (msgs != null) { NdefRecord record = msgs[0].getRecords()[0]; String textRecord = parseTextRecord(record); mTagText += textRecord + "\n\ntext\n" + contentSize + " bytes"; } } catch (Exception e) { } } } /** * 解析NDEF文本数据从第三个字节开始后面的文本数据 * @param ndefRecord * @return */ public static String parseTextRecord(NdefRecord ndefRecord) { /** * 判断数据是否为NDEF格式 */ //判断TNF if (ndefRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) { return null; } //判断可变的长度的类型 if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) { return null; } try { //获得字节数组然后进行分析 byte[] payload = ndefRecord.getPayload(); //下面开始NDEF文本数据第一个字节状态字节 //判断文本是基于UTF-8还是UTF-16的取第一个字节"位与"上16进制的8016进制的80也就是最高位是1 //其他位都是0所以进行"位与"运算后就会保留最高位 String textEncoding = ((payload[0] & 0x80) == 0) ? "UTF-8" : "UTF-16"; //3f最高两位是0第六位是1所以进行"位与"运算后获得第六位 int languageCodeLength = payload[0] & 0x3f; //下面开始NDEF文本数据第二个字节语言编码 //获得语言编码 String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII"); //下面开始NDEF文本数据后面的字节解析出文本 String textRecord = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); return textRecord; } catch (Exception e) { throw new IllegalArgumentException(); } } }
注意Activity清单需要配置一下launchMode属性后面一样要注意
<activity android:name=".ReadTextActivity" android:launchMode="singleTop"/>
2.写NFC标签文本数据
public class WriteTextActivity extends BaseNfcActivity { private String mText = "NFC-NewText-123"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_write_text); } @Override public void onNewIntent(Intent intent) { if (mText == null) return; //获取Tag对象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); NdefMessage ndefMessage = new NdefMessage( new NdefRecord[] { createTextRecord(mText) }); boolean result = writeTag(ndefMessage, detectedTag); if (result){ Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "写入失败", Toast.LENGTH_SHORT).show(); } } /** * 创建NDEF文本数据 * @param text * @return */ public static NdefRecord createTextRecord(String text) { byte[] langBytes = Locale.CHINA.getLanguage().getBytes(Charset.forName("US-ASCII")); Charset utfEncoding = Charset.forName("UTF-8"); //将文本转换为UTF-8格式 byte[] textBytes = text.getBytes(utfEncoding); //设置状态字节编码最高位数为0 int utfBit = 0; //定义状态字节 char status = (char) (utfBit + langBytes.length); byte[] data = new byte[1 + langBytes.length + textBytes.length]; //设置第一个状态字节先将状态码转换成字节 data[0] = (byte) status; //设置语言编码使用数组拷贝方法从0开始拷贝到data中拷贝到data的1到langBytes.length的位置 System.arraycopy(langBytes, 0, data, 1, langBytes.length); //设置文本字节使用数组拷贝方法从0开始拷贝到data中拷贝到data的1 + langBytes.length //到textBytes.length的位置 System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length); //通过字节传入NdefRecord对象 //NdefRecord.RTD_TEXT传入类型 读写 NdefRecord ndefRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); return ndefRecord; } /** * 写数据 * @param ndefMessage 创建好的NDEF文本数据 * @param tag 标签 * @return */ public static boolean writeTag(NdefMessage ndefMessage, Tag tag) { try { Ndef ndef = Ndef.get(tag); ndef.connect(); ndef.writeNdefMessage(ndefMessage); return true; } catch (Exception e) { } return false; } }
我们将手机贴近NFC标签当写入成功会弹出“写入成功”的吐司。下面我们再验证一下是否成功写入
我们看到数据已经写入成功了说明到此我们已经成功的读写NFC标签中的文本数据了。
5.NDEF Uri格式深度解析
与NDEF文本格式一样存储在NFC标签中的Uri也有一定的格式http://members.nfc-forum.org/specs/spec_dashboard
1Uri的格式规范要比文本格式简单一些
Name | 偏移 | 大小 | 值 | 描述 |
---|---|---|---|---|
识别码 | 0 | 1byte | Uri识别码 | 用于存储已知Uri的前缀 |
Uri字段 | 1 | N | UTF-8类型字符串 | 用于存储剩余字符串 |
2Uri的前缀如下都是十六进制的一个数
十进制 | 十六进制 | 协议 | 十进制 | 十六进制 | 协议 |
---|---|---|---|---|---|
0 | 0x00 | N/A | 1 | 0x01 | http://www. |
2 | 0x02 | https://www. | 3 | 0x03 | http:// |
4 | 0x04 | https:// | 5 | 0x05 | tel: |
6 | 0x06 | mailto: | 7 | 0x07 | ftp://anonymous:anonymous@ |
8 | 0x08 | ftp://ftp. | 9 | 0x09 | ftps:// |
10 | 0x0A | sftp:// | 11 | 0x0B | smb:// |
12 | 0x0C | nfs:// | 13 | 0x0D | ftp:// |
14 | 0x0E | dav:// | 15 | 0x0F | news: |
16 | 0x10 | telnet:// | 17 | 0x11 | imap: |
18 | 0x12 | rtsp:// | 19 | 0x13 | urn: |
20 | 0x14 | pop: | 21 | 0x15 | sip: |
22 | 0x16 | sips: | 23 | 0x17 | tftp: |
24 | 0x18 | btspp:// | 25 | 0x19 | btl2cap:// |
26 | 0x1A | btgoep:// | 27 | 0x1B | tcpobex:// |
28 | 0x1C | irdaobex:// | 29 | 0x1D | file:// |
30 | 0x1E | urn:epc:id: | 31 | 0x1F | urn:epc:tag: |
32 | 0x20 | urn:epc:pat: | 33 | 0x21 | urn:epc:raw: |
34 | 0x22 | urn:epc: | 35 | 0x23 | urn:nfc: |
每一个协议都是用十六进制来存储于识别码位置占1byte。
是不是相对简单了些那么下来我们来解析一个Uri。
3预先定义已知Uri前缀
这里我们定义一个UriPrefix类以便方便的获取Uri前缀
public class UriPrefix { public static final Map<Byte, String> URI_PREFIX_MAP = new HashMap<Byte, String>(); // 预先定义已知Uri前缀 static { URI_PREFIX_MAP.put((byte) 0x00, ""); URI_PREFIX_MAP.put((byte) 0x01, "http://www."); URI_PREFIX_MAP.put((byte) 0x02, "https://www."); URI_PREFIX_MAP.put((byte) 0x03, "http://"); URI_PREFIX_MAP.put((byte) 0x04, "https://"); URI_PREFIX_MAP.put((byte) 0x05, "tel:"); URI_PREFIX_MAP.put((byte) 0x06, "mailto:"); URI_PREFIX_MAP.put((byte) 0x07, "ftp://anonymous:anonymous@"); URI_PREFIX_MAP.put((byte) 0x08, "ftp://ftp."); URI_PREFIX_MAP.put((byte) 0x09, "ftps://"); URI_PREFIX_MAP.put((byte) 0x0A, "sftp://"); URI_PREFIX_MAP.put((byte) 0x0B, "smb://"); URI_PREFIX_MAP.put((byte) 0x0C, "nfs://"); URI_PREFIX_MAP.put((byte) 0x0D, "ftp://"); URI_PREFIX_MAP.put((byte) 0x0E, "dav://"); URI_PREFIX_MAP.put((byte) 0x0F, "news:"); URI_PREFIX_MAP.put((byte) 0x10, "telnet://"); URI_PREFIX_MAP.put((byte) 0x11, "imap:"); URI_PREFIX_MAP.put((byte) 0x12, "rtsp://"); URI_PREFIX_MAP.put((byte) 0x13, "urn:"); URI_PREFIX_MAP.put((byte) 0x14, "pop:"); URI_PREFIX_MAP.put((byte) 0x15, "sip:"); URI_PREFIX_MAP.put((byte) 0x16, "sips:"); URI_PREFIX_MAP.put((byte) 0x17, "tftp:"); URI_PREFIX_MAP.put((byte) 0x18, "btspp://"); URI_PREFIX_MAP.put((byte) 0x19, "btl2cap://"); URI_PREFIX_MAP.put((byte) 0x1A, "btgoep://"); URI_PREFIX_MAP.put((byte) 0x1B, "tcpobex://"); URI_PREFIX_MAP.put((byte) 0x1C, "irdaobex://"); URI_PREFIX_MAP.put((byte) 0x1D, "file://"); URI_PREFIX_MAP.put((byte) 0x1E, "urn:epc:id:"); URI_PREFIX_MAP.put((byte) 0x1F, "urn:epc:tag:"); URI_PREFIX_MAP.put((byte) 0x20, "urn:epc:pat:"); URI_PREFIX_MAP.put((byte) 0x21, "urn:epc:raw:"); URI_PREFIX_MAP.put((byte) 0x22, "urn:epc:"); URI_PREFIX_MAP.put((byte) 0x23, "urn:nfc:"); } }
然后我们来看一下清单文件中Activity的相关配置
<activity android:name=".ReadWriteUriActivity" android:label="读写NFC标签的Uri" android:launchMode="singleTop" > <intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> <!-- 拦截NFC标签中存储有以下Uri前缀的 --> <data android:scheme="http" /> <data android:scheme="https" /> <data android:scheme="ftp" /> </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> </activity>
好了接下来就可以进行读写NFC标签中的Uri数据了
1.读NFC标签中的Uri数据
public class ReadUriActivity extends BaseNfcActivity { private TextView mNfcText; private String mTagText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_read_uri); mNfcText = (TextView) findViewById(R.id.tv_nfctext); } @Override public void onNewIntent(Intent intent) { //获取Tag对象 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //获取Ndef的实例 Ndef ndef = Ndef.get(detectedTag); mTagText = ndef.getType() + "\n max size:" + ndef.getMaxSize() + " bytes\n\n"; readNfcTag(intent); mNfcText.setText(mTagText); } /** * 读取NFC标签Uri */ private void readNfcTag(Intent intent) { if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) { Parcelable[] rawMsgs = intent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES); NdefMessage ndefMessage = null; int contentSize = 0; if (rawMsgs != null) { if (rawMsgs.length > 0) { ndefMessage = (NdefMessage) rawMsgs[0]; contentSize = ndefMessage.toByteArray().length; } else { return; } } try { NdefRecord ndefRecord = ndefMessage.getRecords()[0]; Log.i("JAVA",ndefRecord.toString()); Uri uri = parse(ndefRecord); Log.i("JAVA","uri:"+uri.toString()); mTagText += uri.toString() + "\n\nUri\n" + contentSize + " bytes"; } catch (Exception e) { } } } /** * 解析NdefRecord中Uri数据 * @param record * @return */ public static Uri parse(NdefRecord record) { short tnf = record.getTnf(); if (tnf == NdefRecord.TNF_WELL_KNOWN) { return parseWellKnown(record); } else if (tnf == NdefRecord.TNF_ABSOLUTE_URI) { return parseAbsolute(record); } throw new IllegalArgumentException("Unknown TNF " + tnf); } /** * 处理绝对的Uri * 没有Uri识别码也就是没有Uri前缀存储的全部是字符串 * @param ndefRecord 描述NDEF信息的一个信息段一个NdefMessage可能包含一个或者多个NdefRecord * @return */ private static Uri parseAbsolute(NdefRecord ndefRecord) { //获取所有的字节数据 byte[] payload = ndefRecord.getPayload(); Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8"))); return uri; } /** * 处理已知类型的Uri * @param ndefRecord * @return */ private static Uri parseWellKnown(NdefRecord ndefRecord) { //判断数据是否是Uri类型的 if (!Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_URI)) return null; //获取所有的字节数据 byte[] payload = ndefRecord.getPayload(); String prefix = UriPrefix.URI_PREFIX_MAP.get(payload[0]); byte[] prefixBytes = prefix.getBytes(Charset.forName("UTF-8")); byte[] fullUri = new byte[prefixBytes.length + payload.length - 1]; System.arraycopy(prefixBytes, 0, fullUri, 0, prefixBytes.length); System.arraycopy(payload, 1, fullUri, prefixBytes.length, payload.length - 1); Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8"))); return uri; } }
2.写NFC标签中的Uri数据
public class WriteUriActivity extends BaseNfcActivity { private String mUri = "http://www.baidu.com"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_write_uri); } public void onNewIntent(Intent intent) { Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{createUriRecord(mUri)}); boolean result = writeTag(ndefMessage, detectedTag); if (result){ Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "写入失败", Toast.LENGTH_SHORT).show(); } } /** * 将Uri转成NdefRecord * @param uriStr * @return */ public static NdefRecord createUriRecord(String uriStr) { byte prefix = 0; for (Byte b : UriPrefix.URI_PREFIX_MAP.keySet()) { String prefixStr = UriPrefix.URI_PREFIX_MAP.get(b).toLowerCase(); if ("".equals(prefixStr)) continue; if (uriStr.toLowerCase().startsWith(prefixStr)) { prefix = b; uriStr = uriStr.substring(prefixStr.length()); break; } } byte[] data = new byte[1 + uriStr.length()]; data[0] = prefix; System.arraycopy(uriStr.getBytes(), 0, data, 1, uriStr.length()); NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], data); return record; } /** * 写入标签 * @param message * @param tag * @return */ public static boolean writeTag(NdefMessage message, Tag tag) { int size = message.toByteArray().length; try { Ndef ndef = Ndef.get(tag); if (ndef != null) { ndef.connect(); if (!ndef.isWritable()) { return false; } if (ndef.getMaxSize() < size) { return false; } ndef.writeNdefMessage(message); return true; } } catch (Exception e) { } return false; } }
我们将手机贴近NFC标签写入成功后验证一下是否成功写入
我们看到数据已经写入成功了说明到此我们已经成功的读写NFC标签中的Uri数据了。
到这里NDEF格式就大致说完了那么接下来看一下非NDEF格式的数据。
6.非NDEF格式深度解析
1.MifareUltralight数据格式
将NFC标签的存储区域分为16个页每一个页可以存储4个字节一个可存储64个字节512位。页码从0开始0至15。前4页0至3存储了NFC标签相关的信息如NFC标签的序列号、控制位等。从第5页开始存储实际的数据4至15页。
使用MifareUltralight.get方法获取MifareUltralight对象然后调用MifareUltralight.connect方法进行连接并使用MifareUltralight.writePage方法每次写入1页4个字节。也可以使用MifareUltralight.readPages方法每次连续读取4页。如果读取的页的序号超过15则从头开始读。例如从第15页序号为14开始读。readPages方法会读取14、15、0、1页的数据。
2.读MifareUltralight格式数据
public class ReadMUActivity extends BaseNfcActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_read_mu); } @Override public void onNewIntent(Intent intent) { Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); String[] techList = tag.getTechList(); boolean haveMifareUltralight = false; for (String tech : techList) { if (tech.indexOf("MifareUltralight") >= 0) { haveMifareUltralight = true; break; } } if (!haveMifareUltralight) { Toast.makeText(this, "不支持MifareUltralight数据格式", Toast.LENGTH_SHORT).show(); return; } String data = readTag(tag); if (data != null) Toast.makeText(this, data, Toast.LENGTH_SHORT).show(); } public String readTag(Tag tag) { MifareUltralight ultralight = MifareUltralight.get(tag); try { ultralight.connect(); byte[] data = ultralight.readPages(4); return new String(data, Charset.forName("GB2312")); } catch (Exception e) { } finally { try { ultralight.close(); } catch (Exception e) { } } return null; } }
3.写MifareUltralight格式数据
public class WriteMUActivity extends BaseNfcActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_write_mu); } @Override public void onNewIntent(Intent intent) { Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); String[] techList = tag.getTechList(); boolean haveMifareUltralight = false; for (String tech : techList) { if (tech.indexOf("MifareUltralight") >= 0) { haveMifareUltralight = true; break; } } if (!haveMifareUltralight) { Toast.makeText(this, "不支持MifareUltralight数据格式", Toast.LENGTH_SHORT).show(); return; } writeTag(tag); } public void writeTag(Tag tag) { MifareUltralight ultralight = MifareUltralight.get(tag); try { ultralight.connect(); //写入八个汉字从第五页开始写中文需要转换成GB2312格式 ultralight.writePage(4, "北京".getBytes(Charset.forName("GB2312"))); ultralight.writePage(5, "上海".getBytes(Charset.forName("GB2312"))); ultralight.writePage(6, "广州".getBytes(Charset.forName("GB2312"))); ultralight.writePage(7, "天津".getBytes(Charset.forName("GB2312"))); Toast.makeText(this, "写入成功", Toast.LENGTH_SHORT).show(); } catch (Exception e) { } finally { try { ultralight.close(); } catch (Exception e) { } } } }
我们将手机贴近NFC标签写入成功后验证一下是否成功写入
我们看到弹出了“北京上海广州天津”说明数据已经写入成功了说明到此我们已经成功的读写NFC非NDEF格式的数据了。
源码下载demo
以上就是本文的全部内容希望对大家的学习有所帮助也希望大家多多支持。
以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android
nfc标签
读取nfc标签、安卓nfc读取写入标签、android nfc 读取卡号、android nfc读取、android nfc读取m1卡,以便于您获取更多的相关知识。