本文整理完全参考 : https://blog.csdn.net/wolfking0608/article/details/72675180

只是将参考文章做了浏览处理

我在文章结尾放入了我的实现方式 , 分为: Activity , xml 配置 , NfcUtils

Demo下载

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.2,NFC会直接利用蓝牙传输。这种技术被称为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),这种标准被称为NDEF(NFC Data Exchange Format,NFC数据交换格式)。

NDEF格式其实就类似于硬盘的NTFS,下面我们看一下NDEF数据:

(1)NDEF数据的操作

Android SDK API支持如下3种NDEF数据的操作:

1)从NFC标签读取NDEF格式的数据。
2)向NFC标签写入NDEF格式的数据。
3)通过Android Beam技术将NDEF数据发送到另一部NFC设备。

用于描述NDEF格式数据的两个类:

1)NdefMessage:描述NDEF格式的信息,实际上我们写入NFC标签的就是NdefMessage对象。
2)NdefRecord:描述NDEF信息的一个信息段,一个NdefMessage可能包含一个或者多个NdefRecord。

NdefMessage和NdefRecord是Android NFC技术的核心类,无论读写NDEF格式的NFC标签,还是通过Android Beam技术传递Ndef格式的数据,都需要这两个类。

(2)非NDEF数据的操作

对于某些特殊需求,可能要存任意的数据,对于这些数据,我们就需要自定义格式。这些数据格式实际上就是普通的字节流,至于字节流中的数据代表什么,就由开发人员自己定义了。

(3)编写NFC程序的基本步骤

1)设置权限,限制Android版本、安装的设备:

在 AndroidManifest 配置权限

 <uses-permission android:name="android.permission.NFC" /><uses-sdkandroid:minSdkVersion="10"android:targetSdkVersion="19" /><uses-featureandroid:name="android.hardware.nfc"android:required="true" />

2)定义可接收Tag的Activity

Activity清单需要配置一下launchMode属性:

<activityandroid: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,界面可见时*/@Overrideprotected void onStart() {super.onStart();mNfcAdapter = NfcAdapter.getDefaultAdapter(this);//一旦截获NFC消息,就会通过PendingIntent调用窗口mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);}/*** 获得焦点,按钮可以点击*/@Overridepublic void onResume() {super.onResume();//设置处理优于所有其他NFC的处理if (mNfcAdapter != null)mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);}/*** 暂停Activity,界面获取焦点,按钮可以点击*/@Overridepublic 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";//短信@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@Overridepublic 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标签贴近手机,手机可以自动打开一个网页呢?

首先我们创建一个NdefRecord,Android已经为我们提供好了这样的方法:

//直接接受一个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.nfchome.org")) });

其余不变。

上面这个功能还是比较有用的,例如我们往某些商品上贴上NFC标签,里面写入该商品的详细介绍网页Uri,当用户贴近商品时,就会自动打开该商品的详情介绍。

通过上面这两个案例的学习相信很多人已经对NFC感起了兴趣,那么下来渗透式的分析一下NDEF文本格式,看看NDEF到底是个什么东西。

4.NDEF文本格式深度解析

获取NFC标签中的数据要通过 NdefRecord.getPayload 方法完成。当然,在处理这些数据之前,最好判断一下NdefRecord对象中存储的是不是NDEF文本格式数据。

(1)判断数据是否为NDEF格式

1)TNF(类型名格式,Type Name Format)必须是NdefRecord.TNF_WELL_KNOWN。
2)可变的长度类型必须是NdefRecord.RTD_TEXT。

如果这两个标准同时满足,那么就为NDEF格式。

(2)NDEF文本格式规范

不管什么格式的数据本质上都是由一些字节组成的。对于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-8,1:文本编码为UTF-16
6 必须设为0
5..0 语言编码的长度(占用的字节个数)

下面我们动手实现NFC标签中的文本数据的读写操作:

1.读NFC标签文本数据

注意:Activity清单需要配置一下launchMode属性(后面一样要注意)

<activityandroid:name=".ReadTextActivity"android:launchMode="singleTop"/>
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Parcelable;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;import java.util.Arrays;public class ReadTextActivity extends BaseNfcActivity {private TextView mNfcText;private String mTagText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_read_text);mNfcText = (TextView) findViewById(R.id.tv_nfctext);}@Overridepublic void onNewIntent(Intent intent) {//1.获取Tag对象Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);//2.获取Ndef的实例Ndef ndef = Ndef.get(detectedTag);if (ndef!=null) {mTagText = ndef.getType() + "\nmaxsize:" + ndef.getMaxSize() + "bytes\n\n";readNfcTag(intent);mNfcText.setText(mTagText);}else{Toast.makeText(this, " 没有获取到 NDEF 实例! ", Toast.LENGTH_SHORT).show();}}/*** 读取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格式*///判断TNFif (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进制的80,16进制的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();}}
}

2.写NFC标签文本数据

public class WriteTextActivity extends BaseNfcActivity {private String mText = "NFC-NewText-123";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_write_text);}@Overridepublic 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);//设置状态字节编码最高位数为0int 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

(1)Uri的格式规范要比文本格式简单一些:

Name 偏移 大小 描述
识别码 0 1byte Uri识别码 用于存储已知Uri的前缀
Uri字段 1 N UTF-8类型字符串 用于存储剩余字符串

(2)Uri的前缀如下(都是十六进制的一个数):

十进制 十六进制 协议 十进制 十六进制 协议
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的相关配置:

<activityandroid: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;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_read_uri);mNfcText = (TextView) findViewById(R.id.tv_nfctext);}@Overridepublic 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("UnknownTNF"+ tnf);}/*** 处理绝对的Uri* <p>* <p>* 没有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";@Overridepublic 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 {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_read_mu);}@Overridepublic 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{@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_write_mu);}@Overridepublic 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格式的数据了。

NFC标签开发深度解析到此就结束了!


以上为整理大神的文章内容 , 以下为我自己使用的工具类及使用 :

以下内容分为 : xml 配置 , activity 部分 , NfcUtils

xml --> androidManifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fn.fn_nfc"><!--todo 权限部分,必须配置-->
<uses-permission android:name="android.permission.NFC" />
<uses-sdkandroid:minSdkVersion="10"android:targetSdkVersion="19" />
<uses-featureandroid:name="android.hardware.nfc"android:required="true" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><!--todo 界面部分--><activityandroid:name=".MainActivity"android:launchMode="singleTask"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><intent-filter><action android:name="android.nfc.action.TECH_DISCOVERED" /></intent-filter><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><meta-dataandroid:name="android.nfc.action.TECH_DISCOVERED"android:resource="@xml/nfc_tech_filter" /></activity></application></manifest>

xml --> res 下的 xml 文件夹下的文件 nfc_tech_filter.xml

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><!-- 可以处理所有Android支持的NFC类型 --><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.MifareUltralight</tech></tech-list><tech-list><tech>android.nfc.tech.MifareClassic</tech></tech-list>
</resources>

NfcUtils 会有重复的功能 , 但可以相互参考 :

package com.fn.fn_nfc;import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.nfc.tech.NdefFormatable;
import android.os.Parcelable;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;import java.io.IOException;
import java.io.UnsupportedEncodingException;/*** Created by 77167 on 2018/10/15.*/public class NfcUtils {private static final String TAG = "NfcUtils";//nfcpublic static NfcAdapter mNfcAdapter;public static IntentFilter[] mIntentFilter = null;public static PendingIntent mPendingIntent = null;public static String[][] mTechList = null;private final Activity mActivity;/*** 构造函数,用于初始化nfc*/public NfcUtils(Activity activity) {mNfcAdapter = NfcCheck(activity);this.mActivity = activity;NfcInit(activity);}/*** 检查NFC是否打开*/public static NfcAdapter NfcCheck(Activity activity) {NfcAdapter mNfcAdapter = NfcAdapter.getDefaultAdapter(activity);if (mNfcAdapter == null) {return null;} else {if (!mNfcAdapter.isEnabled()) {Intent setNfc = new Intent(Settings.ACTION_NFC_SETTINGS);activity.startActivity(setNfc);}}return mNfcAdapter;}/*** 初始化nfc设置*/public static void NfcInit(Activity activity) {mPendingIntent = PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);IntentFilter filter2 = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);try {filter.addDataType("*/*");} catch (IntentFilter.MalformedMimeTypeException e) {e.printStackTrace();}mIntentFilter = new IntentFilter[]{filter, filter2};mTechList = null;}/*** 往nfc写入数据*/public static void writeNFCToTag(String data, Intent intent) throws IOException, FormatException {//1. 获取Tag对象Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);//额外的//2. 判断 NFC 标签的数据类型Ndef ndef = Ndef.get(tag);ndef.connect();NdefRecord ndefRecord = null;//这里android sdk 版本, 只有大于 21 ,才能使用录入功能if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {ndefRecord = NdefRecord.createTextRecord(null, data);}NdefRecord[] records = {ndefRecord};NdefMessage ndefMessage = new NdefMessage(records);//3. 写入数据ndef.writeNdefMessage(ndefMessage);}/*** 读取NFC的数据*/public static String readNFCFromTag(Intent intent) throws UnsupportedEncodingException {Parcelable[] rawArray = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);if (rawArray != null) {NdefMessage mNdefMsg = (NdefMessage) rawArray[0];NdefRecord mNdefRecord = mNdefMsg.getRecords()[0];if (mNdefRecord != null) {String readResult = new String(mNdefRecord.getPayload(), "UTF-8");return readResult;}}return "";}/*** 读取nfcID*/public static String readNFCId(Intent intent) throws UnsupportedEncodingException {Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);String id = ByteArrayToHexString(tag.getId());return id;}/*** 将字节数组转换为字符串*/private static String ByteArrayToHexString(byte[] inarray) {int i, j, in;String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};String out = "";for (j = 0; j < inarray.length; ++j) {in = (int) inarray[j] & 0xff;i = (in >> 4) & 0x0f;out += hex[i];i = in & 0x0f;out += hex[i];}return out;}/*** 读取 NFC** @return*/public static String readNFCFromTag(Tag tag){if (tag == null) {return "";}//2.判断NFC标签的数据类型(通过Ndef.get方法)Ndef ndef = Ndef.get(tag);return "";}/*** 读取 NFC TAG 方法*/public void readNFCTag(Intent intent){//1.获取Tag对象Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);}/*** 往标签写数据的方法*  https://blog.csdn.net/wolfking0608/article/details/72675180* @param tag*/public void writeNFCTag(Tag tag) {if (tag == null) {return;}//NdefMessage 描述NDEF格式的信息,实际上我们写入NFC标签的就是NdefMessage对象。//NdefRecord  描述NDEF信息的一个信息段,一个NdefMessage可能包含一个或者多个NdefRecord。NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{NdefRecord.createApplicationRecord(mActivity.getPackageName())});//转换成字节获得大小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);Log.i(TAG, "writeNFCTag: 写入成功");} else {//当我们买回来的NFC标签是没有格式化的,或者没有分区的执行此步//Ndef格式类NdefFormatable format = NdefFormatable.get(tag);//判断是否获得了NdefFormatable对象,有一些标签是只读的或者不允许格式化的if (format != null) {//连接format.connect();//格式化并将信息写入标签format.format(ndefMessage);Log.i(TAG, "writeNFCTag: 写入成功");} else {Log.i(TAG, "writeNFCTag: 写入失败");}}} catch (Exception e) {}}}

Activity 只是在 onNewIntent(Intent intent) 读取了 TagID 用 Toast 弹出 :

package com.fn.fn_nfc;import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;import java.io.UnsupportedEncodingException;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initData();}private void initData() {//nfc初始化设置NfcUtils nfcUtils = new NfcUtils(this);}@Overrideprotected void onResume() {super.onResume();//开启前台调度系统NfcUtils.mNfcAdapter.enableForegroundDispatch(this, NfcUtils.mPendingIntent, NfcUtils.mIntentFilter, NfcUtils.mTechList);}@Overrideprotected void onPause() {super.onPause();//关闭前台调度系统NfcUtils.mNfcAdapter.disableForegroundDispatch(this);}@Overrideprotected void onNewIntent(Intent intent) {super.onNewIntent(intent);//当该Activity接收到NFC标签时,运行该方法//调用工具方法,读取NFC数据try {String str = NfcUtils.readNFCId(intent);Log.i("fn_tag", "读取到的标签数据: " + str);Toast.makeText(this, "读取到的标签数据: " + str, Toast.LENGTH_SHORT).show();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}}

至此 , 可以直接运行了

Android NFC 标签读写读取快速开发教程 ( 整理来自 https://blog.csdn.net/wolfking0608/article/details/72675180 )相关推荐

  1. Android工程师遇到瓶颈后转行什么最好?https://blog.csdn.net/feiyu1947/article/details/86438086

    前言: Android不热门了,很多Android开发已经转行了,有的正走在转行的路上,还有的迟疑不决,到底要不要转呢?我们来分析下. 1. 转什么最好 每个人的自身情况不同,转的方向也会有所区别,转 ...

  2. 转载 java开发基础 https://blog.csdn.net/jiangjiewudi/article/details/9565749

    Java就是用来做项目的!Java的主要应用领域就是企业级的项目开发!要想从事企业级的项目开发,你必须掌握如下要点: 1.掌握项目开发的基本步骤 2.具备极强的面向对象的分析与设计技巧 3.掌握用例驱 ...

  3. Jmeter压力测试简单教程(包括服务器状态监控)-----转载自lsoqvle 的博客(https://blog.csdn.net/cbzcbzcbzcbz/article/details/780)

    步骤一  安装Jmeter 我用的版本是3.1版本,为什么是3.1,因为3.2有问题,我也是跑了一段时间后才知道3.2版本太新了还是什么的,有些功能就是不行,在此建议大家,不要轻易使用最新版本,次新版 ...

  4. Android NFC 标签读写Demo与历史漏洞概述

    文章目录 前言 NFC基础 1.1 RFID区别 1.2 工作模式 1.3 日常应用 NFC标签 2.1 标签应用 2.2 应用实践 2.3 标签预览 2.4 前台调度 NFC开发 3.1 NDEF数 ...

  5. GitHub 优秀的 Android 开源项目 转自http://blog.csdn.net/shulianghan/article/details/18046021

    原文地址为http://www.trinea.cn/android/android-open-source-projects-view/,作者Trinea 主要介绍那些不错个性化的View,包括Lis ...

  6. Android Volley完全解析(一),初识Volley的基本用法 转载地址:http://blog.csdn.net/guolin_blog/article/details/17482095

    转载地址:http://blog.csdn.net/guolin_blog/article/details/17482095 1. Volley简介 我们平时在开发Android应用的时候不可避免地都 ...

  7. Android Volley完全解析(二),使用Volley加载网络图片 转载:http://blog.csdn.net/guolin_blog/article/details/174

    转载:http://blog.csdn.net/guolin_blog/article/details/17482165 在上一篇文章中,我们了解了Volley到底是什么,以及它的基本用法.本篇文章中 ...

  8. solr教程,值得刚接触搜索开发人员一看(转载:http://blog.csdn.net/awj3584/article/details/16963525)

    Solr调研总结 开发类型 全文检索相关开发 Solr版本 4.2 文件内容 本文介绍solr的功能使用及相关注意事项;主要包括以下内容:环境搭建及调试;两个核心配置文件介绍;维护索引;查询索引,和在 ...

  9. 移动WebApp开发-phoneGap+android入门(http://blog.csdn.net/cwb1128/article/details/18004505)

      随着HTML5的快速发展,以及大家对于跨平台的移动App开发的渴望,使用PhoneGap开发的需求也会越来越多.根据网络上的资料,并结合自己的实践,搭建了基于phoneGap的android开发环 ...

  10. Windows Phone开发(28):隔离存储B 转:http://blog.csdn.net/tcjiaan/article/details/7436959...

    上一节我们聊了目录的操作,这一节我们继续来看看如何读写文件. 首先说一下题外话,许多朋友都在摇摆不定,三心二意,其实这样的学习态度是很不好的,如果你对Windows phone开发有兴趣,如果你真想学 ...

最新文章

  1. eclipse安装Log4E插件以及简单使用
  2. 多线程随机数组生成+双线程快速排序(C++实现)(0.2秒排100W个数字)
  3. 谷歌 Daydream 实验室:VR中学习新技能是一种怎样的体验?
  4. wordpress 首页调用文章 不同样式的方法
  5. 【Python】集合的交、并、补、差集怎么算?
  6. 空投坐标怎么看6_装修时,怎么确认自己买的“瓷砖”是优等品?看“6点”很重要...
  7. VS2012 快捷键 VS RESHARPER 设置
  8. js setTimeout 使用方法
  9. select中option解析
  10. ExceptionError
  11. 刚刚修复的Windows 0day和Chrome 0day 已被组合用于 WizardOpium 攻击(详解)
  12. Coolite Toolkit学习笔记七:常用控件TreePanel
  13. 【互联网协议】北邮国际学院大三上基础知识
  14. 使用树莓派4B最新官方烧录软件烧录镜像设置密码,直接登录wifi
  15. 牛客网华为机试在线训练JavaScript版解答
  16. 前端优化之二 图片优化——质量与性能的博弈
  17. c语言大一期中考试知识点,大一期末考试复习计划
  18. 【python】计算圆周率到任意位支持任意位(速度快)
  19. Linux学习(六):proftpd搭建,完美解决vsftpd中文引号bug
  20. MATLAB图像数字水印的方案

热门文章

  1. web开发中多线程下载文件
  2. org.apache.commons.fileupload.DiskFileUpload1
  3. git-svn使用教程:git与svn进行同步
  4. 写一篇meta分析要多少时间?如何写好一篇Meta分析,你需要这样做
  5. ROS学习—【在solidworks环境中将六自由度机械臂转换为URDF模型】
  6. iChart--组件定制
  7. 现代通信原理:第七章部分习题答案
  8. 力软框架java_力软敏捷框架 jfGrid 使用例子之一
  9. (转)中国著名黑客你知道多少?
  10. Linux CentOS 7 下 安装SimHei字体