android nfc常用标签读取总结_Android

有几天没有更新博客了不过本篇却准备了许久希望能带给每一位开发者最简单高效的学习方式。废话到此为止下面开始正文。

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卡,以便于您获取更多的相关知识。

时间: 2024-09-27 20:55:32

android nfc常用标签读取总结_Android的相关文章

Android开发常用标签小结_Android

本文较为详细的总结了Android开发常用标签.分享给大家供大家参考.具体如下: android中inputType android中inputType属性在EditText输入值时启动的虚拟键盘的风格有着重要的作用.这也大大的方便的操作.有时需要虚拟键盘只为字符或只为数字.所以inputType尤为重要. android:paddingLeft与android:layout_marginLeft的区别 当按钮分别设置以上两个属性时,得到的效果是不一样的. android:paddingLeft

Android开发常用标签小结

本文较为详细的总结了Android开发常用标签.分享给大家供大家参考.具体如下: android中inputType android中inputType属性在EditText输入值时启动的虚拟键盘的风格有着重要的作用.这也大大的方便的操作.有时需要虚拟键盘只为字符或只为数字.所以inputType尤为重要. android:paddingLeft与android:layout_marginLeft的区别 当按钮分别设置以上两个属性时,得到的效果是不一样的. android:paddingLeft

Android EditText常用属性功能汇总_Android

本文总结分析了Android EditText常用属性.分享给大家供大家参考,具体如下: android:hint="请输入数字!"//设置显示在空间上的提示信息 android:numeric="integer"//设置只能输入整数,如果是小数则是:decimal android:singleLine="true"//设置单行输入,一旦设置为true,则文字不会自动换行. android:password="true"//设

Android中使用SDcard读取文件_Android

平时我们需要在手机上面存储想音频,视频等等的大文件,以前学过使用File进行存储(使用File操作进行存储):由于考虑到手机本身的存储空间小,这时候我们需要把文件存储在SDcard中,今天自己也学习了一下在android进行sdcard的存储使用方法; 首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!-- 在SDCard中创建与删除文件权限 --

Android ListView常用小技巧汇总_Android

ListView在我们Android项目中的地位是有目共睹的,相信几乎每一个App中都有它的身影. ListView主要是用列表形式来加载数据,在特定情况下需要实现一些特殊功能:如刷新数据,加载数据,实现动画效果等. 作为我们常用的控件,有哪些需要注意的呢? **为ListView的每一Item设置分隔线 第一种方法:也是最简单地方法,在布局文件中设置ListView的 divider属性 如:android:divider="@color/black" 第二种方法:设置android

36个Android开发常用经典代码大全_Android

本文汇集36个Android开发常用经典代码片段,包括拨打电话.发送短信.唤醒屏幕并解锁.是否有网络连接.动态显示或者是隐藏软键盘等,希望对您有所帮助. //36个Android开发常用代码片段 //拨打电话 public static void call(Context context, String phoneNumber) { context.startActivity( new Intent(Intent.ACTION_CALL, Uri.parse( "tel:" + pho

Android编程常用技巧实例总结_Android

本文实例讲述了Android编程常用技巧.分享给大家供大家参考,具体如下: 1. 登录的时候,如果输入有误,则输入框左右震动,表示输入有误 在res下准备一个anim文件夹,里面包含两个文件,main_login_shake.xml 和 main_login_cycle_7.xml, 其中,main_login_shake.xml如下: <?xml version="1.0" encoding="utf-8"?> <translate xmlns:

在Unity中捕捉Android的常用按钮返回事件_Android

在Unity开发中捕捉Android的常用事件其实很简单 Input.GetKey(KeyCode.Escape) Input.GetKeyDown(KeyCode.Home) // 返回键 if ( Application.platform == RuntimePlatform.Android &&(Input.GetKeyDown(KeyCode.Escape))) { //.... } // Home键 if ( Application.platform == RuntimePlat

解析android中include标签的使用_Android

在一个项目中我们可能会需要用到相同的布局设计,如果都写在一个xml文件中,代码显得很冗余,并且可读性也很差,所以我们可以把相同布局的代码单独写成一个模块,然后用到的时候可以通过<include /> 标签来重用layout代码.app_title.xml: 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?><RelativeLayout android:id="@+id/titl